Skip to content

Commit 24287bd

Browse files
author
Peter Pratscher
committed
Add account tx and snark job views
Add search Add charts
1 parent 412023c commit 24287bd

File tree

14 files changed

+642
-58
lines changed

14 files changed

+642
-58
lines changed

Makefile

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,17 @@ BUILDDATE=`date -u +"%Y-%m-%dT%H:%M:%S%:z"`
55
PACKAGE=eth2-exporter
66
LDFLAGS="-X ${PACKAGE}/version.Version=${VERSION} -X ${PACKAGE}/version.BuildDate=${BUILDDATE} -X ${PACKAGE}/version.GitCommit=${GITCOMMIT} -X ${PACKAGE}/version.GitDate=${GITDATE}"
77
8-
all: explorer frontend
8+
all: explorer frontend statistics
99
1010
lint:
1111
golint ./...
1212
1313
explorer:
1414
go build --ldflags=${LDFLAGS} -o bin/indexer cmd/indexer/main.go
1515
16+
statistics:
17+
go build --ldflags=${LDFLAGS} -o bin/statistics cmd/statistics/main.go
18+
1619
frontend:
1720
rm -rf bin/templates
1821
rm -rf bin/static

cmd/frontend/main.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,13 @@ func main() {
8080
router.HandleFunc("/blocks/data", handlers.BlocksData).Methods("GET")
8181
router.HandleFunc("/account/{pk}", handlers.Account).Methods("GET")
8282
router.HandleFunc("/account/{pk}/data_blocks", handlers.AccountBlocksData).Methods("GET")
83+
router.HandleFunc("/account/{pk}/data_txs", handlers.AccountTxData).Methods("GET")
84+
router.HandleFunc("/account/{pk}/data_snarkjobs", handlers.AccountSnarkJobsData).Methods("GET")
8385
router.HandleFunc("/accounts", handlers.Accounts).Methods("GET")
8486
router.HandleFunc("/accounts/data", handlers.AccountsData).Methods("GET")
87+
router.HandleFunc("/charts", handlers.Charts).Methods("GET")
8588
router.HandleFunc("/status", handlers.Status).Methods("GET")
89+
router.HandleFunc("/search", handlers.Search).Methods("POST")
8690

8791
router.PathPrefix("/").Handler(http.FileServer(http.Dir("static")))
8892

cmd/statistics/main.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
* Copyright 2020 bitfly gmbh
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package main
18+
19+
import (
20+
"coda-explorer/db"
21+
"flag"
22+
"fmt"
23+
"github.com/jmoiron/sqlx"
24+
"github.com/sirupsen/logrus"
25+
"log"
26+
"time"
27+
)
28+
29+
var logger = logrus.New().WithField("module", "main")
30+
31+
// Helper application to re-generate all statistics
32+
func main() {
33+
dbHost := flag.String("dbHost", "", "Database host")
34+
dbPort := flag.String("dbPort", "", "Database port")
35+
dbUser := flag.String("dbUser", "", "Database user")
36+
dbPassword := flag.String("dbPassword", "", "Database password")
37+
dbName := flag.String("dbName", "", "Database name")
38+
39+
flag.Parse()
40+
41+
dbConn, err := sqlx.Open("postgres", fmt.Sprintf("postgres://%s:%s@%s:%s/%s?sslmode=disable", *dbUser, *dbPassword, *dbHost, *dbPort, *dbName))
42+
if err != nil {
43+
logger.Fatal(err)
44+
}
45+
// The golang postgres sql driver does not properly implement PingContext
46+
// therefore we use a timer to catch db connection timeouts
47+
dbConnectionTimeout := time.NewTimer(15 * time.Second)
48+
go func() {
49+
<-dbConnectionTimeout.C
50+
log.Fatal("Timeout while connecting to the database")
51+
}()
52+
err = dbConn.Ping()
53+
if err != nil {
54+
logger.Fatal(err)
55+
}
56+
dbConnectionTimeout.Stop()
57+
58+
logger.Info("database connection established")
59+
60+
db.DB = dbConn
61+
defer db.DB.Close()
62+
63+
var startTime time.Time
64+
err = db.DB.Get(&startTime, "SELECT MIN(ts) FROM blocks")
65+
if err != nil {
66+
logger.Fatalf("error retrieving start time from blocks table: %v", err)
67+
}
68+
69+
currTime := startTime.Truncate(time.Hour * 24)
70+
endTime := time.Now().Truncate(time.Hour * 24)
71+
for currTime.Before(endTime) {
72+
logger.Infof("exporting statistics for day %v", currTime)
73+
err := db.GenerateAndSaveStatistics(currTime)
74+
if err != nil {
75+
logger.Fatalf("error generating statistics for day %v: %v", currTime, err)
76+
}
77+
currTime = currTime.Add(time.Hour * 24)
78+
}
79+
logger.Infof("regenerated all statistics")
80+
}

handlers/account.go

Lines changed: 168 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ func Account(w http.ResponseWriter, r *http.Request) {
6868
}
6969
}
7070

