EthereumType: admin interface to read and update contract info

This commit is contained in:
Martin Boehm 2024-11-25 10:30:44 +01:00
parent 19a902360e
commit a55c69a8a1
5 changed files with 166 additions and 63 deletions

View File

@ -35,6 +35,66 @@ type htmlTemplates[TD any] struct {
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) {
handlerName := getFunctionName(handler)
return func(w http.ResponseWriter, r *http.Request) {

View File

@ -5,11 +5,15 @@ import (
"encoding/json"
"fmt"
"html/template"
"io"
"net/http"
"path/filepath"
"sort"
"strconv"
"strings"
"github.com/golang/glog"
"github.com/juju/errors"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/trezor/blockbook/api"
"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))
if s.chainParser.GetChainType() == bchain.ChainEthereumType {
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
}
@ -118,7 +124,8 @@ func (s *InternalServer) index(w http.ResponseWriter, r *http.Request) {
const (
adminIndexTpl = iota + errorInternalTpl + 1
adminInternalErrorsTpl
adminLimitExceedingIPS
adminLimitExceedingIPSTpl
adminContractInfoTpl
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[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[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
}
@ -213,5 +221,54 @@ func (s *InternalServer) wsLimitExceedingIPs(w http.ResponseWriter, r *http.Requ
})
data.WsLimitExceedingIPs = ips
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
}

View File

@ -14,7 +14,6 @@ import (
"reflect"
"regexp"
"runtime"
"runtime/debug"
"sort"
"strconv"
"strings"
@ -289,62 +288,6 @@ func getFunctionName(i interface{}) string {
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 {
t := &TemplateData{
CoinName: s.is.Coin,

View 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://&lt;internaladdress&gt;/admin/contract-info/' \
-H 'Content-Type: application/json' \
--data '[{ContractInfo},{ContractInfo},...]'
</pre>
</div>
</div>
{{else}} Not supported {{end}}{{end}}

View File

@ -1,10 +1,14 @@
{{define "specific"}}
<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>
{{if eq .ChainType 1}}
<div class="row">
<div class="col"><a href="/admin/internal-data-errors">Internal Data Errors</a></div>
</div>
{{end}}
{{end}}
<div class="row">
<div class="col"><a href="/admin/contract-info">Contract Info</a></div>
</div>
{{end}}{{end}}