| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191 |
- package p
- import (
- "context"
- "log"
- "net/http"
- "os"
- "strings"
- "sync"
- "cloud.google.com/go/bigtable"
- "cloud.google.com/go/pubsub"
- "github.com/certusone/wormhole/node/pkg/vaa"
- )
- // shared code for the various functions, primarily response formatting.
- // client is a global Bigtable client, to avoid initializing a new client for
- // every request.
- var client *bigtable.Client
- var clientOnce sync.Once
- var tbl *bigtable.Table
- var pubsubClient *pubsub.Client
- var pubSubTokenTransferDetailsTopic *pubsub.Topic
- var coinGeckoCoins = map[string][]CoinGeckoCoin{}
- var solanaTokens = map[string]SolanaToken{}
- // init runs during cloud function initialization. So, this will only run during an
- // an instance's cold start.
- // https://cloud.google.com/functions/docs/bestpractices/networking#accessing_google_apis
- func init() {
- clientOnce.Do(func() {
- // Declare a separate err variable to avoid shadowing client.
- var err error
- project := os.Getenv("GCP_PROJECT")
- instance := os.Getenv("BIGTABLE_INSTANCE")
- client, err = bigtable.NewClient(context.Background(), project, instance)
- if err != nil {
- // http.Error(w, "Error initializing client", http.StatusInternalServerError)
- log.Printf("bigtable.NewClient error: %v", err)
- return
- }
- var pubsubErr error
- pubsubClient, pubsubErr = pubsub.NewClient(context.Background(), project)
- if pubsubErr != nil {
- log.Printf("pubsub.NewClient error: %v", pubsubErr)
- return
- }
- })
- tbl = client.Open("v2Events")
- // create the topic that will be published to after decoding token transfer payloads
- tokenTransferDetailsTopic := os.Getenv("PUBSUB_TOKEN_TRANSFER_DETAILS_TOPIC")
- if tokenTransferDetailsTopic != "" {
- pubSubTokenTransferDetailsTopic = pubsubClient.Topic(tokenTransferDetailsTopic)
- // fetch the token lists once at start up
- coinGeckoCoins = fetchCoinGeckoCoins()
- solanaTokens = fetchSolanaTokenList()
- }
- }
- var columnFamilies = []string{
- "MessagePublication",
- "QuorumState",
- "TokenTransferPayload",
- "AssetMetaPayload",
- "NFTTransferPayload",
- "TokenTransferDetails",
- "ChainDetails",
- }
- type (
- Summary struct {
- EmitterChain string
- EmitterAddress string
- Sequence string
- InitiatingTxID string
- Payload []byte
- SignedVAABytes []byte
- QuorumTime string
- }
- // Details is a Summary, with the VAA decoded as SignedVAA
- Details struct {
- SignedVAA *vaa.VAA
- EmitterChain string
- EmitterAddress string
- Sequence string
- InitiatingTxID string
- Payload []byte
- SignedVAABytes []byte
- QuorumTime string
- }
- )
- func chainIdStringToType(chainId string) vaa.ChainID {
- switch chainId {
- case "1":
- return vaa.ChainIDSolana
- case "2":
- return vaa.ChainIDEthereum
- case "3":
- return vaa.ChainIDTerra
- case "4":
- return vaa.ChainIDBSC
- case "5":
- return vaa.ChainIDPolygon
- }
- return vaa.ChainIDUnset
- }
- func makeSummary(row bigtable.Row) *Summary {
- summary := &Summary{}
- if _, ok := row[columnFamilies[0]]; ok {
- for _, item := range row[columnFamilies[0]] {
- switch item.Column {
- case "MessagePublication:InitiatingTxID":
- summary.InitiatingTxID = string(item.Value)
- case "MessagePublication:Payload":
- summary.Payload = item.Value
- case "MessagePublication:EmitterChain":
- summary.EmitterChain = string(item.Value)
- case "MessagePublication:EmitterAddress":
- summary.EmitterAddress = string(item.Value)
- case "MessagePublication:Sequence":
- summary.Sequence = string(item.Value)
- }
- }
- } else {
- // Some rows have a QuorumState, but no MessagePublication,
- // so populate Summary values from the rowKey.
- keyParts := strings.Split(row.Key(), ":")
- chainId := chainIdStringToType(keyParts[0])
- summary.EmitterChain = chainId.String()
- summary.EmitterAddress = keyParts[1]
- seq := strings.TrimLeft(keyParts[2], "0")
- if seq == "" {
- seq = "0"
- }
- summary.Sequence = seq
- }
- if _, ok := row[columnFamilies[1]]; ok {
- item := row[columnFamilies[1]][0]
- summary.SignedVAABytes = item.Value
- summary.QuorumTime = item.Timestamp.Time().String()
- }
- return summary
- }
- func makeDetails(row bigtable.Row) *Details {
- sum := makeSummary(row)
- deets := &Details{
- EmitterChain: sum.EmitterChain,
- EmitterAddress: sum.EmitterAddress,
- Sequence: sum.Sequence,
- InitiatingTxID: sum.InitiatingTxID,
- Payload: sum.Payload,
- SignedVAABytes: sum.SignedVAABytes,
- QuorumTime: sum.QuorumTime,
- }
- if _, ok := row[columnFamilies[1]]; ok {
- item := row[columnFamilies[1]][0]
- deets.SignedVAA, _ = vaa.Unmarshal(item.Value)
- }
- return deets
- }
- var mux = newMux()
- // Entry is the cloud function entry point
- func Entry(w http.ResponseWriter, r *http.Request) {
- mux.ServeHTTP(w, r)
- }
- func newMux() *http.ServeMux {
- mux := http.NewServeMux()
- mux.HandleFunc("/totals", Totals)
- mux.HandleFunc("/recent", Recent)
- mux.HandleFunc("/transaction", Transaction)
- mux.HandleFunc("/readrow", ReadRow)
- mux.HandleFunc("/readyz", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) })
- return mux
- }
|