-
Notifications
You must be signed in to change notification settings - Fork 717
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
251e6c4
commit 16b9007
Showing
10 changed files
with
1,033 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
@~D&�o%�[�_j��szf=5����f�C�d�ӥ���91)���U�=���@JxӼl]a��f? |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
-----BEGIN CCQ SERVER SIGNING KEY----- | ||
PublicKey: 0x25021A4FCAf61F2EADC8202D3833Df48B2Fa0D54 | ||
|
||
CiCWNLSaicmcA2T563fLSM0r2uFviwPdA1VV9i76DlJh3Q== | ||
=NV1/ | ||
-----END CCQ SERVER SIGNING KEY----- |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
{ | ||
"permissions": [ | ||
{ | ||
"userName": "Test User", | ||
"apiKey": "my_secret_key", | ||
"allowedCalls": [ | ||
{ | ||
"ethCall": { | ||
"note:": "Name of WETH on Goerli", | ||
"chain": 2, | ||
"contractAddress": "B4FBF271143F4FBf7B91A5ded31805e42b2208d6", | ||
"call": "0x06fdde03" | ||
} | ||
}, | ||
{ | ||
"ethCall": { | ||
"note:": "Total supply of WETH on Goerli", | ||
"chain": 2, | ||
"contractAddress": "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6", | ||
"call": "0x18160ddd" | ||
} | ||
}, | ||
{ | ||
"ethCall": { | ||
"note:": "Name of WETH on Devnet", | ||
"chain": 2, | ||
"contractAddress": "0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E", | ||
"call": "0x06fdde03" | ||
} | ||
}, | ||
{ | ||
"ethCall": { | ||
"note:": "Total supply of WETH on Devnet", | ||
"chain": 2, | ||
"contractAddress": "0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E", | ||
"call": "0x18160ddd" | ||
} | ||
} | ||
] | ||
}, | ||
{ | ||
"userName": "Test User Two", | ||
"apiKey": "my_secret_key_2", | ||
"allowUnsigned": true, | ||
"allowedCalls": [ | ||
{ | ||
"ethCall": { | ||
"note:": "Name of WETH on Goerli", | ||
"chain": 2, | ||
"contractAddress": "0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6", | ||
"call": "0x06fdde03" | ||
} | ||
}, | ||
{ | ||
"ethCall": { | ||
"note:": "Name of WETH on Devnet", | ||
"chain": 2, | ||
"contractAddress": "0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E", | ||
"call": "0x06fdde03" | ||
} | ||
} | ||
] | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
package ccq | ||
|
||
import ( | ||
"crypto/ecdsa" | ||
"encoding/hex" | ||
"encoding/json" | ||
"fmt" | ||
"net/http" | ||
"sort" | ||
"time" | ||
|
||
"github.com/certusone/wormhole/node/pkg/common" | ||
gossipv1 "github.com/certusone/wormhole/node/pkg/proto/gossip/v1" | ||
"github.com/certusone/wormhole/node/pkg/query" | ||
"github.com/gorilla/mux" | ||
pubsub "github.com/libp2p/go-libp2p-pubsub" | ||
"go.uber.org/zap" | ||
"google.golang.org/protobuf/proto" | ||
) | ||
|
||
type queryRequest struct { | ||
Bytes string `json:"bytes"` | ||
Signature string `json:"signature"` | ||
} | ||
|
||
type queryResponse struct { | ||
Bytes string `json:"bytes"` | ||
Signatures []string `json:"signatures"` | ||
} | ||
|
||
type httpServer struct { | ||
topic *pubsub.Topic | ||
logger *zap.Logger | ||
env common.Environment | ||
permissions Permissions | ||
signerKey *ecdsa.PrivateKey | ||
pendingResponses *PendingResponses | ||
} | ||
|
||
func (s *httpServer) handleQuery(w http.ResponseWriter, r *http.Request) { | ||
// Set CORS headers for all requests. | ||
w.Header().Set("Access-Control-Allow-Origin", "*") | ||
|
||
// Set CORS headers for the preflight request | ||
if r.Method == http.MethodOptions { | ||
|
||
w.Header().Set("Access-Control-Allow-Methods", "PUT") | ||
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, X-Api-Key") | ||
w.Header().Set("Access-Control-Max-Age", "3600") | ||
w.WriteHeader(http.StatusNoContent) | ||
return | ||
} | ||
var q queryRequest | ||
err := json.NewDecoder(r.Body).Decode(&q) | ||
if err != nil { | ||
http.Error(w, err.Error(), http.StatusBadRequest) | ||
return | ||
} | ||
|
||
// There should be one and only one API key in the header. | ||
apiKey, exists := r.Header["X-Api-Key"] | ||
if !exists || len(apiKey) != 1 { | ||
s.logger.Debug("received a request without an api key", zap.Stringer("url", r.URL), zap.Error(err)) | ||
http.Error(w, "api key is missing", http.StatusBadRequest) | ||
return | ||
} | ||
|
||
queryRequestBytes, err := hex.DecodeString(q.Bytes) | ||
if err != nil { | ||
s.logger.Debug("failed to decode request bytes", zap.Error(err)) | ||
http.Error(w, err.Error(), http.StatusBadRequest) | ||
return | ||
} | ||
|
||
signature, err := hex.DecodeString(q.Signature) | ||
if err != nil { | ||
s.logger.Debug("failed to decode signature bytes", zap.Error(err)) | ||
http.Error(w, err.Error(), http.StatusBadRequest) | ||
return | ||
} | ||
|
||
signedQueryRequest := &gossipv1.SignedQueryRequest{ | ||
QueryRequest: queryRequestBytes, | ||
Signature: signature, | ||
} | ||
|
||
if err := validateRequest(s.logger, s.env, s.permissions, s.signerKey, apiKey[0], signedQueryRequest); err != nil { | ||
s.logger.Debug("invalid request", zap.String("api_key", apiKey[0]), zap.Error(err)) | ||
http.Error(w, err.Error(), http.StatusBadRequest) | ||
return | ||
} | ||
|
||
m := gossipv1.GossipMessage{ | ||
Message: &gossipv1.GossipMessage_SignedQueryRequest{ | ||
SignedQueryRequest: signedQueryRequest, | ||
}, | ||
} | ||
|
||
b, err := proto.Marshal(&m) | ||
if err != nil { | ||
s.logger.Debug("failed to marshal gossip message", zap.Error(err)) | ||
http.Error(w, err.Error(), http.StatusInternalServerError) | ||
return | ||
} | ||
|
||
pendingResponse := NewPendingResponse(signedQueryRequest) | ||
added := s.pendingResponses.Add(pendingResponse) | ||
if !added { | ||
http.Error(w, "Duplicate request", http.StatusInternalServerError) | ||
return | ||
} | ||
|
||
err = s.topic.Publish(r.Context(), b) | ||
if err != nil { | ||
s.logger.Debug("failed to publish gossip message", zap.Error(err)) | ||
http.Error(w, err.Error(), http.StatusInternalServerError) | ||
s.pendingResponses.Remove(pendingResponse) | ||
return | ||
} | ||
|
||
// Wait for the response or timeout | ||
select { | ||
case <-time.After(query.RequestTimeout + 5*time.Second): | ||
http.Error(w, "Timed out waiting for response", http.StatusGatewayTimeout) | ||
case res := <-pendingResponse.ch: | ||
resBytes, err := res.Response.Marshal() | ||
if err != nil { | ||
http.Error(w, err.Error(), http.StatusInternalServerError) | ||
break | ||
} | ||
// Signature indices must be ascending for on-chain verification | ||
sort.Slice(res.Signatures, func(i, j int) bool { | ||
return res.Signatures[i].Index < res.Signatures[j].Index | ||
}) | ||
signatures := make([]string, 0, len(res.Signatures)) | ||
for _, s := range res.Signatures { | ||
// ECDSA signature + a byte for the index of the guardian in the guardian set | ||
signature := fmt.Sprintf("%s%02x", s.Signature, uint8(s.Index)) | ||
signatures = append(signatures, signature) | ||
} | ||
w.Header().Add("Content-Type", "application/json") | ||
err = json.NewEncoder(w).Encode(&queryResponse{ | ||
Signatures: signatures, | ||
Bytes: hex.EncodeToString(resBytes), | ||
}) | ||
if err != nil { | ||
http.Error(w, err.Error(), http.StatusInternalServerError) | ||
} | ||
} | ||
|
||
s.pendingResponses.Remove(pendingResponse) | ||
} | ||
|
||
func (s *httpServer) handleHealth(w http.ResponseWriter, r *http.Request) { | ||
s.logger.Debug("health check") | ||
} | ||
|
||
func NewHTTPServer(addr string, t *pubsub.Topic, permissions Permissions, signerKey *ecdsa.PrivateKey, p *PendingResponses, logger *zap.Logger, env common.Environment) *http.Server { | ||
s := &httpServer{ | ||
topic: t, | ||
permissions: permissions, | ||
signerKey: signerKey, | ||
pendingResponses: p, | ||
logger: logger, | ||
env: env, | ||
} | ||
r := mux.NewRouter() | ||
r.HandleFunc("/v1/query", s.handleQuery).Methods("PUT", "OPTIONS") | ||
r.HandleFunc("/v1/health", s.handleHealth).Methods("GET") | ||
return &http.Server{ | ||
Addr: addr, | ||
Handler: r, | ||
ReadHeaderTimeout: 5 * time.Second, | ||
} | ||
} |
Oops, something went wrong.