/
notional-transferred-from.go
160 lines (135 loc) · 4.96 KB
/
notional-transferred-from.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
// 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, start time.Time) {
if len(transfersFromCache.Daily) == 0 && loadCache {
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, 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
if transfersFromCache.Daily == nil {
transfersFromCache.Daily = map[string]map[string]float64{}
}
transfersFromCache.Daily[dateStr] = map[string]float64{"*": 0}
muTransfersFromCache.Unlock()
for _, chainId := range tvlChainIDs {
queryResult := fetchTransferRowsInInterval(tbl, ctx, chainIDRowPrefix(chainId), start, end)
// iterate through the rows and increment the amounts
for _, row := range queryResult {
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, 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 := context.Background()
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)
}