shared.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  1. package p
  2. import (
  3. "context"
  4. "log"
  5. "net/http"
  6. "os"
  7. "strings"
  8. "sync"
  9. "cloud.google.com/go/bigtable"
  10. "cloud.google.com/go/pubsub"
  11. "github.com/certusone/wormhole/node/pkg/vaa"
  12. )
  13. // shared code for the various functions, primarily response formatting.
  14. // client is a global Bigtable client, to avoid initializing a new client for
  15. // every request.
  16. var client *bigtable.Client
  17. var clientOnce sync.Once
  18. var tbl *bigtable.Table
  19. var pubsubClient *pubsub.Client
  20. var pubSubTokenTransferDetailsTopic *pubsub.Topic
  21. var coinGeckoCoins = map[string][]CoinGeckoCoin{}
  22. var solanaTokens = map[string]SolanaToken{}
  23. // init runs during cloud function initialization. So, this will only run during an
  24. // an instance's cold start.
  25. // https://cloud.google.com/functions/docs/bestpractices/networking#accessing_google_apis
  26. func init() {
  27. clientOnce.Do(func() {
  28. // Declare a separate err variable to avoid shadowing client.
  29. var err error
  30. project := os.Getenv("GCP_PROJECT")
  31. instance := os.Getenv("BIGTABLE_INSTANCE")
  32. client, err = bigtable.NewClient(context.Background(), project, instance)
  33. if err != nil {
  34. // http.Error(w, "Error initializing client", http.StatusInternalServerError)
  35. log.Printf("bigtable.NewClient error: %v", err)
  36. return
  37. }
  38. var pubsubErr error
  39. pubsubClient, pubsubErr = pubsub.NewClient(context.Background(), project)
  40. if pubsubErr != nil {
  41. log.Printf("pubsub.NewClient error: %v", pubsubErr)
  42. return
  43. }
  44. })
  45. tbl = client.Open("v2Events")
  46. // create the topic that will be published to after decoding token transfer payloads
  47. tokenTransferDetailsTopic := os.Getenv("PUBSUB_TOKEN_TRANSFER_DETAILS_TOPIC")
  48. if tokenTransferDetailsTopic != "" {
  49. pubSubTokenTransferDetailsTopic = pubsubClient.Topic(tokenTransferDetailsTopic)
  50. // fetch the token lists once at start up
  51. coinGeckoCoins = fetchCoinGeckoCoins()
  52. solanaTokens = fetchSolanaTokenList()
  53. }
  54. }
  55. var columnFamilies = []string{
  56. "MessagePublication",
  57. "QuorumState",
  58. "TokenTransferPayload",
  59. "AssetMetaPayload",
  60. "NFTTransferPayload",
  61. "TokenTransferDetails",
  62. "ChainDetails",
  63. }
  64. var messagePubFam = columnFamilies[0]
  65. var quorumStateFam = columnFamilies[1]
  66. var transferPayloadFam = columnFamilies[2]
  67. var metaPayloadFam = columnFamilies[3]
  68. var nftPayloadFam = columnFamilies[4]
  69. var transferDetailsFam = columnFamilies[5]
  70. var chainDetailsFam = columnFamilies[6]
  71. type (
  72. // Summary is MessagePublication data & QuorumState data
  73. Summary struct {
  74. EmitterChain string
  75. EmitterAddress string
  76. Sequence string
  77. InitiatingTxID string
  78. Payload []byte
  79. SignedVAABytes []byte
  80. QuorumTime string
  81. }
  82. // Details is a Summary extended with all the post-processing ColumnFamilies
  83. Details struct {
  84. Summary
  85. SignedVAA *vaa.VAA
  86. TokenTransferPayload *TokenTransferPayload
  87. AssetMetaPayload *AssetMetaPayload
  88. NFTTransferPayload *NFTTransferPayload
  89. TransferDetails *TransferDetails
  90. ChainDetails *ChainDetails
  91. }
  92. // The following structs match the ColumnFamiles they are named after
  93. TokenTransferPayload struct {
  94. Amount string
  95. OriginAddress string
  96. OriginChain string
  97. TargetAddress string
  98. TargetChain string
  99. }
  100. AssetMetaPayload struct {
  101. TokenAddress string
  102. TokenChain string
  103. Decimals string
  104. Symbol string
  105. Name string
  106. }
  107. NFTTransferPayload struct {
  108. OriginAddress string
  109. OriginChain string
  110. Symbol string
  111. Name string
  112. TokenId string
  113. URI string
  114. TargetAddress string
  115. TargetChain string
  116. }
  117. TransferDetails struct {
  118. Amount string
  119. Decimals string
  120. NotionalUSDStr string
  121. TokenPriceUSDStr string
  122. TransferTimestamp string
  123. OriginSymbol string
  124. OriginName string
  125. OriginTokenAddress string
  126. // fields below exist on the row, but no need to return them currently.
  127. // NotionalUSD uint64
  128. // TokenPriceUSD uint64
  129. }
  130. ChainDetails struct {
  131. SenderAddress string
  132. ReceiverAddress string
  133. }
  134. )
  135. func chainIdStringToType(chainId string) vaa.ChainID {
  136. switch chainId {
  137. case "1":
  138. return vaa.ChainIDSolana
  139. case "2":
  140. return vaa.ChainIDEthereum
  141. case "3":
  142. return vaa.ChainIDTerra
  143. case "4":
  144. return vaa.ChainIDBSC
  145. case "5":
  146. return vaa.ChainIDPolygon
  147. }
  148. return vaa.ChainIDUnset
  149. }
  150. func makeSummary(row bigtable.Row) *Summary {
  151. summary := &Summary{}
  152. if _, ok := row[messagePubFam]; ok {
  153. for _, item := range row[messagePubFam] {
  154. switch item.Column {
  155. case "MessagePublication:InitiatingTxID":
  156. summary.InitiatingTxID = string(item.Value)
  157. case "MessagePublication:Payload":
  158. summary.Payload = item.Value
  159. case "MessagePublication:EmitterChain":
  160. summary.EmitterChain = string(item.Value)
  161. case "MessagePublication:EmitterAddress":
  162. summary.EmitterAddress = string(item.Value)
  163. case "MessagePublication:Sequence":
  164. summary.Sequence = string(item.Value)
  165. }
  166. }
  167. } else {
  168. // Some rows have a QuorumState, but no MessagePublication,
  169. // so populate Summary values from the rowKey.
  170. keyParts := strings.Split(row.Key(), ":")
  171. chainId := chainIdStringToType(keyParts[0])
  172. summary.EmitterChain = chainId.String()
  173. summary.EmitterAddress = keyParts[1]
  174. seq := strings.TrimLeft(keyParts[2], "0")
  175. if seq == "" {
  176. seq = "0"
  177. }
  178. summary.Sequence = seq
  179. }
  180. if _, ok := row[quorumStateFam]; ok {
  181. item := row[quorumStateFam][0]
  182. summary.SignedVAABytes = item.Value
  183. summary.QuorumTime = item.Timestamp.Time().String()
  184. }
  185. return summary
  186. }
  187. func makeDetails(row bigtable.Row) *Details {
  188. deets := &Details{}
  189. sum := makeSummary(row)
  190. deets.Summary = Summary{
  191. EmitterChain: sum.EmitterChain,
  192. EmitterAddress: sum.EmitterAddress,
  193. Sequence: sum.Sequence,
  194. InitiatingTxID: sum.InitiatingTxID,
  195. Payload: sum.Payload,
  196. SignedVAABytes: sum.SignedVAABytes,
  197. QuorumTime: sum.QuorumTime,
  198. }
  199. if _, ok := row[quorumStateFam]; ok {
  200. item := row[quorumStateFam][0]
  201. deets.SignedVAA, _ = vaa.Unmarshal(item.Value)
  202. }
  203. if _, ok := row[transferPayloadFam]; ok {
  204. tokenTransferPayload := &TokenTransferPayload{}
  205. for _, item := range row[transferPayloadFam] {
  206. switch item.Column {
  207. case "TokenTransferPayload:Amount":
  208. tokenTransferPayload.Amount = string(item.Value)
  209. case "TokenTransferPayload:OriginAddress":
  210. tokenTransferPayload.OriginAddress = string(item.Value)
  211. case "TokenTransferPayload:OriginChain":
  212. tokenTransferPayload.OriginChain = string(item.Value)
  213. case "TokenTransferPayload:TargetAddress":
  214. tokenTransferPayload.TargetAddress = string(item.Value)
  215. case "TokenTransferPayload:TargetChain":
  216. tokenTransferPayload.TargetChain = string(item.Value)
  217. }
  218. }
  219. deets.TokenTransferPayload = tokenTransferPayload
  220. }
  221. if _, ok := row[metaPayloadFam]; ok {
  222. assetMetaPayload := &AssetMetaPayload{}
  223. for _, item := range row[metaPayloadFam] {
  224. switch item.Column {
  225. case "AssetMetaPayload:TokenAddress":
  226. assetMetaPayload.TokenAddress = string(item.Value)
  227. case "AssetMetaPayload:TokenChain":
  228. assetMetaPayload.TokenChain = string(item.Value)
  229. case "AssetMetaPayload:Decimals":
  230. assetMetaPayload.Decimals = string(item.Value)
  231. case "AssetMetaPayload:Symbol":
  232. assetMetaPayload.Symbol = string(item.Value)
  233. case "AssetMetaPayload:Name":
  234. assetMetaPayload.Name = string(item.Value)
  235. }
  236. }
  237. deets.AssetMetaPayload = assetMetaPayload
  238. }
  239. if _, ok := row[nftPayloadFam]; ok {
  240. nftTransferPayload := &NFTTransferPayload{}
  241. for _, item := range row[nftPayloadFam] {
  242. switch item.Column {
  243. case "NFTTransferPayload:OriginAddress":
  244. nftTransferPayload.OriginAddress = string(item.Value)
  245. case "NFTTransferPayload:OriginChain":
  246. nftTransferPayload.OriginChain = string(item.Value)
  247. case "NFTTransferPayload:Symbol":
  248. nftTransferPayload.Symbol = string(item.Value)
  249. case "NFTTransferPayload:Name":
  250. nftTransferPayload.Name = string(item.Value)
  251. case "NFTTransferPayload:TokenId":
  252. nftTransferPayload.TokenId = string(item.Value)
  253. case "NFTTransferPayload:URI":
  254. nftTransferPayload.URI = string(TrimUnicodeFromByteArray(item.Value))
  255. case "NFTTransferPayload:TargetAddress":
  256. nftTransferPayload.TargetAddress = string(item.Value)
  257. case "NFTTransferPayload:TargetChain":
  258. nftTransferPayload.TargetChain = string(item.Value)
  259. }
  260. }
  261. deets.NFTTransferPayload = nftTransferPayload
  262. }
  263. if _, ok := row[transferDetailsFam]; ok {
  264. transferDetails := &TransferDetails{}
  265. for _, item := range row[transferDetailsFam] {
  266. switch item.Column {
  267. case "TokenTransferDetails:Amount":
  268. transferDetails.Amount = string(item.Value)
  269. case "TokenTransferDetails:Decimals":
  270. transferDetails.Decimals = string(item.Value)
  271. case "TokenTransferDetails:NotionalUSDStr":
  272. transferDetails.NotionalUSDStr = string(item.Value)
  273. case "TokenTransferDetails:TokenPriceUSDStr":
  274. transferDetails.TokenPriceUSDStr = string(item.Value)
  275. case "TokenTransferDetails:TransferTimestamp":
  276. transferDetails.TransferTimestamp = string(item.Value)
  277. case "TokenTransferDetails:OriginSymbol":
  278. transferDetails.OriginSymbol = string(item.Value)
  279. case "TokenTransferDetails:OriginName":
  280. transferDetails.OriginName = string(item.Value)
  281. case "TokenTransferDetails:OriginTokenAddress":
  282. transferDetails.OriginTokenAddress = string(item.Value)
  283. // NotionalUSD and TokenPriceUSD are more percise than the string versions returned,
  284. // however the precision is not required, so leaving this commented out for now.
  285. // case "TokenTransferDetails:NotionalUSD":
  286. // reader := bytes.NewReader(item.Value)
  287. // var notionalUSD uint64
  288. // if err := binary.Read(reader, binary.BigEndian, &notionalUSD); err != nil {
  289. // log.Fatalf("failed to read NotionalUSD of row: %v. err %v ", row.Key(), err)
  290. // }
  291. // transferDetails.NotionalUSD = notionalUSD
  292. // case "TokenTransferDetails:TokenPriceUSD":
  293. // reader := bytes.NewReader(item.Value)
  294. // var tokenPriceUSD uint64
  295. // if err := binary.Read(reader, binary.BigEndian, &tokenPriceUSD); err != nil {
  296. // log.Fatalf("failed to read TokenPriceUSD of row: %v. err %v", row.Key(), err)
  297. // }
  298. // transferDetails.NotionalUSD = tokenPriceUSD
  299. }
  300. }
  301. deets.TransferDetails = transferDetails
  302. }
  303. if _, ok := row[chainDetailsFam]; ok {
  304. chainDetails := &ChainDetails{}
  305. for _, item := range row[chainDetailsFam] {
  306. switch item.Column {
  307. case "ChainDetails:SenderAddress":
  308. chainDetails.SenderAddress = string(item.Value)
  309. case "ChainDetails:ReceiverAddress":
  310. chainDetails.ReceiverAddress = string(item.Value)
  311. }
  312. }
  313. deets.ChainDetails = chainDetails
  314. }
  315. return deets
  316. }
  317. var mux = newMux()
  318. // Entry is the cloud function entry point
  319. func Entry(w http.ResponseWriter, r *http.Request) {
  320. mux.ServeHTTP(w, r)
  321. }
  322. func newMux() *http.ServeMux {
  323. mux := http.NewServeMux()
  324. mux.HandleFunc("/totals", Totals)
  325. mux.HandleFunc("/recent", Recent)
  326. mux.HandleFunc("/transaction", Transaction)
  327. mux.HandleFunc("/readrow", ReadRow)
  328. mux.HandleFunc("/findvalues", FindValues)
  329. mux.HandleFunc("/readyz", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) })
  330. return mux
  331. }