Show spending transactions in explorer

pull/62/head
Martin Boehm 2018-09-27 13:44:13 +02:00
parent 97613134bc
commit 44da0c8c41
3 changed files with 81 additions and 32 deletions

View File

@ -44,6 +44,61 @@ func (w *Worker) getAddressesFromVout(vout *bchain.Vout) (bchain.AddressDescript
return addrDesc, a, s, err
}
// setSpendingTxToVout is helper function, that finds transaction that spent given output and sets it to the output
// there is not an index, it must be found using addresses -> txaddresses -> tx
func (w *Worker) setSpendingTxToVout(vout *Vout, txid string, height, bestheight uint32) error {
err := w.db.GetAddrDescTransactions(vout.ScriptPubKey.AddrDesc, height, ^uint32(0), func(t string, index uint32, isOutput bool) error {
if isOutput == false {
tsp, err := w.db.GetTxAddresses(t)
if err != nil {
glog.Warning("DB inconsistency: tx ", t, ": not found in txAddresses")
} else {
if len(tsp.Inputs) > int(index) {
if tsp.Inputs[index].ValueSat.Cmp(&vout.ValueSat) == 0 {
spentTx, spentHeight, err := w.txCache.GetTransaction(t, bestheight)
if err != nil {
glog.Warning("Tx ", t, ": not found")
} else {
if len(spentTx.Vin) > int(index) {
if spentTx.Vin[index].Txid == txid {
vout.SpentTxID = t
vout.SpentHeight = int(spentHeight)
vout.SpentIndex = int(index)
return &db.StopIteration{}
}
}
}
}
}
}
}
return nil
})
return err
}
// GetSpendingTxid returns transaction id of transaction that spent given output
func (w *Worker) GetSpendingTxid(txid string, n int) (string, error) {
start := time.Now()
bestheight, _, err := w.db.GetBestBlock()
if err != nil {
return "", err
}
tx, err := w.GetTransaction(txid, bestheight, false)
if err != nil {
return "", err
}
if n >= len(tx.Vout) || n < 0 {
return "", NewApiError(fmt.Sprintf("Passed incorrect vout index %v for tx %v, len vout %v", n, tx.Txid, len(tx.Vout)), false)
}
err = w.setSpendingTxToVout(&tx.Vout[n], tx.Txid, uint32(tx.Blockheight), bestheight)
if err != nil {
return "", err
}
glog.Info("GetSpendingTxid ", txid, " ", n, " finished in ", time.Since(start))
return tx.Vout[n].SpentTxID, nil
}
// GetTransaction reads transaction data from txid
func (w *Worker) GetTransaction(txid string, bestheight uint32, spendingTxs bool) (*Tx, error) {
start := time.Now()
@ -125,37 +180,9 @@ func (w *Worker) GetTransaction(txid string, bestheight uint32, spendingTxs bool
if ta != nil {
vout.Spent = ta.Outputs[i].Spent
if spendingTxs && vout.Spent {
// find transaction that spent this output
// there is not an index, it must be found in addresses -> txaddresses -> tx
err = w.db.GetAddrDescTransactions(vout.ScriptPubKey.AddrDesc, height, ^uint32(0), func(t string, index uint32, isOutput bool) error {
if isOutput == false {
tsp, err := w.db.GetTxAddresses(t)
if err != nil {
glog.Warning("DB inconsistency: tx ", t, ": not found in txAddresses")
} else {
if len(tsp.Inputs) > int(index) {
if tsp.Inputs[index].ValueSat.Cmp(&vout.ValueSat) == 0 {
spentTx, spentHeight, err := w.txCache.GetTransaction(t, bestheight)
if err != nil {
glog.Warning("Tx ", t, ": not found")
} else {
if len(spentTx.Vin) > int(index) {
if spentTx.Vin[index].Txid == bchainTx.Txid {
vout.SpentTxID = t
vout.SpentHeight = int(spentHeight)
vout.SpentIndex = int(index)
return &db.StopIteration{}
}
}
}
}
}
}
}
return nil
})
err = w.setSpendingTxToVout(vout, bchainTx.Txid, height, bestheight)
if err != nil {
glog.Errorf("GetAddrDescTransactions error %v, %v, output %v", err, vout.ScriptPubKey.AddrDesc)
glog.Errorf("setSpendingTxToVout error %v, %v, output %v", err, vout.ScriptPubKey.AddrDesc, vout.N)
}
}
}

View File

@ -115,6 +115,7 @@ func (s *PublicServer) ConnectFullPublicInterface() {
serveMux.HandleFunc(path+"search/", s.htmlTemplateHandler(s.explorerSearch))
serveMux.HandleFunc(path+"blocks", s.htmlTemplateHandler(s.explorerBlocks))
serveMux.HandleFunc(path+"block/", s.htmlTemplateHandler(s.explorerBlock))
serveMux.HandleFunc(path+"spending/", s.htmlTemplateHandler(s.explorerSpendingTx))
} else {
// redirect to wallet requests for tx and address, possibly to external site
serveMux.HandleFunc(path+"tx/", s.txRedirect)
@ -370,7 +371,7 @@ func (s *PublicServer) explorerTx(w http.ResponseWriter, r *http.Request) (tpl,
txid := r.URL.Path[i+1:]
bestheight, _, err := s.db.GetBestBlock()
if err == nil {
tx, err = s.api.GetTransaction(txid, bestheight, true)
tx, err = s.api.GetTransaction(txid, bestheight, false)
}
if err != nil {
return errorTpl, nil, err
@ -381,6 +382,27 @@ func (s *PublicServer) explorerTx(w http.ResponseWriter, r *http.Request) (tpl,
return txTpl, data, nil
}
func (s *PublicServer) explorerSpendingTx(w http.ResponseWriter, r *http.Request) (tpl, *TemplateData, error) {
s.metrics.ExplorerViews.With(common.Labels{"action": "spendingtx"}).Inc()
var err error
parts := strings.Split(r.URL.Path, "/")
if len(parts) > 2 {
tx := parts[len(parts)-2]
n, ec := strconv.Atoi(parts[len(parts)-1])
if ec == nil {
spendingTx, err := s.api.GetSpendingTxid(tx, n)
if err == nil && spendingTx != "" {
http.Redirect(w, r, joinURL("/tx/", spendingTx), 302)
return noTpl, nil, nil
}
}
}
if err == nil {
err = api.NewApiError("Transaction not found", true)
}
return errorTpl, nil, err
}
func (s *PublicServer) explorerAddress(w http.ResponseWriter, r *http.Request) (tpl, *TemplateData, error) {
var address *api.Address
var err error

View File

@ -59,7 +59,7 @@
<span class="float-left">Unparsed address</span>
{{- end -}}
<span class="float-right{{if stringInSlice $addr $vout.ScriptPubKey.Addresses}} text-success{{end}}">
{{formatAmount $vout.Value}} {{$cs}} {{if $vout.Spent}}{{if $vout.SpentTxID}}<a class="text-danger" href="/tx/{{$vout.SpentTxID}}" title="Spent"></a>{{else}}<span class="text-danger" title="Spent"></span>{{end}}{{else -}}
{{formatAmount $vout.Value}} {{$cs}} {{if $vout.Spent}}<a class="text-danger" href="{{if $vout.SpentTxID}}/tx/{{$vout.SpentTxID}}{{else}}/spending/{{$tx.Txid}}/{{$vout.N}}{{end}}" title="Spent"></a>{{else -}}
<span class="text-success" title="Unspent"> <b>×</b></span>
{{- end -}}
</span>