351 lines
7.3 KiB
Go
351 lines
7.3 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"html/template"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
|
|
rice "github.com/GeertJohan/go.rice"
|
|
"github.com/filecoin-project/go-address"
|
|
"github.com/filecoin-project/specs-actors/actors/abi"
|
|
"github.com/filecoin-project/specs-actors/actors/abi/big"
|
|
"github.com/ipfs/go-cid"
|
|
"golang.org/x/xerrors"
|
|
|
|
"github.com/filecoin-project/lotus/api"
|
|
"github.com/filecoin-project/lotus/chain/types"
|
|
)
|
|
|
|
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,
|
|
"qstr": h.qstr,
|
|
"qstrs": h.qstrs,
|
|
"messages": h.messages,
|
|
|
|
"pageDown": pageDown,
|
|
"parseInt": func(s string) (int, error) { i, e := strconv.ParseInt(s, 10, 64); return int(i), e },
|
|
"substr": func(i, j int, s string) string { return s[i:j] },
|
|
"sub": func(a, b int) int { return a - b }, // TODO: really not builtin?
|
|
|
|
"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.Errorf("%+v", 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 distinct on (addr) power, slashed_at from miner_heads
|
|
inner join blocks b on miner_heads.stateroot = b.parentStateRoot
|
|
order by addr, height desc) as p` + 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, args)
|
|
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
|
|
}
|
|
|
|
func (h *handler) qstr(q string, p ...interface{}) (string, error) {
|
|
// explicitly not caring about sql injection too much, this doesn't take user input
|
|
|
|
r, err := h.qstrs(q, 1, p...)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return r[0], nil
|
|
}
|
|
|
|
func (h *handler) qstrs(q string, n int, p ...interface{}) ([]string, error) {
|
|
// explicitly not caring about sql injection too much, this doesn't take user input
|
|
|
|
c := make([]string, n)
|
|
ia := make([]interface{}, n)
|
|
for i := range c {
|
|
ia[i] = &c[i]
|
|
}
|
|
err := h.st.db.QueryRow(q, p...).Scan(ia...)
|
|
if err != nil {
|
|
log.Error("qnum ", q, p, err)
|
|
return nil, err
|
|
}
|
|
|
|
return c, nil
|
|
}
|
|
|
|
type sbig types.BigInt
|
|
|
|
func (bi *sbig) Scan(value interface{}) error {
|
|
switch value := value.(type) {
|
|
case string:
|
|
i, ok := big.NewInt(0).SetString(value, 10)
|
|
if !ok {
|
|
if value == "<nil>" {
|
|
return nil
|
|
}
|
|
return xerrors.Errorf("failed to parse bigint string: '%s'", value)
|
|
}
|
|
|
|
bi.Int = i
|
|
|
|
return nil
|
|
case int64:
|
|
bi.Int = big.NewInt(value).Int
|
|
return nil
|
|
default:
|
|
return xerrors.Errorf("non-string types unsupported: %T", value)
|
|
}
|
|
}
|
|
|
|
type Message struct {
|
|
To address.Address
|
|
From address.Address
|
|
|
|
Nonce uint64
|
|
|
|
Value sbig
|
|
|
|
GasPrice sbig
|
|
GasLimit sbig
|
|
|
|
Method abi.MethodNum
|
|
Params []byte
|
|
}
|
|
|
|
func (h *handler) messages(filter string, args ...interface{}) (out []types.Message, err error) {
|
|
if len(filter) > 0 {
|
|
filter = " where " + filter
|
|
}
|
|
|
|
log.Info("select * from messages " + filter)
|
|
|
|
rws, err := h.st.db.Query("select * from messages "+filter, args...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for rws.Next() {
|
|
var r Message
|
|
var cs string
|
|
|
|
if err := rws.Scan(
|
|
&cs,
|
|
&r.From,
|
|
&r.To,
|
|
&r.Nonce,
|
|
&r.Value,
|
|
&r.GasPrice,
|
|
&r.GasLimit,
|
|
&r.Method,
|
|
&r.Params,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
c, err := cid.Parse(cs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
tr := types.Message{
|
|
To: r.To,
|
|
From: r.From,
|
|
Nonce: r.Nonce,
|
|
Value: types.BigInt(r.Value),
|
|
GasPrice: types.BigInt(r.GasPrice),
|
|
GasLimit: types.BigInt(r.GasLimit),
|
|
Method: r.Method,
|
|
Params: r.Params,
|
|
}
|
|
if c != tr.Cid() {
|
|
log.Warn("msg cid doesn't match")
|
|
}
|
|
|
|
out = append(out, tr)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func pageDown(base, n int) []int {
|
|
out := make([]int, n)
|
|
for i := range out {
|
|
out[i] = base - i
|
|
}
|
|
|
|
return out
|
|
}
|
|
|
|
var _ http.Handler = &handler{}
|