162 lines
5.0 KiB
Go
162 lines
5.0 KiB
Go
// Package p contains an HTTP Cloud Function.
|
|
package p
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"log"
|
|
"net/http"
|
|
"sync"
|
|
"time"
|
|
|
|
"cloud.google.com/go/bigtable"
|
|
)
|
|
|
|
type transfersFromResult struct {
|
|
Daily map[string]map[string]float64
|
|
Total float64
|
|
}
|
|
|
|
// an in-memory cache of previously calculated results
|
|
var transfersFromCache transfersFromResult
|
|
var muTransfersFromCache sync.RWMutex
|
|
var transfersFromFilePath = "notional-transferred-from.json"
|
|
|
|
// finds the daily amount transferred from each chain from the specified start to the present.
|
|
func createTransfersFromOfInterval(tbl *bigtable.Table, ctx context.Context, prefix string, start time.Time) {
|
|
if len(transfersFromCache.Daily) == 0 {
|
|
loadJsonToInterface(ctx, transfersFromFilePath, &muTransfersFromCache, &transfersFromCache)
|
|
}
|
|
|
|
now := time.Now().UTC()
|
|
numPrevDays := int(now.Sub(start).Hours() / 24)
|
|
|
|
var intervalsWG sync.WaitGroup
|
|
// there will be a query for each previous day, plus today
|
|
intervalsWG.Add(numPrevDays + 1)
|
|
|
|
for daysAgo := 0; daysAgo <= numPrevDays; daysAgo++ {
|
|
go func(tbl *bigtable.Table, ctx context.Context, prefix string, daysAgo int) {
|
|
defer intervalsWG.Done()
|
|
// start is the SOD, end is EOD
|
|
// "0 daysAgo start" is 00:00:00 AM of the current day
|
|
// "0 daysAgo end" is 23:59:59 of the current day (the future)
|
|
|
|
// calculate the start and end times for the query
|
|
hoursAgo := (24 * daysAgo)
|
|
daysAgoDuration := -time.Duration(hoursAgo) * time.Hour
|
|
n := now.Add(daysAgoDuration)
|
|
year := n.Year()
|
|
month := n.Month()
|
|
day := n.Day()
|
|
loc := n.Location()
|
|
|
|
start := time.Date(year, month, day, 0, 0, 0, 0, loc)
|
|
end := time.Date(year, month, day, 23, 59, 59, maxNano, loc)
|
|
|
|
dateStr := start.Format("2006-01-02")
|
|
|
|
muTransfersFromCache.Lock()
|
|
// check to see if there is cache data for this date/query
|
|
if _, ok := transfersFromCache.Daily[dateStr]; ok && useCache(dateStr) {
|
|
// have a cache for this date
|
|
if daysAgo >= 1 {
|
|
// only use the cache for yesterday and older
|
|
muTransfersFromCache.Unlock()
|
|
return
|
|
}
|
|
}
|
|
// no cache for this query, initialize the map
|
|
transfersFromCache.Daily[dateStr] = map[string]float64{"*": 0}
|
|
muTransfersFromCache.Unlock()
|
|
|
|
queryResult := fetchTransferRowsInInterval(tbl, ctx, prefix, start, end)
|
|
|
|
// iterate through the rows and increment the amounts
|
|
for _, row := range queryResult {
|
|
if _, ok := tokensToSkip[row.TokenAddress]; ok {
|
|
// skip blacklisted token
|
|
continue
|
|
}
|
|
if _, ok := transfersFromCache.Daily[dateStr][row.LeavingChain]; !ok {
|
|
transfersFromCache.Daily[dateStr][row.LeavingChain] = 0
|
|
}
|
|
transfersFromCache.Daily[dateStr]["*"] = transfersFromCache.Daily[dateStr]["*"] + row.Notional
|
|
transfersFromCache.Daily[dateStr][row.LeavingChain] = transfersFromCache.Daily[dateStr][row.LeavingChain] + row.Notional
|
|
}
|
|
}(tbl, ctx, prefix, daysAgo)
|
|
}
|
|
intervalsWG.Wait()
|
|
|
|
// having consistent keys in each object is helpful for clients, explorer GUI
|
|
transfersFromCache.Total = 0
|
|
seenChainSet := map[string]bool{}
|
|
for _, chains := range transfersFromCache.Daily {
|
|
for chain, amount := range chains {
|
|
seenChainSet[chain] = true
|
|
if chain == "*" {
|
|
transfersFromCache.Total += amount
|
|
}
|
|
}
|
|
}
|
|
for date, chains := range transfersFromCache.Daily {
|
|
for chain := range seenChainSet {
|
|
if _, ok := chains[chain]; !ok {
|
|
transfersFromCache.Daily[date][chain] = 0
|
|
}
|
|
}
|
|
}
|
|
|
|
persistInterfaceToJson(ctx, transfersFromFilePath, &muTransfersFromCache, transfersFromCache)
|
|
}
|
|
|
|
// finds the value that has been transferred from each chain
|
|
func ComputeNotionalTransferredFrom(w http.ResponseWriter, r *http.Request) {
|
|
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", "POST")
|
|
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
|
|
w.Header().Set("Access-Control-Max-Age", "3600")
|
|
w.WriteHeader(http.StatusNoContent)
|
|
return
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
|
defer cancel()
|
|
|
|
createTransfersFromOfInterval(tbl, ctx, "", releaseDay)
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
}
|
|
|
|
func NotionalTransferredFrom(w http.ResponseWriter, r *http.Request) {
|
|
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", "POST")
|
|
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
|
|
w.Header().Set("Access-Control-Max-Age", "3600")
|
|
w.WriteHeader(http.StatusNoContent)
|
|
return
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
|
defer cancel()
|
|
|
|
var result transfersFromResult
|
|
loadJsonToInterface(ctx, transfersFromFilePath, &muTransfersFromCache, &result)
|
|
|
|
jsonBytes, err := json.Marshal(result)
|
|
if err != nil {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
w.Write([]byte(err.Error()))
|
|
log.Println(err.Error())
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write(jsonBytes)
|
|
}
|