71-
// BlocksData will return information about blocks
71+
// AccountBlocksData will return information about blocks mined by an account
7272
func AccountBlocksData(w http.ResponseWriter, r *http.Request) {
7373
w.Header().Set("Content-Type", "application/json")
7474

@@ -101,15 +101,12 @@ func AccountBlocksData(w http.ResponseWriter, r *http.Request) {
101101

102102
var blocksCount int64
103103

104-
err = db.DB.Get(&blocksCount, "SELECT blocksproposed FROM accounts WHERE publickey = $1", pk)
104+
err = db.DB.Get(&blocksCount, "SELECT least(blocksproposed, 10000) FROM accounts WHERE publickey = $1", pk)
105105
if err != nil {
106106
logger.Errorf("error retrieving blockproposed for account %v: %v", pk, err)
107107
http.Error(w, "Internal server error", 503)
108108
return
109109
}
110-
if blocksCount > 10000 {
111-
blocksCount = 10000
112-
}
113110

114111
var blocks []*types.Block
115112

@@ -154,3 +151,169 @@ func AccountBlocksData(w http.ResponseWriter, r *http.Request) {
154151
return
155152
}
156153
}
154+
155+
// AccountBlocksData will return information about tx sent and received by an account
156+
func AccountTxData(w http.ResponseWriter, r *http.Request) {
157+
w.Header().Set("Content-Type", "application/json")
158+
159+
q := r.URL.Query()
160+
161+
draw, err := strconv.ParseInt(q.Get("draw"), 10, 64)
162+
if err != nil {
163+
logger.Errorf("error converting datatables data parameter from string to int: %v", err)
164+
http.Error(w, "Internal server error", 503)
165+
return
166+
}
167+
start, err := strconv.ParseInt(q.Get("start"), 10, 64)
168+
if err != nil {
169+
logger.Errorf("error converting datatables start parameter from string to int: %v", err)
170+
http.Error(w, "Internal server error", 503)
171+
return
172+
}
173+
length, err := strconv.ParseInt(q.Get("length"), 10, 64)
174+
if err != nil {
175+
logger.Errorf("error converting datatables length parameter from string to int: %v", err)
176+
http.Error(w, "Internal server error", 503)
177+
return
178+
}
179+
if length > 100 {
180+
length = 100
181+
}
182+
183+
vars := mux.Vars(r)
184+
pk := vars["pk"]
185+
186+
var txCount int64
187+
188+
err = db.DB.Get(&txCount, "SELECT least(count(*), 10000) FROM accounttransactions WHERE publickey = $1 AND canonical", pk)
189+
if err != nil {
190+
logger.Errorf("error retrieving tx count for account %v: %v", pk, err)
191+
http.Error(w, "Internal server error", 503)
192+
return
193+
}
194+
195+
var txs []*types.TxPageData
196+
197+
err = db.DB.Select(&txs, `SELECT userjobs.*, blocks.height, blocks.slot, blocks.epoch, blocks.ts
198+
FROM accounttransactions
199+
LEFT JOIN userjobs ON accounttransactions.blockstatehash = userjobs.blockstatehash AND accounttransactions.id = userjobs.id
200+
LEFT JOIN blocks ON accounttransactions.blockstatehash = blocks.statehash
201+
WHERE accounttransactions.publickey = $1 AND accounttransactions.canonical
202+
ORDER BY ts DESC LIMIT $2 OFFSET $3`, pk, length, start)
203+
204+
if err != nil {
205+
logger.Errorf("error retrieving tx data for account %v: %v", pk, err)
206+
http.Error(w, "Internal server error", 503)
207+
return
208+
}
209+
210+
tableData := make([][]interface{}, len(txs))
211+
for i, tx := range txs {
212+
tableData[i] = []interface{}{
213+
tx.ID,
214+
tx.Ts.Unix(),
215+
tx.Height,
216+
tx.Sender,
217+
tx.Recipient,
218+
tx.Amount,
219+
tx.Fee,
220+
tx.Delegation,
221+
tx.BlockStateHash,
222+
}
223+
}
224+
225+
data := &types.DataTableResponse{
226+
Draw: draw,
227+
RecordsTotal: txCount,
228+
RecordsFiltered: txCount,
229+
Data: tableData,
230+
}
231+
232+
err = json.NewEncoder(w).Encode(data)
233+
if err != nil {
234+
logger.Errorf("error enconding json response for %v route: %v", r.URL.String(), err)
235+
http.Error(w, "Internal server error", 503)
236+
return
237+
}
238+
}
239+
240+
// AccountBlocksData will return information about blocks mined by an account
241+
func AccountSnarkJobsData(w http.ResponseWriter, r *http.Request) {
242+
w.Header().Set("Content-Type", "application/json")
243+
244+
q := r.URL.Query()
245+
246+
draw, err := strconv.ParseInt(q.Get("draw"), 10, 64)
247+
if err != nil {
248+
logger.Errorf("error converting datatables data parameter from string to int: %v", err)
249+
http.Error(w, "Internal server error", 503)
250+
return
251+
}
252+
start, err := strconv.ParseInt(q.Get("start"), 10, 64)
253+
if err != nil {
254+
logger.Errorf("error converting datatables start parameter from string to int: %v", err)
255+
http.Error(w, "Internal server error", 503)
256+
return
257+
}
258+
length, err := strconv.ParseInt(q.Get("length"), 10, 64)
259+
if err != nil {
260+
logger.Errorf("error converting datatables length parameter from string to int: %v", err)
261+
http.Error(w, "Internal server error", 503)
262+
return
263+
}
264+
if length > 100 {
265+
length = 100
266+
}
267+
268+
vars := mux.Vars(r)
269+
pk := vars["pk"]
270+
271+
var blocksCount int64
272+
273+
err = db.DB.Get(&blocksCount, "SELECT least(snarkjobs, 10000) FROM accounts WHERE publickey = $1", pk)
274+
if err != nil {
275+
logger.Errorf("error retrieving snarkjobs for account %v: %v", pk, err)
276+
http.Error(w, "Internal server error", 503)
277+
return
278+
}
279+
280+
var snarkJobs []*types.SnarkJobPageData
281+
282+
err = db.DB.Select(&snarkJobs, `SELECT snarkjobs.*, blocks.height, blocks.slot, blocks.epoch, blocks.ts
283+
FROM snarkjobs
284+
LEFT JOIN blocks On snarkjobs.blockstatehash = blocks.statehash
285+
WHERE prover = $1 AND snarkjobs.canonical
286+
ORDER BY blocks.height DESC LIMIT $2 OFFSET $3`, pk, length, start)
287+
288+
if err != nil {
289+
logger.Errorf("error retrieving snark job data for account %v: %v", pk, err)
290+
http.Error(w, "Internal server error", 503)
291+
return
292+
}
293+
294+
tableData := make([][]interface{}, len(snarkJobs))
295+
for i, sj := range snarkJobs {
296+
tableData[i] = []interface{}{
297+
sj.Jobids,
298+
sj.Prover,
299+
sj.Fee,
300+
sj.Ts.Unix(),
301+
sj.Height,
302+
sj.BlockStateHash,
303+
}
304+
}
305+
306+
data := &types.DataTableResponse{
307+
Draw: draw,
308+
RecordsTotal: blocksCount,
309+
RecordsFiltered: blocksCount,
310+
Data: tableData,
311+
}
312+
313+
err = json.NewEncoder(w).Encode(data)
314+
if err != nil {
315+
logger.Errorf("error enconding json response for %v route: %v", r.URL.String(), err)
316+
http.Error(w, "Internal server error", 503)
317+
return
318+
}
319+
}

handlers/charts.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright 2020 bitfly gmbh
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package handlers
18+
19+
import (
20+
"coda-explorer/db"
21+
"coda-explorer/templates"
22+
"coda-explorer/types"
23+
"coda-explorer/version"
24+
"html/template"
25+
"net/http"
26+
)
27+
28+
// ChartBlocks will return information about the daily produced blocks using a go template
29+
var chartsTemplate = template.Must(template.New("blocks").Funcs(templates.GetTemplateFuncs()).ParseFiles("templates/layout.html", "templates/charts.html"))
30+
31+
func Charts(w http.ResponseWriter, r *http.Request) {
32+
33+
w.Header().Set("Content-Type", "text/html")
34+
35+
var stats []*types.Statistic
36+
err := db.DB.Select(&stats, "SELECT * FROM statistics WHERE value > 0 ORDER BY ts, indicator")
37+
if err != nil {
38+
logger.Errorf("error retrieving statistcs data for route %v: %v", r.URL.String(), err)
39+
http.Error(w, "Internal server error", 503)
40+
return
41+
}
42+
43+
data := &types.PageData{
44+
Meta: &types.Meta{
45+
Title: "coda explorer",
46+
Description: "",
47+
Path: "",
48+
},
49+
ShowSyncingMessage: false,
50+
Active: "charts",
51+
Data: stats,
52+
Version: version.Version,
53+
}
54+
55+
err = chartsTemplate.ExecuteTemplate(w, "layout", data)
56+
57+
if err != nil {
58+
logger.Errorf("error executing template for %v route: %v", r.URL.String(), err)
59+
http.Error(w, "Internal server error", 503)
60+
return
61+
}
62+
}

0 commit comments

Comments
 (0)