EthereumType: admin interface to read and update contract info
This commit is contained in:
parent
19a902360e
commit
a55c69a8a1
@ -35,6 +35,66 @@ type htmlTemplates[TD any] struct {
|
|||||||
postHtmlTemplateHandler func(data *TD, w http.ResponseWriter, r *http.Request)
|
postHtmlTemplateHandler func(data *TD, w http.ResponseWriter, r *http.Request)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *htmlTemplates[TD]) jsonHandler(handler func(r *http.Request, apiVersion int) (interface{}, error), apiVersion int) func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
type jsonError struct {
|
||||||
|
Text string `json:"error"`
|
||||||
|
HTTPStatus int `json:"-"`
|
||||||
|
}
|
||||||
|
handlerName := getFunctionName(handler)
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var data interface{}
|
||||||
|
var err error
|
||||||
|
defer func() {
|
||||||
|
if e := recover(); e != nil {
|
||||||
|
glog.Error(handlerName, " recovered from panic: ", e)
|
||||||
|
debug.PrintStack()
|
||||||
|
if s.debug {
|
||||||
|
data = jsonError{fmt.Sprint("Internal server error: recovered from panic ", e), http.StatusInternalServerError}
|
||||||
|
} else {
|
||||||
|
data = jsonError{"Internal server error", http.StatusInternalServerError}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
|
if e, isError := data.(jsonError); isError {
|
||||||
|
w.WriteHeader(e.HTTPStatus)
|
||||||
|
}
|
||||||
|
err = json.NewEncoder(w).Encode(data)
|
||||||
|
if err != nil {
|
||||||
|
glog.Warning("json encode ", err)
|
||||||
|
}
|
||||||
|
if s.metrics != nil {
|
||||||
|
s.metrics.ExplorerPendingRequests.With((common.Labels{"method": handlerName})).Dec()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if s.metrics != nil {
|
||||||
|
s.metrics.ExplorerPendingRequests.With((common.Labels{"method": handlerName})).Inc()
|
||||||
|
}
|
||||||
|
data, err = handler(r, apiVersion)
|
||||||
|
if err != nil || data == nil {
|
||||||
|
if apiErr, ok := err.(*api.APIError); ok {
|
||||||
|
if apiErr.Public {
|
||||||
|
data = jsonError{apiErr.Error(), http.StatusBadRequest}
|
||||||
|
} else {
|
||||||
|
data = jsonError{apiErr.Error(), http.StatusInternalServerError}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err != nil {
|
||||||
|
glog.Error(handlerName, " error: ", err)
|
||||||
|
}
|
||||||
|
if s.debug {
|
||||||
|
if data != nil {
|
||||||
|
data = jsonError{fmt.Sprintf("Internal server error: %v, data %+v", err, data), http.StatusInternalServerError}
|
||||||
|
} else {
|
||||||
|
data = jsonError{fmt.Sprintf("Internal server error: %v", err), http.StatusInternalServerError}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
data = jsonError{"Internal server error", http.StatusInternalServerError}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *htmlTemplates[TD]) htmlTemplateHandler(handler func(w http.ResponseWriter, r *http.Request) (tpl, *TD, error)) func(w http.ResponseWriter, r *http.Request) {
|
func (s *htmlTemplates[TD]) htmlTemplateHandler(handler func(w http.ResponseWriter, r *http.Request) (tpl, *TD, error)) func(w http.ResponseWriter, r *http.Request) {
|
||||||
handlerName := getFunctionName(handler)
|
handlerName := getFunctionName(handler)
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|||||||
@ -5,11 +5,15 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
|
"github.com/juju/errors"
|
||||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
"github.com/trezor/blockbook/api"
|
"github.com/trezor/blockbook/api"
|
||||||
"github.com/trezor/blockbook/bchain"
|
"github.com/trezor/blockbook/bchain"
|
||||||
@ -72,6 +76,8 @@ func NewInternalServer(binding, certFiles string, db *db.RocksDB, chain bchain.B
|
|||||||
serveMux.HandleFunc(path+"admin/ws-limit-exceeding-ips", s.htmlTemplateHandler(s.wsLimitExceedingIPs))
|
serveMux.HandleFunc(path+"admin/ws-limit-exceeding-ips", s.htmlTemplateHandler(s.wsLimitExceedingIPs))
|
||||||
if s.chainParser.GetChainType() == bchain.ChainEthereumType {
|
if s.chainParser.GetChainType() == bchain.ChainEthereumType {
|
||||||
serveMux.HandleFunc(path+"admin/internal-data-errors", s.htmlTemplateHandler(s.internalDataErrors))
|
serveMux.HandleFunc(path+"admin/internal-data-errors", s.htmlTemplateHandler(s.internalDataErrors))
|
||||||
|
serveMux.HandleFunc(path+"admin/contract-info", s.htmlTemplateHandler(s.contractInfoPage))
|
||||||
|
serveMux.HandleFunc(path+"admin/contract-info/", s.jsonHandler(s.apiContractInfo, 0))
|
||||||
}
|
}
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
@ -118,7 +124,8 @@ func (s *InternalServer) index(w http.ResponseWriter, r *http.Request) {
|
|||||||
const (
|
const (
|
||||||
adminIndexTpl = iota + errorInternalTpl + 1
|
adminIndexTpl = iota + errorInternalTpl + 1
|
||||||
adminInternalErrorsTpl
|
adminInternalErrorsTpl
|
||||||
adminLimitExceedingIPS
|
adminLimitExceedingIPSTpl
|
||||||
|
adminContractInfoTpl
|
||||||
|
|
||||||
internalTplCount
|
internalTplCount
|
||||||
)
|
)
|
||||||
@ -173,7 +180,8 @@ func (s *InternalServer) parseTemplates() []*template.Template {
|
|||||||
t[errorInternalTpl] = createTemplate("./static/internal_templates/error.html", "./static/internal_templates/base.html")
|
t[errorInternalTpl] = createTemplate("./static/internal_templates/error.html", "./static/internal_templates/base.html")
|
||||||
t[adminIndexTpl] = createTemplate("./static/internal_templates/index.html", "./static/internal_templates/base.html")
|
t[adminIndexTpl] = createTemplate("./static/internal_templates/index.html", "./static/internal_templates/base.html")
|
||||||
t[adminInternalErrorsTpl] = createTemplate("./static/internal_templates/block_internal_data_errors.html", "./static/internal_templates/base.html")
|
t[adminInternalErrorsTpl] = createTemplate("./static/internal_templates/block_internal_data_errors.html", "./static/internal_templates/base.html")
|
||||||
t[adminLimitExceedingIPS] = createTemplate("./static/internal_templates/ws_limit_exceeding_ips.html", "./static/internal_templates/base.html")
|
t[adminLimitExceedingIPSTpl] = createTemplate("./static/internal_templates/ws_limit_exceeding_ips.html", "./static/internal_templates/base.html")
|
||||||
|
t[adminContractInfoTpl] = createTemplate("./static/internal_templates/contract_info.html", "./static/internal_templates/base.html")
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -213,5 +221,54 @@ func (s *InternalServer) wsLimitExceedingIPs(w http.ResponseWriter, r *http.Requ
|
|||||||
})
|
})
|
||||||
data.WsLimitExceedingIPs = ips
|
data.WsLimitExceedingIPs = ips
|
||||||
data.WsGetAccountInfoLimit = s.is.WsGetAccountInfoLimit
|
data.WsGetAccountInfoLimit = s.is.WsGetAccountInfoLimit
|
||||||
return adminLimitExceedingIPS, data, nil
|
return adminLimitExceedingIPSTpl, data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InternalServer) contractInfoPage(w http.ResponseWriter, r *http.Request) (tpl, *InternalTemplateData, error) {
|
||||||
|
data := s.newTemplateData(r)
|
||||||
|
return adminContractInfoTpl, data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InternalServer) apiContractInfo(r *http.Request, apiVersion int) (interface{}, error) {
|
||||||
|
if r.Method == http.MethodPost {
|
||||||
|
return s.updateContracts(r)
|
||||||
|
}
|
||||||
|
var contractAddress string
|
||||||
|
i := strings.LastIndexByte(r.URL.Path, '/')
|
||||||
|
if i > 0 {
|
||||||
|
contractAddress = r.URL.Path[i+1:]
|
||||||
|
}
|
||||||
|
if len(contractAddress) == 0 {
|
||||||
|
return nil, api.NewAPIError("Missing contract address", true)
|
||||||
|
}
|
||||||
|
|
||||||
|
contractInfo, valid, err := s.api.GetContractInfo(contractAddress, bchain.UnknownTokenType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, api.NewAPIError(err.Error(), true)
|
||||||
|
}
|
||||||
|
if !valid {
|
||||||
|
return nil, api.NewAPIError("Not a contract", true)
|
||||||
|
}
|
||||||
|
return contractInfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InternalServer) updateContracts(r *http.Request) (interface{}, error) {
|
||||||
|
data, err := io.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, api.NewAPIError("Cannot get request body", true)
|
||||||
|
}
|
||||||
|
var contractInfos []bchain.ContractInfo
|
||||||
|
err = json.Unmarshal(data, &contractInfos)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Annotatef(err, "Cannot unmarshal body to array of ContractInfo objects")
|
||||||
|
}
|
||||||
|
for i := range contractInfos {
|
||||||
|
c := &contractInfos[i]
|
||||||
|
err := s.db.StoreContractInfo(c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, api.NewAPIError("Error updating contract "+c.Contract+" "+err.Error(), true)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return "{\"success\":\"Updated " + strconv.Itoa(len(contractInfos)) + " contracts\"}", nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,7 +14,6 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
"runtime/debug"
|
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@ -289,62 +288,6 @@ func getFunctionName(i interface{}) string {
|
|||||||
return name
|
return name
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PublicServer) jsonHandler(handler func(r *http.Request, apiVersion int) (interface{}, error), apiVersion int) func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
type jsonError struct {
|
|
||||||
Text string `json:"error"`
|
|
||||||
HTTPStatus int `json:"-"`
|
|
||||||
}
|
|
||||||
handlerName := getFunctionName(handler)
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var data interface{}
|
|
||||||
var err error
|
|
||||||
defer func() {
|
|
||||||
if e := recover(); e != nil {
|
|
||||||
glog.Error(handlerName, " recovered from panic: ", e)
|
|
||||||
debug.PrintStack()
|
|
||||||
if s.debug {
|
|
||||||
data = jsonError{fmt.Sprint("Internal server error: recovered from panic ", e), http.StatusInternalServerError}
|
|
||||||
} else {
|
|
||||||
data = jsonError{"Internal server error", http.StatusInternalServerError}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
|
||||||
if e, isError := data.(jsonError); isError {
|
|
||||||
w.WriteHeader(e.HTTPStatus)
|
|
||||||
}
|
|
||||||
err = json.NewEncoder(w).Encode(data)
|
|
||||||
if err != nil {
|
|
||||||
glog.Warning("json encode ", err)
|
|
||||||
}
|
|
||||||
s.metrics.ExplorerPendingRequests.With((common.Labels{"method": handlerName})).Dec()
|
|
||||||
}()
|
|
||||||
s.metrics.ExplorerPendingRequests.With((common.Labels{"method": handlerName})).Inc()
|
|
||||||
data, err = handler(r, apiVersion)
|
|
||||||
if err != nil || data == nil {
|
|
||||||
if apiErr, ok := err.(*api.APIError); ok {
|
|
||||||
if apiErr.Public {
|
|
||||||
data = jsonError{apiErr.Error(), http.StatusBadRequest}
|
|
||||||
} else {
|
|
||||||
data = jsonError{apiErr.Error(), http.StatusInternalServerError}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if err != nil {
|
|
||||||
glog.Error(handlerName, " error: ", err)
|
|
||||||
}
|
|
||||||
if s.debug {
|
|
||||||
if data != nil {
|
|
||||||
data = jsonError{fmt.Sprintf("Internal server error: %v, data %+v", err, data), http.StatusInternalServerError}
|
|
||||||
} else {
|
|
||||||
data = jsonError{fmt.Sprintf("Internal server error: %v", err), http.StatusInternalServerError}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
data = jsonError{"Internal server error", http.StatusInternalServerError}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *PublicServer) newTemplateData(r *http.Request) *TemplateData {
|
func (s *PublicServer) newTemplateData(r *http.Request) *TemplateData {
|
||||||
t := &TemplateData{
|
t := &TemplateData{
|
||||||
CoinName: s.is.Coin,
|
CoinName: s.is.Coin,
|
||||||
|
|||||||
39
static/internal_templates/contract_info.html
Normal file
39
static/internal_templates/contract_info.html
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
{{define "specific"}} {{if eq .ChainType 1}}
|
||||||
|
<script>
|
||||||
|
function getContractInfo(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const address = document.getElementById('contractAddress').value.trim();
|
||||||
|
if (!address) {
|
||||||
|
alert('Enter a contract address');
|
||||||
|
} else {
|
||||||
|
window.location = window.location.href.split('?')[0] + '/' + address;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<form onsubmit="return getContractInfo(event)">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-6">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
placeholder="contract address"
|
||||||
|
id="contractAddress"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<input class="btn btn-secondary" type="submit" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<div class="row" style="margin: 35px">
|
||||||
|
To update contract, use POST request to /admin/contract-info/ endpoint. Example:
|
||||||
|
<div style="margin-top: 20px">
|
||||||
|
<pre>
|
||||||
|
curl -k -v \
|
||||||
|
'https://<internaladdress>/admin/contract-info/' \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
--data '[{ContractInfo},{ContractInfo},...]'
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{else}} Not supported {{end}}{{end}}
|
||||||
@ -1,10 +1,14 @@
|
|||||||
{{define "specific"}}
|
{{define "specific"}}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col"><a href="/admin/ws-limit-exceeding-ips">IP addresses that exceeded websocket usage limit</a></div>
|
<div class="col">
|
||||||
|
<a href="/admin/ws-limit-exceeding-ips">IP addresses that exceeded websocket usage limit</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{if eq .ChainType 1}}
|
{{if eq .ChainType 1}}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col"><a href="/admin/internal-data-errors">Internal Data Errors</a></div>
|
<div class="col"><a href="/admin/internal-data-errors">Internal Data Errors</a></div>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
<div class="row">
|
||||||
{{end}}
|
<div class="col"><a href="/admin/contract-info">Contract Info</a></div>
|
||||||
|
</div>
|
||||||
|
{{end}}{{end}}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user