| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179 |
- // 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)
- }
|