diff --git a/api/worker.go b/api/worker.go
index 3aba7a10..2dd6cb89 100644
--- a/api/worker.go
+++ b/api/worker.go
@@ -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)
}
}
}
diff --git a/server/public.go b/server/public.go
index eb9258ab..b39aa796 100644
--- a/server/public.go
+++ b/server/public.go
@@ -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
diff --git a/static/templates/txdetail.html b/static/templates/txdetail.html
index 65183ea8..25a893c4 100644
--- a/static/templates/txdetail.html
+++ b/static/templates/txdetail.html
@@ -59,7 +59,7 @@
Unparsed address
{{- end -}}
- {{formatAmount $vout.Value}} {{$cs}} {{if $vout.Spent}}{{if $vout.SpentTxID}}➡{{else}}➡{{end}}{{else -}}
+ {{formatAmount $vout.Value}} {{$cs}} {{if $vout.Spent}}➡{{else -}}
×
{{- end -}}