Show ethereum specific data in tx detail in explorer

This commit is contained in:
Martin Boehm 2018-11-28 14:56:45 +01:00
parent 8ac57a3d56
commit 463eab9d2d
9 changed files with 187 additions and 16 deletions

View File

@ -241,7 +241,10 @@ func (w *Worker) GetTransaction(txid string, spendingTxs bool, specificJSON bool
}
}
ethSpecific = eth.GetEthereumTxData(bchainTx)
feesSat.Mul(ethSpecific.GasPriceNum, ethSpecific.GasUsed)
// mempool txs do not have fees yet
if ethSpecific.GasUsed != nil {
feesSat.Mul(ethSpecific.GasPriceNum, ethSpecific.GasUsed)
}
if len(bchainTx.Vout) > 0 {
valInSat = bchainTx.Vout[0].ValueSat
}

View File

@ -430,7 +430,7 @@ func GetErc20FromTx(tx *bchain.Tx) ([]Erc20Transfer, error) {
var r []Erc20Transfer
var err error
csd, ok := tx.CoinSpecificData.(completeTransaction)
if ok {
if ok && csd.Receipt != nil {
r, err = erc20GetTransfersFromLog(csd.Receipt.Logs)
if err != nil {
return nil, err

View File

@ -40,7 +40,7 @@
"system_user": "blockbook-ethereum-classic",
"internal_binding_template": ":{{.Ports.BlockbookInternal}}",
"public_binding_template": ":{{.Ports.BlockbookPublic}}",
"explorer_url": "https://gastracker.io/",
"explorer_url": "",
"additional_params": "-resyncindexperiod=4441",
"block_chain": {
"parse": true,

View File

@ -42,7 +42,7 @@
"system_user": "blockbook-ethereum",
"internal_binding_template": ":{{.Ports.BlockbookInternal}}",
"public_binding_template": ":{{.Ports.BlockbookPublic}}",
"explorer_url": "https://etherscan.io/",
"explorer_url": "",
"additional_params": "",
"block_chain": {
"parse": true,

View File

@ -41,7 +41,7 @@
"system_user": "blockbook-ethereum",
"internal_binding_template": ":{{.Ports.BlockbookInternal}}",
"public_binding_template": ":{{.Ports.BlockbookPublic}}",
"explorer_url": "https://ropsten.etherscan.io/",
"explorer_url": "",
"additional_params": "",
"block_chain": {
"parse": true,

View File

@ -14,6 +14,7 @@ import (
"net/http"
"reflect"
"runtime"
"runtime/debug"
"strconv"
"strings"
"time"
@ -81,7 +82,7 @@ func NewPublicServer(binding string, certFiles string, db *db.RocksDB, chain bch
is: is,
debug: debugMode,
}
s.templates = parseTemplates()
s.templates = s.parseTemplates()
// map only basic functions, the rest is enabled by method MapFullPublicInterface
serveMux.Handle(path+"favicon.ico", http.FileServer(http.Dir("./static/")))
@ -202,6 +203,7 @@ func (s *PublicServer) jsonHandler(handler func(r *http.Request) (interface{}, e
defer func() {
if e := recover(); e != nil {
glog.Error(getFunctionName(handler), " recovered from panic: ", e)
debug.PrintStack()
if s.debug {
data = jsonError{fmt.Sprint("Internal server error: recovered from panic ", e), http.StatusInternalServerError}
} else {
@ -264,6 +266,7 @@ func (s *PublicServer) htmlTemplateHandler(handler func(w http.ResponseWriter, r
defer func() {
if e := recover(); e != nil {
glog.Error(getFunctionName(handler), " recovered from panic: ", e)
debug.PrintStack()
t = errorInternalTpl
if s.debug {
data = s.newTemplateDataWithError(fmt.Sprint("Internal server error: recovered from panic ", e))
@ -286,7 +289,7 @@ func (s *PublicServer) htmlTemplateHandler(handler func(w http.ResponseWriter, r
if s.debug {
// reload templates on each request
// to reflect changes during development
s.templates = parseTemplates()
s.templates = s.parseTemplates()
}
t, data, err = handler(w, r)
if err != nil || (data == nil && t != noTpl) {
@ -349,7 +352,7 @@ type TemplateData struct {
Status string
}
func parseTemplates() []*template.Template {
func (s *PublicServer) parseTemplates() []*template.Template {
templateFuncMap := template.FuncMap{
"formatTime": formatTime,
"formatUnixTime": formatUnixTime,
@ -361,11 +364,17 @@ func parseTemplates() []*template.Template {
t[errorTpl] = template.Must(template.New("error").Funcs(templateFuncMap).ParseFiles("./static/templates/error.html", "./static/templates/base.html"))
t[errorInternalTpl] = template.Must(template.New("error").Funcs(templateFuncMap).ParseFiles("./static/templates/error.html", "./static/templates/base.html"))
t[indexTpl] = template.Must(template.New("index").Funcs(templateFuncMap).ParseFiles("./static/templates/index.html", "./static/templates/base.html"))
t[txTpl] = template.Must(template.New("tx").Funcs(templateFuncMap).ParseFiles("./static/templates/tx.html", "./static/templates/txdetail.html", "./static/templates/base.html"))
t[addressTpl] = template.Must(template.New("address").Funcs(templateFuncMap).ParseFiles("./static/templates/address.html", "./static/templates/txdetail.html", "./static/templates/paging.html", "./static/templates/base.html"))
t[blocksTpl] = template.Must(template.New("blocks").Funcs(templateFuncMap).ParseFiles("./static/templates/blocks.html", "./static/templates/paging.html", "./static/templates/base.html"))
t[blockTpl] = template.Must(template.New("block").Funcs(templateFuncMap).ParseFiles("./static/templates/block.html", "./static/templates/txdetail.html", "./static/templates/paging.html", "./static/templates/base.html"))
t[sendTransactionTpl] = template.Must(template.New("block").Funcs(templateFuncMap).ParseFiles("./static/templates/sendtx.html", "./static/templates/base.html"))
if s.chainParser.GetChainType() == bchain.ChainEthereumType {
t[txTpl] = template.Must(template.New("tx").Funcs(templateFuncMap).ParseFiles("./static/templates/tx.html", "./static/templates/txdetail_ethereumtype.html", "./static/templates/base.html"))
t[addressTpl] = template.Must(template.New("address").Funcs(templateFuncMap).ParseFiles("./static/templates/address.html", "./static/templates/txdetail_ethereumtype.html", "./static/templates/paging.html", "./static/templates/base.html"))
t[blockTpl] = template.Must(template.New("block").Funcs(templateFuncMap).ParseFiles("./static/templates/block.html", "./static/templates/txdetail_ethereumtype.html", "./static/templates/paging.html", "./static/templates/base.html"))
} else {
t[txTpl] = template.Must(template.New("tx").Funcs(templateFuncMap).ParseFiles("./static/templates/tx.html", "./static/templates/txdetail.html", "./static/templates/base.html"))
t[addressTpl] = template.Must(template.New("address").Funcs(templateFuncMap).ParseFiles("./static/templates/address.html", "./static/templates/txdetail.html", "./static/templates/paging.html", "./static/templates/base.html"))
t[blockTpl] = template.Must(template.New("block").Funcs(templateFuncMap).ParseFiles("./static/templates/block.html", "./static/templates/txdetail.html", "./static/templates/paging.html", "./static/templates/base.html"))
}
return t
}

View File

@ -8,6 +8,7 @@ import (
"encoding/json"
"math/big"
"net/http"
"runtime/debug"
"strconv"
"strings"
"time"
@ -161,6 +162,7 @@ func (s *SocketIoServer) onMessage(c *gosocketio.Channel, req map[string]json.Ra
defer func() {
if r := recover(); r != nil {
glog.Error(c.Id(), " onMessage ", method, " recovered from panic: ", r)
debug.PrintStack()
e := resultError{}
e.Error.Message = "Internal error"
rv = e
@ -664,6 +666,7 @@ func (s *SocketIoServer) onSubscribe(c *gosocketio.Channel, req []byte) interfac
defer func() {
if r := recover(); r != nil {
glog.Error(c.Id(), " onSubscribe recovered from panic: ", r)
debug.PrintStack()
}
}()

View File

@ -7,20 +7,46 @@
<div class="data-div">
<table class="table data-table">
<tbody>
{{if $tx.Confirmations}}
{{- if $tx.Confirmations -}}
<tr>
<td style="width: 25%;">Mined Time</td>
<td class="data">{{formatUnixTime $tx.Blocktime}}</td>
</tr>{{end}}
</tr>{{end -}}
<tr>
<td style="width: 25%;">In Block</td>
<td class="ellipsis data">{{if $tx.Confirmations}}{{$tx.Blockhash}}{{else}}Unconfirmed{{end}}</td>
</tr>
{{if $tx.Confirmations}}
{{- if $tx.Confirmations -}}
<tr>
<td>In Block Height</td>
<td class="data"><a href="/block/{{$tx.Blockheight}}">{{$tx.Blockheight}}</a></td>
</tr>{{end}}
{{- if $tx.EthereumSpecific -}}
<tr>
<td>Status</td>
{{- if $tx.EthereumSpecific.Status -}}
{{- if ne $tx.EthereumSpecific.Status 1 -}}
<td class="data">Pending</td>
{{- else -}}
<td class="data text-success">Success</td>
{{- end -}}
{{- else -}}
<td class="data text-danger">Fail</td>
{{- end -}}
</tr>
<tr>
<td>Value</td>
<td class="data">{{formatAmount $tx.ValueOut}} {{$cs}}</td>
</tr>
<tr>
<td>Gas Used / Limit</td>
<td class="data">{{if $tx.EthereumSpecific.GasUsed}}{{$tx.EthereumSpecific.GasUsed}}{{else}}pending{{end}} / {{$tx.EthereumSpecific.GasLimit}}</td>
</tr>
<tr>
<td>Gas Price</td>
<td class="data">{{formatAmount $tx.EthereumSpecific.GasPrice}} {{$cs}}</td>
</tr>
{{- else -}}
<tr>
<td>Total Input</td>
<td class="data">{{formatAmount $tx.ValueIn}} {{$cs}}</td>
@ -29,11 +55,12 @@
<td>Total Output</td>
<td class="data">{{formatAmount $tx.ValueOut}} {{$cs}}</td>
</tr>
{{if $tx.Fees}}
{{- end -}}
{{- if $tx.Fees -}}
<tr>
<td>Fees</td>
<td class="data">{{formatAmount $tx.Fees}} {{$cs}}</td>
</tr>{{end}}
</tr>{{end -}}
</tbody>
</table>
</div>

View File

@ -0,0 +1,129 @@
{{define "txdetail"}}{{$cs := .CoinShortcut}}{{$addr := .AddrStr}}{{$tx := .Tx}}
<div class="alert alert-data">
<div class="row line-bot">
<div class="col-xs-7 col-md-8 ellipsis">
<a href="/tx/{{$tx.Txid}}">{{$tx.Txid}}</a>
</div>
{{- if $tx.Confirmations -}}
<div class="col-xs-5 col-md-4 text-muted text-right">mined {{formatUnixTime $tx.Blocktime}}</div>
{{- end -}}
</div>
<div class="row line-mid">
<div class="col-md-4">
<div class="row">
<table class="table data-table">
<tbody>
{{- range $vin := $tx.Vin -}}
<tr>
<td>
{{- range $a := $vin.Addresses -}}
<span class="ellipsis float-left">
{{if and (ne $a $addr) $vin.Searchable}}<a href="/address/{{$a}}">{{$a}}</a>{{else}}{{$a}}{{end}}
</span>
{{- else -}}
<span class="float-left">Unparsed address</span>
{{- end -}}
</td>
</tr>
{{- else -}}
<tr>
<td>No Inputs</td>
</tr>
{{- end -}}
</tbody>
</table>
</div>
</div>
<div class="col-md-1 col-xs-12 text-center">
<svg class="octicon" viewBox="0 0 8 16">
<path fill-rule="evenodd" d="M7.5 8l-5 5L1 11.5 4.75 8 1 4.5 2.5 3l5 5z"></path>
</svg>
</div>
<div class="col-md-4">
<div class="row">
<table class="table data-table">
<tbody>
{{- range $vout := $tx.Vout -}}
<tr>
<td>
{{- range $a := $vout.ScriptPubKey.Addresses -}}
<span class="ellipsis float-left">
{{- if and (ne $a $addr) $vout.ScriptPubKey.Searchable}}<a href="/address/{{$a}}">{{$a}}</a>{{else}}{{$a}}{{- end -}}
</span>
{{- else -}}
<span class="float-left">Unparsed address</span>
{{- end -}}
</td>
</tr>
{{- else -}}
<tr>
<td>No Outputs</td>
</tr>
{{- end -}}
</tbody>
</table>
</div>
</div>
<div class="col-md-3 text-right" style="padding: .4rem 0;">
{{formatAmount $tx.ValueOut}} {{$cs}}
</div>
</div>
{{- if $tx.Erc20Transfers -}}
<div class="row line-top" style="padding: 15px 0 6px 15px;">
ERC20 Token Transfers
</div>
{{- range $erc20 := $tx.Erc20Transfers -}}
<div class="row" style="padding: 2px 15px;">
<div class="col-md-4">
<div class="row">
<table class="table data-table">
<tbody>
<tr>
<td>
<span class="ellipsis float-left">{{if ne $erc20.From $addr}}<a href="/address/{{$erc20.From}}">{{$erc20.From}}</a>{{else}}{{$erc20.From}}{{end}}</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="col-md-1 col-xs-12 text-center">
<svg class="octicon" viewBox="0 0 8 16">
<path fill-rule="evenodd" d="M7.5 8l-5 5L1 11.5 4.75 8 1 4.5 2.5 3l5 5z"></path>
</svg>
</div>
<div class="col-md-4">
<div class="row">
<table class="table data-table">
<tbody>
<tr>
<td>
<span class="ellipsis float-left">{{if ne $erc20.To $addr}}<a href="/address/{{$erc20.From}}">{{$erc20.To}}</a>{{else}}{{$erc20.To}}{{end}}</span>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="col-md-3 text-right" style="padding: .4rem 0;">{{formatAmount $erc20.Tokens}} {{$erc20.Symbol}}</div>
</div>
{{- end -}}
<div class="row" style="padding: 6px 15px;"></div>
{{- end -}}
<div class="row line-top">
<div class="col-xs-6 col-sm-4 col-md-4">
{{- if $tx.Fees -}}
<span class="txvalues txvalues-default">Fee: {{formatAmount $tx.Fees}} {{$cs}}</span>
{{- end -}}
</div>
<div class="col-xs-6 col-sm-8 col-md-8 text-right">
{{- if $tx.Confirmations -}}
<span class="txvalues txvalues-success">{{$tx.Confirmations}} Confirmations</span>
{{- else -}}
<span class="txvalues txvalues-danger ng-hide">Unconfirmed Transaction!</span>
{{- end -}}
<span class="txvalues txvalues-primary">{{formatAmount $tx.ValueOut}} {{$cs}}</span>
</div>
</div>
</div>
{{end}}