wormhole/event_database/cloud_functions/fetch_row.go

180 lines
4.7 KiB
Go

// Package p contains an HTTP Cloud Function.
package p
import (
"context"
"encoding/json"
"fmt"
"html"
"io"
"log"
"net/http"
"os"
"strings"
"sync"
"cloud.google.com/go/bigtable"
)
// client is a global Bigtable client, to avoid initializing a new client for
// every request.
var client *bigtable.Client
var clientOnce sync.Once
var columnFamilies = []string{"MessagePublication", "Signatures", "VAAState", "QuorumState"}
type (
MessagePub struct {
InitiatingTxID string
Payload []byte
}
Summary struct {
Message MessagePub
GuardianAddresses []string
SignedVAA []byte
QuorumTime string
}
)
func makeSummary(row bigtable.Row) *Summary {
summary := &Summary{}
if _, ok := row[columnFamilies[0]]; ok {
message := &MessagePub{}
for _, item := range row[columnFamilies[0]] {
switch item.Column {
case "MessagePublication:InitiatingTxID":
message.InitiatingTxID = string(item.Value)
case "MessagePublication:Payload":
message.Payload = item.Value
}
}
summary.Message = *message
}
if _, ok := row[columnFamilies[1]]; ok {
for _, item := range row[columnFamilies[1]] {
column := strings.Split(item.Column, ":")
summary.GuardianAddresses = append(summary.GuardianAddresses, column[1])
}
}
if _, ok := row[columnFamilies[3]]; ok {
for _, item := range row[columnFamilies[3]] {
if item.Column == "QuorumState:SignedVAA" {
summary.SignedVAA = item.Value
summary.QuorumTime = item.Timestamp.Time().String()
}
}
}
return summary
}
func ReadRow(w http.ResponseWriter, r *http.Request) {
// Set CORS headers for the preflight request
if r.Method == http.MethodOptions {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "POST")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
w.Header().Set("Access-Control-Max-Age", "3600")
w.WriteHeader(http.StatusNoContent)
return
}
// Set CORS headers for the main request.
w.Header().Set("Access-Control-Allow-Origin", "*")
var rowKey string
// allow GET requests with querystring params, or POST requests with json body.
switch r.Method {
case http.MethodGet:
queryParams := r.URL.Query()
emitterChain := queryParams.Get("emitterChain")
emitterAddress := queryParams.Get("emitterAddress")
sequence := queryParams.Get("sequence")
readyCheck := queryParams.Get("readyCheck")
if readyCheck != "" {
// for running in devnet
w.WriteHeader(http.StatusOK)
fmt.Fprint(w, html.EscapeString("ready"))
return
}
// check for empty values
if emitterChain == "" || emitterAddress == "" || sequence == "" {
fmt.Fprint(w, "body values cannot be empty")
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}
rowKey = emitterChain + ":" + emitterAddress + ":" + sequence
case http.MethodPost:
// declare request body properties
var d struct {
EmitterChain string `json:"emitterChain"`
EmitterAddress string `json:"emitterAddress"`
Sequence string `json:"sequence"`
}
// deserialize request body
if err := json.NewDecoder(r.Body).Decode(&d); err != nil {
switch err {
case io.EOF:
fmt.Fprint(w, "request body required")
return
default:
log.Printf("json.NewDecoder: %v", err)
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}
}
// check for empty values
if d.EmitterChain == "" || d.EmitterAddress == "" || d.Sequence == "" {
fmt.Fprint(w, "body values cannot be empty")
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}
rowKey = d.EmitterChain + ":" + d.EmitterAddress + ":" + d.Sequence
default:
http.Error(w, "405 - Method Not Allowed", http.StatusMethodNotAllowed)
log.Println("Method Not Allowed")
return
}
clientOnce.Do(func() {
// Declare a separate err variable to avoid shadowing client.
var err error
project := os.Getenv("GCP_PROJECT")
client, err = bigtable.NewClient(context.Background(), project, "wormhole")
if err != nil {
http.Error(w, "Error initializing client", http.StatusInternalServerError)
log.Printf("bigtable.NewClient: %v", err)
return
}
})
tbl := client.Open("v2Events")
row, err := tbl.ReadRow(r.Context(), rowKey)
if err != nil {
http.Error(w, "Error reading rows", http.StatusInternalServerError)
log.Printf("tbl.ReadRows(): %v", err)
return
}
if row == nil {
http.NotFound(w, r)
log.Printf("did not find row for key %v", rowKey)
return
}
summary := makeSummary(row)
jsonBytes, err := json.Marshal(summary)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error()))
log.Println(err.Error())
return
}
w.WriteHeader(http.StatusOK)
w.Write(jsonBytes)
}