Highlight xpub addresses in explorer

This commit is contained in:
Martin Boehm 2019-02-04 16:53:49 +01:00
parent 9d3cd3b3e9
commit 266b0575b6
8 changed files with 87 additions and 37 deletions

View File

@ -233,7 +233,9 @@ type Address struct {
TotalTokens int `json:"totalTokens,omitempty"` TotalTokens int `json:"totalTokens,omitempty"`
Tokens []Token `json:"tokens,omitempty"` Tokens []Token `json:"tokens,omitempty"`
Erc20Contract *bchain.Erc20Contract `json:"erc20contract,omitempty"` Erc20Contract *bchain.Erc20Contract `json:"erc20contract,omitempty"`
Filter string `json:"-"` // helpers for explorer
Filter string `json:"-"`
XPubAddresses map[string]struct{} `json:"-"`
} }
// AddressUtxo holds information about address and its transactions // AddressUtxo holds information about address and its transactions

View File

@ -235,6 +235,10 @@ func (w *Worker) GetAddressForXpub(xpub string, page int, txsOnPage int, option
} }
// gap is increased one as there must be gap of empty addresses before the derivation is stopped // gap is increased one as there must be gap of empty addresses before the derivation is stopped
gap++ gap++
page--
if page < 0 {
page = 0
}
var processedHash string var processedHash string
cachedXpubsMux.Lock() cachedXpubsMux.Lock()
data, found := cachedXpubs[xpub] data, found := cachedXpubs[xpub]
@ -305,7 +309,7 @@ func (w *Worker) GetAddressForXpub(xpub string, page int, txsOnPage int, option
if option >= TxidHistory { if option >= TxidHistory {
txc := make(xpubTxids, 0, 32) txc := make(xpubTxids, 0, 32)
var addTxids func(ad *xpubAddress) var addTxids func(ad *xpubAddress)
if filter.FromHeight != 0 || filter.ToHeight != 0 || filter.Vout != AddressFilterVoutOff { if filter.FromHeight == 0 && filter.ToHeight == 0 && filter.Vout == AddressFilterVoutOff {
addTxids = func(ad *xpubAddress) { addTxids = func(ad *xpubAddress) {
txc = append(txc, ad.txids...) txc = append(txc, ad.txids...)
} }
@ -372,13 +376,21 @@ func (w *Worker) GetAddressForXpub(xpub string, page int, txsOnPage int, option
} }
} }
totalTokens := 0 totalTokens := 0
xpubAddresses := make(map[string]struct{})
tokens := make([]Token, 0, 4) tokens := make([]Token, 0, 4)
for i := range data.addresses { for i := range data.addresses {
ad := &data.addresses[i] ad := &data.addresses[i]
if ad.balance != nil { if ad.balance != nil {
totalTokens++ totalTokens++
if filter.AllTokens || !IsZeroBigInt(&ad.balance.BalanceSat) { if filter.AllTokens || !IsZeroBigInt(&ad.balance.BalanceSat) {
tokens = append(tokens, w.tokenFromXpubAddress(ad, 0, i)) t := w.tokenFromXpubAddress(ad, 0, i)
tokens = append(tokens, t)
xpubAddresses[t.Name] = struct{}{}
} else {
a, _, _ := w.chainParser.GetAddressesFromAddrDesc(ad.addrDesc)
if len(a) > 0 {
xpubAddresses[a[0]] = struct{}{}
}
} }
} }
} }
@ -387,7 +399,14 @@ func (w *Worker) GetAddressForXpub(xpub string, page int, txsOnPage int, option
if ad.balance != nil { if ad.balance != nil {
totalTokens++ totalTokens++
if filter.AllTokens || !IsZeroBigInt(&ad.balance.BalanceSat) { if filter.AllTokens || !IsZeroBigInt(&ad.balance.BalanceSat) {
tokens = append(tokens, w.tokenFromXpubAddress(ad, 1, i)) t := w.tokenFromXpubAddress(ad, 1, i)
tokens = append(tokens, t)
xpubAddresses[t.Name] = struct{}{}
} else {
a, _, _ := w.chainParser.GetAddressesFromAddrDesc(ad.addrDesc)
if len(a) > 0 {
xpubAddresses[a[0]] = struct{}{}
}
} }
} }
} }
@ -402,10 +421,11 @@ func (w *Worker) GetAddressForXpub(xpub string, page int, txsOnPage int, option
Txs: int(data.txs), Txs: int(data.txs),
// UnconfirmedBalanceSat: (*Amount)(&uBalSat), // UnconfirmedBalanceSat: (*Amount)(&uBalSat),
// UnconfirmedTxs: len(txm), // UnconfirmedTxs: len(txm),
Transactions: txs, Transactions: txs,
Txids: txids, Txids: txids,
TotalTokens: totalTokens, TotalTokens: totalTokens,
Tokens: tokens, Tokens: tokens,
XPubAddresses: xpubAddresses,
} }
glog.Info("GetAddressForXpub ", xpub[:16], ", ", len(data.addresses)+len(data.changeAddresses), " derived addresses, ", data.txs, " total txs finished in ", time.Since(start)) glog.Info("GetAddressForXpub ", xpub[:16], ", ", len(data.addresses)+len(data.changeAddresses), " derived addresses, ", data.txs, " total txs finished in ", time.Since(start))
return &addr, nil return &addr, nil

