Add initial implementation of tx explorer

pull/32/head
Martin Boehm 2018-06-28 21:18:52 +02:00
parent 13de56658c
commit ce5462118c
6 changed files with 263 additions and 62 deletions

View File

@ -49,18 +49,21 @@ func (w *Worker) GetTransaction(txid string, bestheight uint32, spendingTx bool)
vin.N = i
vin.Vout = bchainVin.Vout
vin.ScriptSig.Hex = bchainVin.ScriptSig.Hex
otx, _, err := w.txCache.GetTransaction(bchainVin.Txid, bestheight)
if err != nil {
return nil, err
}
if len(otx.Vout) > int(vin.Vout) {
vout := &otx.Vout[vin.Vout]
vin.Value = vout.Value
valIn += vout.Value
vin.ValueSat = int64(vout.Value*1E8 + 0.5)
if vout.Address != nil {
a := vout.Address.String()
vin.Addr = a
// bchainVin.Txid=="" is coinbase transaction
if bchainVin.Txid != "" {
otx, _, err := w.txCache.GetTransaction(bchainVin.Txid, bestheight)
if err != nil {
return nil, err
}
if len(otx.Vout) > int(vin.Vout) {
vout := &otx.Vout[vin.Vout]
vin.Value = vout.Value
valIn += vout.Value
vin.ValueSat = int64(vout.Value*1E8 + 0.5)
if vout.Address != nil {
a := vout.Address.String()
vin.Addr = a
}
}
}
}
@ -77,9 +80,13 @@ func (w *Worker) GetTransaction(txid string, bestheight uint32, spendingTx bool)
// TODO
}
}
// for coinbase transactions valIn is 0
fees = valIn - valOut
if fees < 0 {
fees = 0
}
// for now do not return size, we would have to compute vsize of segwit transactions
// size:=len(bchainTx.Hex) / 2
fees = valIn - valOut
r := &Tx{
Blockhash: blockhash,
Blockheight: int(height),

View File

@ -89,19 +89,28 @@ func NewPublicServer(binding string, certFiles string, db *db.RocksDB, chain bch
// default handler
serveMux.HandleFunc(path, s.index)
templateFuncMap := template.FuncMap{
"formatUnixTime": formatUnixTime,
}
s.txTpl = template.Must(template.New("tx").Funcs(templateFuncMap).ParseFiles("./static/templates/tx.html", "./static/templates/txdetail.html", "./static/templates/base.html"))
s.txTpl = parseTemplates()
return s, nil
}
func parseTemplates() (txTpl *template.Template) {
templateFuncMap := template.FuncMap{
"formatUnixTime": formatUnixTime,
"formatAmount": formatAmount,
}
txTpl = template.Must(template.New("tx").Funcs(templateFuncMap).ParseFiles("./static/templates/tx.html", "./static/templates/txdetail.html", "./static/templates/base.html"))
return
}
func formatUnixTime(ut int64) string {
return time.Unix(ut, 0).Format(time.RFC1123)
}
func formatAmount(a float64) string {
return fmt.Sprintf("%0.8f", a)
}
// Run starts the server
func (s *PublicServer) Run() error {
if s.certFiles == "" {
@ -173,7 +182,8 @@ func (s *PublicServer) explorerTx(w http.ResponseWriter, r *http.Request) {
bestheight, _, err := s.db.GetBestBlock()
if err == nil {
tx, err = s.api.GetTransaction(txid, bestheight, true)
} else {
}
if err != nil {
glog.Error(err)
}
}
@ -181,16 +191,13 @@ func (s *PublicServer) explorerTx(w http.ResponseWriter, r *http.Request) {
// temporarily reread the template on each request
// to reflect changes during development
templateFuncMap := template.FuncMap{
"formatUnixTime": formatUnixTime,
}
txTpl := template.Must(template.New("tx").Funcs(templateFuncMap).ParseFiles("./static/templates/tx.html", "./static/templates/txdetail.html", "./static/templates/base.html"))
s.txTpl = parseTemplates()
data := struct {
CoinName string
Specific *api.Tx
}{s.is.Coin, tx}
if err := txTpl.ExecuteTemplate(w, "base.html", data); err != nil {
if err := s.txTpl.ExecuteTemplate(w, "base.html", data); err != nil {
glog.Error(err)
}
}

View File

@ -4,28 +4,52 @@ html, body {
body {
width: 100%;
min-width: 727px;
min-height: 100%;
background-color: #ffffff;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Helvetica Neue", Arial, sans-serif;
font-size: 14px;
font-size: 11px;
}
a {
color: #428bca;
text-decoration: none;
}
.octicon {
color: #777;
display: inline-block;
vertical-align: text-top;
fill: currentColor;
height: 16px;
}
@media (min-width: 768px) {
.container {
width: 750px;
max-width: 750px;
}
}
@media (min-width: 992px) {
body {
font-size: 12px;
}
.container {
width: 970px;
max-width: 970px;
}
.octicon {
height: 24px;
}
}
@media (min-width: 1200px) {
body {
font-size: 14px;
}
.container {
width: 1170px;
max-width: 1170px;
}
.octicon {
height: 32px;
}
}
@ -73,6 +97,82 @@ body {
overflow: hidden;
}
.value-group {
.alert-data {
color: #383d41;
background-color: #f4f4f4;
border-color: #d6d8db;
padding: 15px;
}
.line-top {
border-top: 1px solid #EAEAEA;
padding: 15px 0 0;
}
.line-mid {
padding: 15px;
}
.line-bot {
border-bottom: 2px solid #EAEAEA;
padding: 0 0 15px;
}
.txvalues {
display: inline-block;
padding: .7em 2em;
font-size: 13px;
font-weight: 100;
color: #fff;
text-align: center;
white-space: nowrap;
vertical-align: baseline;
border-radius: .25em;
}
.txvalues-default {
background-color: #EBEBEB;
color: #333;
}
.txvalues-success {
background-color: dimgray;
}
.txvalues-primary {
background-color: #000;
}
.txvalues-danger {
background-color: #AC0015;
text-transform: uppercase;
}
.ellipsis {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.data-div {
margin: 20px 0;
}
.data-table {
table-layout: fixed;
border-radius: .25rem;
background: white;
}
.data-table td, .data-table th {
padding: .4rem;
}
.data {
font-weight: bold;
}
.alert .data-table {
margin: 0;
}

View File

@ -2,7 +2,6 @@
<html lang="en">
<head>
<base href="/explorer">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,shrink-to-fit=no">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
@ -50,19 +49,19 @@
<main id="wrap">
<div class="container">
{{template "specific" .Specific}}
</div>
</div>
</main>
<footer id="footer" class="footer">
<div class="container">
<nav class="navbar navbar-expand-lg navbar-dark bg-trezor">
<ul class="navbar-nav flex-row">
<li class="nav-item">
<li class="nav-item">
<a class="nav-link" href="http://satoshilabs.com/" target="_blank" rel="noopener noreferrer">© 2018 SatoshiLabs</a>
</li>
<li class="nav-item">
<a class="nav-link" href="https://wallet.trezor.io/tos.pdf" target="_blank" rel="noopener noreferrer">Terms</a>
</li>
</ul>
<li class="nav-item">
<a class="nav-link" href="https://wallet.trezor.io/tos.pdf" target="_blank" rel="noopener noreferrer">Terms</a>
</li>
</ul>
<ul class="navbar-nav flex-row ml-md-auto d-none d-md-flex">
<li class="nav-item active">
<a class="nav-link" rel="noopener noreferrer" href="http://shop.trezor.io">Don't have a TREZOR? Get one!</a>

View File

@ -1,27 +1,40 @@
{{define "specific"}}
<h1>Transaction</h1>
<div class="alert alert-secondary">
<strong>
<span>Transaction</span>
</strong>
<span class="text-muted">{{.Txid}}</span>
<div class="alert alert-data">
<span class="ellipsis data">{{.Txid}}</span>
</div>
<h2>Summary</h2>
<div class="value-group">
<div class="row">
<div class="col-md-4">Mined Time</div>
<div class="col-md-8 text-muted">{{formatUnixTime .Blocktime}}</div>
</div>
<div class="row">
<div class="col-md-4">In Block</div>
<div class="col-md-8 text-muted">{{.Blockhash}}</div>
</div>
<div class="row">
<div class="col-md-4">In Block Height</div>
<div class="col-md-8 text-muted">{{.Blockheight}}</div>
</div>
</div>
<h2>Details</h2>
<div class="value-group">
{{template "txdetail" .}} {{end}}
<h3>Summary</h3>
<div class="data-div">
<table class="table data-table">
<tbody>
{{if .Confirmations}}<tr>
<td style="width: 25%;">Mined Time</td>
<td class="data">{{formatUnixTime .Blocktime}}</td>
</tr>{{end}}
<tr>
<td style="width: 25%;">In Block</td>
<td class="ellipsis data">{{if .Confirmations}}{{.Blockhash}}{{else}}Unconfirmed{{end}}</td>
</tr>
{{if .Confirmations}}<tr>
<td>In Block Height</td>
<td class="data">{{.Blockheight}}</td>
</tr>{{end}}
<tr>
<td>Total Input</td>
<td class="data">{{formatAmount .ValueIn}} {{.CoinShortcut}}</td>
</tr>
<tr>
<td>Total Output</td>
<td class="data">{{formatAmount .ValueOut}} {{.CoinShortcut}}</td>
</tr>
{{if .Fees}}<tr>
<td>Fees</td>
<td class="data">{{formatAmount .Fees}} {{.CoinShortcut}}</td>
</tr>{{end}}
</tbody>
</table>
</div>
<h3>Details</h3>
<div class="data-div">
{{template "txdetail" .}} {{end}}
</div>

View File

@ -1,3 +1,78 @@
{{define "txdetail"}}
{{.Txid}}
{{define "txdetail"}}{{$cs := .CoinShortcut}}
<div class="alert alert-data">
<div class="row line-bot">
<div class="col-xs-7 col-md-8 ellipsis">
<a href="/explorer/tx/{{.Txid}}">{{.Txid}}</a>
</div>
{{if .Confirmations}}
<div class="col-xs-5 col-md-4 text-muted text-right">mined {{formatUnixTime .Blocktime}}</div>
{{end}}
</div>
<div class="row line-mid">
<div class="col-md-5">
<div class="row">
<table class="table data-table">
<tbody>
{{range $vin := .Vin}}
<tr>
<td>
{{if $vin.Txid}}
<span class="float-left ellipsis">
{{if $vin.Addr}}
<a href="/explorer/address/{{$vin.Addr}}">{{$vin.Addr}}</a>
{{else}}Unparsed address{{end}}
</span>
<span class="float-right"> {{$vin.Value}} {{$cs}} </span>
{{else}}No Inputs (Newly Generated Coins){{end}}
</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-6">
<div class="row">
<table class="table data-table">
<tbody>
{{range $vout := .Vout}}
<tr>
<td>
{{range $a := $vout.ScriptPubKey.Addresses}}
<span class="ellipsis float-left">
<a href="/explorer/address/{{$a}}">{{$a}}</a>
</span>
{{else}}
<span class="float-left">Unparsed address</span>
{{end}}
<span class="float-right">{{$vout.Value}} {{$cs}}</span>
</td>
</tr>
{{end}}
</tbody>
</table>
</div>
</div>
</div>
<div class="row line-top">
<div class="col-xs-6 col-sm-4 col-md-4">
{{if .Fees}}
<span class="txvalues txvalues-default">Fee: {{formatAmount .Fees}} {{$cs}}</span>
{{end}}
</div>
<div class="col-xs-6 col-sm-8 col-md-8 text-right">
{{if .Confirmations}}
<span class="txvalues txvalues-success">{{.Confirmations}} Confirmations</span>
{{else}}
<span class="txvalues txvalues-danger ng-hide">Unconfirmed Transaction!</span>
{{end}}
<span class="txvalues txvalues-primary">{{formatAmount .ValueOut}} {{$cs}}</span>
</div>
</div>
</div>
{{end}}