chainwatch: very, very basic key info view

This commit is contained in:
Łukasz Magiera 2019-11-16 20:47:06 +01:00
parent 3dd9691467
commit d235e7f20e
7 changed files with 300 additions and 18 deletions

View File

@ -66,10 +66,6 @@ var runCmd = &cli.Command{
log.Info("Remote version: %s", v.Version) log.Info("Remote version: %s", v.Version)
//http.Handle("/", http.FileServer(rice.MustFindBox("site").HTTPBox()))
fmt.Printf("Open http://%s\n", cctx.String("front"))
st, err := openStorage() st, err := openStorage()
if err != nil { if err != nil {
return err return err
@ -78,6 +74,15 @@ var runCmd = &cli.Command{
runSyncer(ctx, api, st) runSyncer(ctx, api, st)
h, err := newHandler(api, st)
if err != nil {
return err
}
http.Handle("/", h)
fmt.Printf("Open http://%s\n", cctx.String("front"))
go func() { go func() {
<-ctx.Done() <-ctx.Done()
os.Exit(0) os.Exit(0)

View File

@ -13,13 +13,18 @@
</div> </div>
<div class="Index-nodes"> <div class="Index-nodes">
<div class="Index-node"> <div class="Index-node">
X Actors; Y Miners; Z Power (A Total; B Slashed); <b>{{countCol "actors" "id"}}</b> Actors;
<b>{{countCol "miner_heads" "addr"}}</b> Miners;
<b>{{netPower "slashed_at = 0" | sizeStr}}</b> Power
(<b>{{netPower "" | sizeStr}}</b> Total;
<b>{{netPower "slashed_at > 0" | sizeStr}}</b> Slashed)
</div> </div>
<div class="Index-node"> <div class="Index-node">
U Messages; V Gas Used; Total D FIL transferred; E state changes {{count "messages"}} Messages; {{count "actors"}} state changes
</div> </div>
<div class="Index-node"> <div class="Index-node">
N Wallets; E% FIL in wallets; F% FIL in miners; M% in market; %G Other actors; %H FIL it treasury {{count "id_address_map" "id != address"}} <a href="keys.html">Keys</a>;
E% FIL in wallets; F% FIL in miners; M% in market; %G Other actors; %H FIL it treasury
</div> </div>
</div> </div>
</div> </div>

View File

@ -0,0 +1,29 @@
<!DOCTYPE html>
<html>
<head>
<title>Lotus ChainWatch</title>
<link rel="stylesheet" type="text/css" href="main.css">
</head>
<body>
{{$wallet := param "w"}}
<div class="Index">
<div class="Index-header">
<div>
<span>Lotus ChainWatch - Wallet {{$wallet}}</span>
</div>
</div>
<div class="Index-nodes">
<div class="Index-node">
Balance: {{queryNum "select balance from actors inner join main.id_address_map m on m.address = ? where actors.id = m.id order by nonce desc limit 1" $wallet }}
</div>
<div class="Index-node">
Messages:
{{ range strings "messages" "case when `to` = ? then `from` else `to` end" "`from` = ? or `to` = ?" $wallet $wallet $wallet}}
<div>to/from {{.}}</div>
{{end}}
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html>
<head>
<title>Lotus ChainWatch</title>
<link rel="stylesheet" type="text/css" href="main.css">
</head>
<body>
<div class="Index">
<div class="Index-header">
<div>
<span>Lotus ChainWatch - Wallets</span>
</div>
</div>
<div class="Index-nodes">
<div class="Index-node">
{{range strings "id_address_map" "address" "address != id"}}
<div><a href="key.html?w={{.}}">{{.}}</a></div>
{{end}}
</div>
</div>
</div>
</body>
</html>

View File

@ -12,6 +12,7 @@ body {
background: #1a1a1a; background: #1a1a1a;
color: #f0f0f0; color: #f0f0f0;
font-family: monospace; font-family: monospace;
overflow: auto;
display: grid; display: grid;
grid-template-columns: auto 80vw auto; grid-template-columns: auto 80vw auto;

View File

@ -31,13 +31,13 @@ func (st *storage) setup() error {
return err return err
} }
_, err = tx.Exec(` _, err = tx.Exec(`
create table actors create table if not exists actors
( (
id text not null, id text not null,
code text not null, code text not null,
head text not null, head text not null,
nonce int not null, nonce int not null,
balance text, balance text not null,
stateroot text stateroot text
constraint actors_blocks_stateroot_fk constraint actors_blocks_stateroot_fk
references blocks (parentStateRoot), references blocks (parentStateRoot),
@ -45,7 +45,10 @@ create table actors
primary key (id, nonce, balance, stateroot) primary key (id, nonce, balance, stateroot)
); );
create table id_address_map create index if not exists actors_id_index
on actors (id);
create table if not exists id_address_map
( (
id text not null id text not null
constraint id_address_map_actors_id_fk constraint id_address_map_actors_id_fk
@ -55,7 +58,13 @@ create table id_address_map
primary key (id, address) primary key (id, address)
); );
create table messages create index if not exists id_address_map_address_index
on id_address_map (address);
create index if not exists id_address_map_id_index
on id_address_map (id);
create table if not exists messages
( (
cid text not null cid text not null
constraint messages_pk constraint messages_pk
@ -74,10 +83,10 @@ create table messages
params blob params blob
); );
create unique index messages_cid_uindex create unique index if not exists messages_cid_uindex
on messages (cid); on messages (cid);
create table blocks create table if not exists blocks
( (
cid text not null cid text not null
constraint blocks_pk constraint blocks_pk
@ -91,10 +100,10 @@ create table blocks
timestamp int not null timestamp int not null
); );
create unique index blocks_cid_uindex create unique index if not exists blocks_cid_uindex
on blocks (cid); on blocks (cid);
create table block_messages create table if not exists block_messages
( (
block text not null block text not null
constraint block_messages_blk_fk constraint block_messages_blk_fk
@ -106,7 +115,7 @@ create table block_messages
primary key (block, message) primary key (block, message)
); );
create table miner_heads create table if not exists miner_heads
( (
head text not null head text not null
constraint miner_heads_actors_head_fk constraint miner_heads_actors_head_fk
@ -127,8 +136,10 @@ create table miner_heads
active int, active int,
ppe int not null, ppe int not null,
slashed_at int not null, slashed_at int not null,
constraint miner_heads_id_address_map_address_address_fk constraint miner_heads_id_address_map_owner_fk
foreign key (owner, worker) references id_address_map (address, address), foreign key (owner) references id_address_map (address),
constraint miner_heads_id_address_map_worker_fk
foreign key (worker) references id_address_map (address),
constraint miner_heads_pk constraint miner_heads_pk
primary key (head, addr) primary key (head, addr)
); );

View File

@ -0,0 +1,208 @@
package main
import (
"fmt"
rice "github.com/GeertJohan/go.rice"
"github.com/filecoin-project/lotus/chain/types"
"golang.org/x/xerrors"
"html/template"
"net/http"
"os"
"path/filepath"
"github.com/filecoin-project/lotus/api"
)
type handler struct {
api api.FullNode
st *storage
site *rice.Box
assets http.Handler
templates map[string]*template.Template
}
func newHandler(api api.FullNode, st *storage) (*handler, error) {
h := &handler{
api: api,
st: st,
site: rice.MustFindBox("site"),
templates: map[string]*template.Template{},
}
h.assets = http.FileServer(h.site.HTTPBox())
funcs := template.FuncMap{
"count": h.count,
"countCol": h.countCol,
"sum": h.sum,
"netPower": h.netPower,
"queryNum": h.queryNum,
"sizeStr": sizeStr,
"strings": h.strings,
"param": func(string) string { return "" }, // replaced in request handler
}
base := template.New("")
base.Funcs(funcs)
return h, h.site.Walk(".", func(path string, info os.FileInfo, err error) error {
if filepath.Ext(path) != ".html" {
return nil
}
if err != nil {
return err
}
log.Info(path)
h.templates["/"+path], err = base.New(path).Parse(h.site.MustString(path))
return err
})
}
func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
h, err := newHandler(h.api, h.st) // for faster dev
if err != nil {
log.Error(err)
return
}
p := r.URL.Path
if p == "/" {
p = "/index.html"
}
t, ok := h.templates[p]
if !ok {
h.assets.ServeHTTP(w, r)
return
}
t, err = t.Clone()
if err != nil {
log.Error(err)
return
}
t.Funcs(map[string]interface{}{
"param": r.FormValue,
})
if err := t.Execute(w, nil); err != nil {
log.Error(err)
return
}
log.Info(r.URL.Path)
}
// Template funcs
func (h *handler) count(table string, filters ...string) (int, error) {
// explicitly not caring about sql injection too much, this doesn't take user input
filts := ""
if len(filters) > 0 {
filts = " where "
for _, filter := range filters {
filts += filter + " and "
}
filts = filts[:len(filts)-5]
}
var c int
err := h.st.db.QueryRow("select count(1) from " + table + filts).Scan(&c)
if err != nil {
return 0, err
}
return c, nil
}
func (h *handler) countCol(table string, col string, filters ...string) (int, error) {
// explicitly not caring about sql injection too much, this doesn't take user input
filts := ""
if len(filters) > 0 {
filts = " where "
for _, filter := range filters {
filts += filter + " and "
}
filts = filts[:len(filts)-5]
}
var c int
err := h.st.db.QueryRow("select count(distinct " + col + ") from " + table + filts).Scan(&c)
if err != nil {
return 0, err
}
return c, nil
}
func (h *handler) sum(table string, col string) (types.BigInt, error) {
return h.queryNum("select sum(cast(" + col + " as bigint)) from " + table)
}
func (h *handler) netPower(slashFilt string) (types.BigInt, error) {
if slashFilt != "" {
slashFilt = " where " + slashFilt
}
return h.queryNum(`select sum(power) from (
select miner_heads.power, miner_heads.slashed_at, max(height) from miner_heads
inner join blocks b on miner_heads.stateroot = b.parentStateRoot
group by miner_heads.addr)` + slashFilt)
}
func (h *handler) queryNum(q string, p ...interface{}) (types.BigInt, error) {
// explicitly not caring about sql injection too much, this doesn't take user input
var c string
err := h.st.db.QueryRow(q, p...).Scan(&c)
if err != nil {
log.Error("qnum ", q, p, err)
return types.NewInt(0), err
}
i := types.NewInt(0)
_, ok := i.SetString(c, 10)
if !ok {
return types.NewInt(0), xerrors.New("num parse error: " + c)
}
return i, nil
}
var units = []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB"}
func sizeStr(size types.BigInt) string {
size = types.BigMul(size, types.NewInt(100))
i := 0
for types.BigCmp(size, types.NewInt(102400)) >= 0 && i < len(units)-1 {
size = types.BigDiv(size, types.NewInt(1024))
i++
}
return fmt.Sprintf("%s.%s %s", types.BigDiv(size, types.NewInt(100)), types.BigMod(size, types.NewInt(100)), units[i])
}
func (h *handler) strings(table string, col string, filter string, args ...interface{}) (out []string, err error) {
if len(filter) > 0 {
filter = " where " + filter
}
log.Info("strings qstr ", "select "+col+" from "+table+filter)
rws, err := h.st.db.Query("select "+col+" from "+table+filter, args...)
if err != nil {
return nil, err
}
for rws.Next() {
var r string
if err := rws.Scan(&r); err != nil {
return nil, err
}
out = append(out, r)
}
return
}
var _ http.Handler = &handler{}