View File

@ -415,7 +415,7 @@ func (s *PublicServer) parseTemplates() []*template.Template {
"formatAmount": s.formatAmount, "formatAmount": s.formatAmount,
"formatAmountWithDecimals": formatAmountWithDecimals, "formatAmountWithDecimals": formatAmountWithDecimals,
"setTxToTemplateData": setTxToTemplateData, "setTxToTemplateData": setTxToTemplateData,
"stringInSlice": stringInSlice, "isOwnAddress": isOwnAddress,
} }
var createTemplate func(filenames ...string) *template.Template var createTemplate func(filenames ...string) *template.Template
if s.debug { if s.debug {
@ -504,6 +504,23 @@ func setTxToTemplateData(td *TemplateData, tx *api.Tx) *TemplateData {
return td return td
} }
// returns true if addresses are "own",
// i.e. either the address of the address detail or belonging to the xpub
func isOwnAddress(td *TemplateData, addresses []string) bool {
if len(addresses) == 1 {
a := addresses[0]
if a == td.AddrStr {
return true
}
if td.Address != nil && td.Address.XPubAddresses != nil {
if _, found := td.Address.XPubAddresses[a]; found {
return true
}
}
}
return false
}
func (s *PublicServer) explorerTx(w http.ResponseWriter, r *http.Request) (tpl, *TemplateData, error) { func (s *PublicServer) explorerTx(w http.ResponseWriter, r *http.Request) (tpl, *TemplateData, error) {
var tx *api.Tx var tx *api.Tx
var err error var err error

View File

@ -151,7 +151,7 @@ func httpTests_BitcoinType(t *testing.T, ts *httptest.Server) {
`td class="data">0 FAKE</td>`, `td class="data">0 FAKE</td>`,
`<a href="/address/mzVznVsCHkVHX9UN8WPFASWUUHtxnNn4Jj">mzVznVsCHkVHX9UN8WPFASWUUHtxnNn4Jj</a>`, `<a href="/address/mzVznVsCHkVHX9UN8WPFASWUUHtxnNn4Jj">mzVznVsCHkVHX9UN8WPFASWUUHtxnNn4Jj</a>`,
`13.60030331 FAKE`, `13.60030331 FAKE`,
`<td><span class="float-left">No Inputs (Newly Generated Coins)</span></td>`, `<td><span class="tx-addr">No Inputs (Newly Generated Coins)</span></td>`,
`</html>`, `</html>`,
}, },
}, },
@ -168,8 +168,8 @@ func httpTests_BitcoinType(t *testing.T, ts *httptest.Server) {
`<td class="data">0.00012345 FAKE</td>`, `<td class="data">0.00012345 FAKE</td>`,
`<a href="/tx/7c3be24063f268aaa1ed81b64776798f56088757641a34fb156c4f51ed2e9d25">7c3be24063f268aaa1ed81b64776798f56088757641a34fb156c4f51ed2e9d25</a>`, `<a href="/tx/7c3be24063f268aaa1ed81b64776798f56088757641a34fb156c4f51ed2e9d25">7c3be24063f268aaa1ed81b64776798f56088757641a34fb156c4f51ed2e9d25</a>`,
`3172.83951061 FAKE <a class="text-danger" href="/spending/7c3be24063f268aaa1ed81b64776798f56088757641a34fb156c4f51ed2e9d25/0" title="Spent">➡</a></span>`, `3172.83951061 FAKE <a class="text-danger" href="/spending/7c3be24063f268aaa1ed81b64776798f56088757641a34fb156c4f51ed2e9d25/0" title="Spent">➡</a></span>`,
`<td><span class="ellipsis float-left"><a href="/address/mtR97eM2HPWVM6c8FGLGcukgaHHQv7THoL">mtR97eM2HPWVM6c8FGLGcukgaHHQv7THoL</a></span><span class="float-right">`, `<td><span class="ellipsis tx-addr"><a href="/address/mtR97eM2HPWVM6c8FGLGcukgaHHQv7THoL">mtR97eM2HPWVM6c8FGLGcukgaHHQv7THoL</a></span><span class="tx-amt">`,
`td><span class="ellipsis float-left"><a href="/address/mtR97eM2HPWVM6c8FGLGcukgaHHQv7THoL">mtR97eM2HPWVM6c8FGLGcukgaHHQv7THoL</a></span><span class="float-right">`, `td><span class="ellipsis tx-addr"><a href="/address/mtR97eM2HPWVM6c8FGLGcukgaHHQv7THoL">mtR97eM2HPWVM6c8FGLGcukgaHHQv7THoL</a></span><span class="tx-amt">`,
`9172.83951061 FAKE <span class="text-success" title="Unspent"> <b>×</b></span></span>`, `9172.83951061 FAKE <span class="text-success" title="Unspent"> <b>×</b></span></span>`,
`<a href="/tx/00b2c06055e5e90e9c82bd4181fde310104391a7fa4f289b1704e5d90caa3840">00b2c06055e5e90e9c82bd4181fde310104391a7fa4f289b1704e5d90caa3840</a>`, `<a href="/tx/00b2c06055e5e90e9c82bd4181fde310104391a7fa4f289b1704e5d90caa3840">00b2c06055e5e90e9c82bd4181fde310104391a7fa4f289b1704e5d90caa3840</a>`,
`</html>`, `</html>`,
@ -290,7 +290,7 @@ func httpTests_BitcoinType(t *testing.T, ts *httptest.Server) {
`td class="data">0 FAKE</td>`, `td class="data">0 FAKE</td>`,
`<a href="/address/mzVznVsCHkVHX9UN8WPFASWUUHtxnNn4Jj">mzVznVsCHkVHX9UN8WPFASWUUHtxnNn4Jj</a>`, `<a href="/address/mzVznVsCHkVHX9UN8WPFASWUUHtxnNn4Jj">mzVznVsCHkVHX9UN8WPFASWUUHtxnNn4Jj</a>`,
`13.60030331 FAKE`, `13.60030331 FAKE`,
`<td><span class="float-left">No Inputs (Newly Generated Coins)</span></td>`, `<td><span class="tx-addr">No Inputs (Newly Generated Coins)</span></td>`,
`</html>`, `</html>`,
}, },
}, },
@ -307,8 +307,8 @@ func httpTests_BitcoinType(t *testing.T, ts *httptest.Server) {
`<td class="data">0.00012345 FAKE</td>`, `<td class="data">0.00012345 FAKE</td>`,
`<a href="/tx/7c3be24063f268aaa1ed81b64776798f56088757641a34fb156c4f51ed2e9d25">7c3be24063f268aaa1ed81b64776798f56088757641a34fb156c4f51ed2e9d25</a>`, `<a href="/tx/7c3be24063f268aaa1ed81b64776798f56088757641a34fb156c4f51ed2e9d25">7c3be24063f268aaa1ed81b64776798f56088757641a34fb156c4f51ed2e9d25</a>`,
`3172.83951061 FAKE <a class="text-danger" href="/spending/7c3be24063f268aaa1ed81b64776798f56088757641a34fb156c4f51ed2e9d25/0" title="Spent">➡</a></span>`, `3172.83951061 FAKE <a class="text-danger" href="/spending/7c3be24063f268aaa1ed81b64776798f56088757641a34fb156c4f51ed2e9d25/0" title="Spent">➡</a></span>`,
`<td><span class="ellipsis float-left"><a href="/address/mtR97eM2HPWVM6c8FGLGcukgaHHQv7THoL">mtR97eM2HPWVM6c8FGLGcukgaHHQv7THoL</a></span><span class="float-right">`, `<td><span class="ellipsis tx-addr"><a href="/address/mtR97eM2HPWVM6c8FGLGcukgaHHQv7THoL">mtR97eM2HPWVM6c8FGLGcukgaHHQv7THoL</a></span><span class="tx-amt">`,
`td><span class="ellipsis float-left"><a href="/address/mtR97eM2HPWVM6c8FGLGcukgaHHQv7THoL">mtR97eM2HPWVM6c8FGLGcukgaHHQv7THoL</a></span><span class="float-right">`, `td><span class="ellipsis tx-addr"><a href="/address/mtR97eM2HPWVM6c8FGLGcukgaHHQv7THoL">mtR97eM2HPWVM6c8FGLGcukgaHHQv7THoL</a></span><span class="tx-amt">`,
`9172.83951061 FAKE <span class="text-success" title="Unspent"> <b>×</b></span></span>`, `9172.83951061 FAKE <span class="text-success" title="Unspent"> <b>×</b></span></span>`,
`<a href="/tx/00b2c06055e5e90e9c82bd4181fde310104391a7fa4f289b1704e5d90caa3840">00b2c06055e5e90e9c82bd4181fde310104391a7fa4f289b1704e5d90caa3840</a>`, `<a href="/tx/00b2c06055e5e90e9c82bd4181fde310104391a7fa4f289b1704e5d90caa3840">00b2c06055e5e90e9c82bd4181fde310104391a7fa4f289b1704e5d90caa3840</a>`,
`</html>`, `</html>`,

View File

@ -292,15 +292,6 @@ type resultGetAddressHistory struct {
} `json:"result"` } `json:"result"`
} }
func stringInSlice(a string, list []string) bool {
for _, b := range list {
if b == a {
return true
}
}
return false
}
func txToResTx(tx *api.Tx) resTx { func txToResTx(tx *api.Tx) resTx {
inputs := make([]txInputs, len(tx.Vin)) inputs := make([]txInputs, len(tx.Vin))
for i := range tx.Vin { for i := range tx.Vin {

View File

@ -182,6 +182,26 @@ h3 {
text-transform: uppercase; text-transform: uppercase;
} }
.tx-own {
background-color: #fff6e6;
}
.tx-amt {
float: right!important;
}
.tx-in .tx-own .tx-amt {
color: #dc3545!important;
}
.tx-out .tx-own .tx-amt {
color: #28a745!important;
}
.tx-addr {
float: left!important;
}
.ellipsis { .ellipsis {
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
@ -283,5 +303,5 @@ table.data-table table.data-table th {
} }
.key { .key {
color: #333 ; color: #333;
} }

View File

@ -1,4 +1,4 @@
{{define "txdetail"}}{{$cs := .CoinShortcut}}{{$addr := .AddrStr}}{{$tx := .Tx}} {{define "txdetail"}}{{$cs := .CoinShortcut}}{{$addr := .AddrStr}}{{$tx := .Tx}}{{$data := .}}
<div class="alert alert-data"> <div class="alert alert-data">
<div class="row line-bot"> <div class="row line-bot">
<div class="col-xs-7 col-md-8 ellipsis"> <div class="col-xs-7 col-md-8 ellipsis">
@ -10,23 +10,23 @@
</div> </div>
<div class="row line-mid"> <div class="row line-mid">
<div class="col-md-5"> <div class="col-md-5">
<div class="row"> <div class="row tx-in">
<table class="table data-table"> <table class="table data-table">
<tbody> <tbody>
{{- range $vin := $tx.Vin -}} {{- range $vin := $tx.Vin -}}
<tr> <tr{{if isOwnAddress $data $vin.Addresses}} class="tx-own"{{end}}>
<td> <td>
{{- if $vin.Txid -}} {{- if $vin.Txid -}}
<a class="float-left text-muted" href="/tx/{{$vin.Txid}}" title="Outpoint {{$vin.Txid}},{{$vin.Vout}}">&nbsp;</a> <a class="float-left text-muted" href="/tx/{{$vin.Txid}}" title="Outpoint {{$vin.Txid}},{{$vin.Vout}}">&nbsp;</a>
{{- end -}} {{- end -}}
{{- range $a := $vin.Addresses -}} {{- range $a := $vin.Addresses -}}
<span class="ellipsis float-left"> <span class="ellipsis tx-addr">
{{if and (ne $a $addr) $vin.Searchable}}<a href="/address/{{$a}}">{{$a}}</a>{{else}}{{$a}}{{end}} {{if and (ne $a $addr) $vin.Searchable}}<a href="/address/{{$a}}">{{$a}}</a>{{else}}{{$a}}{{end}}
</span> </span>
{{- else -}} {{- else -}}
<span class="float-left">{{- if $vin.Hex -}}Unparsed address{{- else -}}No Inputs (Newly Generated Coins){{- end -}}</span> <span class="tx-addr">{{- if $vin.Hex -}}Unparsed address{{- else -}}No Inputs (Newly Generated Coins){{- end -}}</span>
{{- end -}}{{- if $vin.Addresses -}} {{- end -}}{{- if $vin.Addresses -}}
<span class="float-right{{if stringInSlice $addr $vin.Addresses}} text-danger{{end}}">{{formatAmount $vin.ValueSat}} {{$cs}}</span> <span class="tx-amt">{{formatAmount $vin.ValueSat}} {{$cs}}</span>
{{- end -}} {{- end -}}
</td> </td>
</tr> </tr>
@ -45,20 +45,20 @@
</svg> </svg>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<div class="row"> <div class="row tx-out">
<table class="table data-table"> <table class="table data-table">
<tbody> <tbody>
{{- range $vout := $tx.Vout -}} {{- range $vout := $tx.Vout -}}
<tr> <tr{{if isOwnAddress $data $vout.Addresses}} class="tx-own"{{end}}>
<td> <td>
{{- range $a := $vout.Addresses -}} {{- range $a := $vout.Addresses -}}
<span class="ellipsis float-left"> <span class="ellipsis tx-addr">
{{- if and (ne $a $addr) $vout.Searchable}}<a href="/address/{{$a}}">{{$a}}</a>{{else}}{{$a}}{{- end -}} {{- if and (ne $a $addr) $vout.Searchable}}<a href="/address/{{$a}}">{{$a}}</a>{{else}}{{$a}}{{- end -}}
</span> </span>
{{- else -}} {{- else -}}
<span class="float-left">Unparsed address</span> <span class="tx-addr">Unparsed address</span>
{{- end -}} {{- end -}}
<span class="float-right{{if stringInSlice $addr $vout.Addresses}} text-success{{end}}"> <span class="tx-amt">
{{formatAmount $vout.ValueSat}} {{$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 -}} {{formatAmount $vout.ValueSat}} {{$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> <span class="text-success" title="Unspent"> <b>×</b></span>
{{- end -}} {{- end -}}

View File

@ -29,7 +29,7 @@
<td>Total XPUB Addresses</td> <td>Total XPUB Addresses</td>
<td class="data">{{$addr.TotalTokens}}</td> <td class="data">{{$addr.TotalTokens}}</td>
</tr> </tr>
{{- if $addr.Tokens -}} {{- if $addr.TotalTokens -}}
<tr> <tr>
<td>{{if $data.AllTokens}}XPUB Addresses{{else}}XPUB Addresses with Balance{{end}}</td> <td>{{if $data.AllTokens}}XPUB Addresses{{else}}XPUB Addresses with Balance{{end}}</td>
<td style="padding: 0;"> <td style="padding: 0;">