Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DRAFT] CCQ: Cross-Chain Query Integration #2981

Closed
wants to merge 37 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
7e3276d
WIP: CCQ
evan-gray May 18, 2023
d486af7
WIP: CCQ add block to call
evan-gray May 21, 2023
dbd20aa
WIP: CCQ serialize and sign response
evan-gray May 23, 2023
6fb836d
WIP: CCQ deserialize response
evan-gray May 24, 2023
fd1cdac
CCQ: Add enable flag and feature flag (#2986)
bruce-riley May 24, 2023
2490182
Move flag check to p2p (#2987)
bruce-riley May 25, 2023
050b96e
CCQ: Add ccqAllowedRequesters parameter (#2990)
bruce-riley May 25, 2023
947975f
CCQ: Use environment specific prefix to sign (#2994)
bruce-riley May 26, 2023
b6787cc
CCQ: Add request retries (#2995)
bruce-riley May 26, 2023
4458d0a
CCQ: Improve retry logic (#3008)
bruce-riley May 31, 2023
dbcf8d5
CCQ: Validation and marshalling changes (#3017)
bruce-riley May 31, 2023
0720224
CCQ: Added integration test
kev1n-peters May 30, 2023
20799c8
CCQ: Batch queries (#3023)
bruce-riley Jun 12, 2023
0f3634a
tilt: deploy core and token bridge to wormchain (#3084)
panoel Jun 15, 2023
9ddee1a
CCQ: Messaging changes (#3093)
bruce-riley Jun 21, 2023
f2a0611
CCQ: Reorg code into a query package (#3112)
bruce-riley Jun 22, 2023
abecbb7
CCQ: Message tweaks (#3147)
bruce-riley Jun 28, 2023
e1fd3b4
CCQ: Make work with node refactor (#3154)
bruce-riley Jun 30, 2023
064ffa0
CCQ: revert polygonRPC
evan-gray Aug 3, 2023
7f2ad4a
CCQ: evm example
evan-gray Jun 1, 2023
46c8127
Rework to support batches
bruce-riley Jun 28, 2023
977618c
Start using forge tests
bruce-riley Jul 21, 2023
d8f84c2
Convert to a library
bruce-riley Jul 31, 2023
97b5822
ccq: Query Server (#3113)
kev1n-peters Aug 25, 2023
bf29fb4
sdk/js-query: initial commit
kev1n-peters Aug 28, 2023
31a7555
CCQ: Gossip split (#3320)
bruce-riley Sep 1, 2023
69dfd25
CCQ: Server allow list (#3347)
bruce-riley Sep 5, 2023
30c4751
CCQ: Add some sdk tests (#3357)
bruce-riley Sep 6, 2023
a97a786
CCQ: ccq_server health check (#3365)
bruce-riley Sep 8, 2023
328b6de
CCQ: Abstract contract
bruce-riley Sep 12, 2023
e079566
CCQ: Server allow unsigned requests (#3379)
bruce-riley Sep 29, 2023
81f102d
CCQ: Query demo contract (#3373)
bruce-riley Oct 1, 2023
5c92689
CCQ: Fix tilt CI tests (#3410)
bruce-riley Oct 1, 2023
2067e47
CCQ: Query server CORS change (#3415)
bruce-riley Oct 2, 2023
e5efea2
CCQ: Query server env is hard coded to devnet (#3417)
bruce-riley Oct 2, 2023
7dfe9c4
CCQ: Ethereum parsing library rework (#3404)
bruce-riley Oct 4, 2023
33823dc
CCQ: Eth library gas tweaks (#3420)
bruce-riley Oct 4, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions Dockerfile.const
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,18 @@
FROM cli-gen as cli-export
FROM node:18-alpine@sha256:44aaf1ccc80eaed6572a0f2ef7d6b5a2982d54481e4255480041ac92221e2f11 as const-build

# fetch scripts/guardian-set-init.sh deps
RUN apk update && apk add bash g++ make python3 curl jq findutils

# Support additional root CAs
COPY README.md cert.pem* /certs/
# Alpine
RUN if [ -e /certs/cert.pem ]; then cp /certs/cert.pem /etc/ssl/cert.pem; fi
# Node
ENV NODE_EXTRA_CA_CERTS=/certs/cert.pem
ENV NODE_OPTIONS=--use-openssl-ca
# npm
RUN if [ -e /certs/cert.pem ]; then npm config set cafile /certs/cert.pem; fi

# fetch scripts/guardian-set-init.sh deps
RUN apk update && apk add bash g++ make python3 curl jq findutils

# Copy and link CLI
COPY --from=cli-export clients/js /cli
Expand Down
22 changes: 21 additions & 1 deletion Tiltfile
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ config.define_bool("wormchain", False, "Enable a wormchain node")
config.define_bool("ibc_relayer", False, "Enable IBC relayer between cosmos chains")
config.define_bool("redis", False, "Enable a redis instance")
config.define_bool("generic_relayer", False, "Enable the generic relayer off-chain component")

config.define_bool("query_server", False, "Enable cross-chain query server")

cfg = config.parse()
num_guardians = int(cfg.get("num", "1"))
Expand All @@ -97,6 +97,7 @@ ibc_relayer = cfg.get("ibc_relayer", ci)
btc = cfg.get("btc", False)
redis = cfg.get('redis', ci)
generic_relayer = cfg.get("generic_relayer", ci)
query_server = cfg.get("query_server", ci)

if ci:
guardiand_loglevel = cfg.get("guardiand_loglevel", "warn")
Expand Down Expand Up @@ -584,6 +585,12 @@ if ci_tests:
trigger_mode = trigger_mode,
resource_deps = [], # uses devnet-consts.json, but wormchain/contracts/tools/test_accountant.sh handles waiting for guardian, not having deps gets the build earlier
)
k8s_resource(
"query-ci-tests",
labels = ["ci"],
trigger_mode = trigger_mode,
resource_deps = [], # node/hack/query/test/test_query.sh handles waiting for guardian, not having deps gets the build earlier
)

if terra_classic:
docker_build(
Expand Down Expand Up @@ -893,3 +900,16 @@ if aptos:
labels = ["aptos"],
trigger_mode = trigger_mode,
)

if query_server:
k8s_yaml_with_ns("devnet/query-server.yaml")

k8s_resource(
"query-server",
resource_deps = ["guardian"],
port_forwards = [
port_forward(6069, name = "REST [:6069]", host = webHost)
],
labels = ["query-server"],
trigger_mode = trigger_mode
)
12 changes: 11 additions & 1 deletion devnet/node.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ metadata:
app: guardian
spec:
ports:
- port: 8996
name: ccq-p2p
protocol: UDP
- port: 8999
name: p2p
protocol: UDP
Expand Down Expand Up @@ -160,7 +163,11 @@ spec:
- /tmp/data
- --publicRpcLogDetail
- "full"
# - --chainGovernorEnabled=true
# - --chainGovernorEnabled=true
- --ccqEnabled=true
- --ccqAllowedRequesters=beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe,25021A4FCAf61F2EADC8202D3833Df48B2Fa0D54
- --ccqAllowedPeers=12D3KooWSnju8zhywCYVi2JwTqky1sySPnmtYLsHHzc4WerMnDQH
# - --logLevel=debug
securityContext:
capabilities:
add:
Expand All @@ -171,6 +178,9 @@ spec:
port: 6060
path: /readyz
ports:
- containerPort: 8996
name: ccq-p2p
protocol: UDP
- containerPort: 8999
name: p2p
protocol: UDP
Expand Down
60 changes: 60 additions & 0 deletions devnet/query-server.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
apiVersion: v1
kind: Service
metadata:
name: query-server
labels:
app: query-server
spec:
ports:
- name: rest
port: 6069
protocol: TCP
selector:
app: query-server
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: query-server
spec:
selector:
matchLabels:
app: query-server
serviceName: query-server
replicas: 1
template:
metadata:
labels:
app: query-server
spec:
containers:
- name: query-server
image: guardiand-image
command:
- /guardiand
- query-server
- --env
- dev
- --nodeKey
- node/cmd/ccq/ccq.p2p.key
- --signerKey
- node/cmd/ccq/ccq.signing.key
- --listenAddr
- "[::]:6069"
- --permFile
- "node/cmd/ccq/devnet.config.json"
- --ethRPC
- http://eth-devnet:8545
- --ethContract
- "0xC89Ce4735882C9F0f0FE26686c53074E09B0D550"
# Hardcoded devnet bootstrap (generated from deterministic key in guardiand)
- --bootstrap
- /dns4/guardian-0.guardian/udp/8996/quic/p2p/12D3KooWL3XJ9EMCyZvmmGXL2LMiVBtrVa2BuESsJiXkSj7333Jw
- --logLevel=info
ports:
- containerPort: 6069
name: rest
protocol: TCP
readinessProbe:
tcpSocket:
port: rest
25 changes: 25 additions & 0 deletions devnet/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,28 @@ spec:
- "/app/accountant/success"
initialDelaySeconds: 5
periodSeconds: 5
---
kind: Job
apiVersion: batch/v1
metadata:
name: query-ci-tests
spec:
backoffLimit: 0
template:
spec:
restartPolicy: Never
containers:
- name: query-ci-tests
image: guardiand-image
command:
- /bin/sh
- -c
- "cd /app/node/hack/query/test && bash test_query.sh && touch success"
readinessProbe:
exec:
command:
- test
- -e
- "/app/node/hack/query/test/success"
initialDelaySeconds: 5
periodSeconds: 5
103 changes: 103 additions & 0 deletions ethereum/contracts/query/QueryDemo.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// contracts/query/QueryDemo.sol
// SPDX-License-Identifier: Apache 2

pragma solidity ^0.8.0;

import "../libraries/external/BytesLib.sol";
import "../interfaces/IWormhole.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Context.sol";
import "./QueryResponse.sol";

/// @dev QueryDemo is a library that implements the parsing and verification of Cross Chain Query (CCQ) responses.
contract QueryDemo is Context, QueryResponse {
using BytesLib for bytes;

struct ChainEntry {
uint16 chainID;
address contractAddress;
uint256 counter;
uint256 blockNum;
uint256 blockTime;
}

address private immutable owner;
address private immutable wormhole;
uint16 private immutable myChainID;
mapping(uint16 => ChainEntry) private counters;
uint16[] private foreignChainIDs;

bytes4 GetMyCounter = bytes4(hex"916d5743");

constructor(address _owner, address _wormhole, uint16 _myChainID) {
owner = _owner;
wormhole = _wormhole;
myChainID = _myChainID;
counters[_myChainID] = ChainEntry(_myChainID, address(this), 0, 0, 0);
}

// updateRegistration should be used to add the other chains and to set / update contract addresses.
function updateRegistration(uint16 _chainID, address _contractAddress) public onlyOwner {
if (counters[_chainID].chainID == 0) {
foreignChainIDs.push(_chainID);
counters[_chainID].chainID = _chainID;
}

counters[_chainID].contractAddress = _contractAddress;
}

// getMyCounter (call signature 916d5743) returns the counter value for this chain. It is meant to be used in a cross chain query.
function getMyCounter() public view returns (uint256) {
return counters[myChainID].counter;
}

// getState() returns this chain's view of all the counters. It is meant to be used in the front end.
function getState() public view returns (ChainEntry[] memory) {
ChainEntry[] memory ret = new ChainEntry[](foreignChainIDs.length + 1);
ret[0] = counters[myChainID];
for (uint idx=0; idx<foreignChainIDs.length; idx++) {
ret[idx+1] = counters[foreignChainIDs[idx]];
}

return ret;
}

// updateCounters takes the cross chain query response for the two other counters, stores the results for the other chains, and updates the counter for this chain.
function updateCounters(bytes memory response, IWormhole.Signature[] memory signatures) public {
uint256 adjustedBlockTime;
ParsedQueryResponse memory r = parseAndVerifyQueryResponse(address(wormhole), response, signatures);
require(r.responses.length == foreignChainIDs.length, "unexpected number of results");
for (uint idx=0; idx<r.responses.length; idx++) {
require(counters[r.responses[idx].chainId].chainID == foreignChainIDs[idx], "unexpected foreign chain ID");
EthCallQueryResponse memory eqr = parseEthCallQueryResponse(r.responses[idx]);
require(eqr.blockNum > counters[r.responses[idx].chainId].blockNum, "update is obsolete");
// wormhole time is in microseconds, timestamp is in seconds
adjustedBlockTime = eqr.blockTime / 1_000_000;
require(adjustedBlockTime > block.timestamp - 300, "update is stale");
require(eqr.result.length == 1, "result mismatch");
require(eqr.result[0].contractAddress == counters[r.responses[idx].chainId].contractAddress, "contract address is wrong");

// TODO: Is there an easier way to verify that the call data is correct!
bytes memory callData = eqr.result[0].callData;
bytes4 result;
assembly {
result := mload(add(callData, 32))
}
require(result == GetMyCounter, "unexpected callData");

require(eqr.result[0].result.length == 32, "result is not a uint256");
counters[r.responses[idx].chainId].blockNum = eqr.blockNum;
counters[r.responses[idx].chainId].blockTime = adjustedBlockTime;
counters[r.responses[idx].chainId].counter = abi.decode(eqr.result[0].result, (uint256));
}

counters[myChainID].blockNum = block.number;
counters[myChainID].blockTime = block.timestamp;
counters[myChainID].counter += 1;
}

modifier onlyOwner() {
require(owner == _msgSender(), "caller is not the owner");
_;
}
}
Loading