Implement explorer for EthereumType coin - WIP

This commit is contained in:
Martin Boehm 2018-11-26 00:20:01 +01:00
parent 8886256d0b
commit 7a990f9b5b
5 changed files with 98 additions and 61 deletions

View File

@ -4,6 +4,7 @@ import (
"blockbook/bchain" "blockbook/bchain"
"blockbook/common" "blockbook/common"
"blockbook/db" "blockbook/db"
"encoding/json"
"math/big" "math/big"
"time" "time"
) )
@ -70,25 +71,26 @@ type Vout struct {
// Tx holds information about a transaction // Tx holds information about a transaction
type Tx struct { type Tx struct {
Txid string `json:"txid"` Txid string `json:"txid"`
Version int32 `json:"version,omitempty"` Version int32 `json:"version,omitempty"`
Locktime uint32 `json:"locktime,omitempty"` Locktime uint32 `json:"locktime,omitempty"`
Vin []Vin `json:"vin"` Vin []Vin `json:"vin"`
Vout []Vout `json:"vout"` Vout []Vout `json:"vout"`
Blockhash string `json:"blockhash,omitempty"` Blockhash string `json:"blockhash,omitempty"`
Blockheight int `json:"blockheight"` Blockheight int `json:"blockheight"`
Confirmations uint32 `json:"confirmations"` Confirmations uint32 `json:"confirmations"`
Time int64 `json:"time,omitempty"` Time int64 `json:"time,omitempty"`
Blocktime int64 `json:"blocktime"` Blocktime int64 `json:"blocktime"`
ValueOut string `json:"valueOut"` ValueOut string `json:"valueOut"`
ValueOutSat big.Int `json:"-"` ValueOutSat big.Int `json:"-"`
Size int `json:"size,omitempty"` Size int `json:"size,omitempty"`
ValueIn string `json:"valueIn"` ValueIn string `json:"valueIn"`
ValueInSat big.Int `json:"-"` ValueInSat big.Int `json:"-"`
Fees string `json:"fees"` Fees string `json:"fees"`
FeesSat big.Int `json:"-"` FeesSat big.Int `json:"-"`
Hex string `json:"hex"` Hex string `json:"hex"`
CoinSpecificData interface{} `json:"-"` CoinSpecificData interface{} `json:"-"`
CoinSpecificJSON json.RawMessage `json:"-"`
} }
// Paging contains information about paging for address, blocks and block // Paging contains information about paging for address, blocks and block
@ -98,18 +100,27 @@ type Paging struct {
ItemsOnPage int `json:"itemsOnPage"` ItemsOnPage int `json:"itemsOnPage"`
} }
type Erc20Token struct {
Contract string `json:"contract"`
Txs int `json:"txs"`
Name string `json:"name"`
Symbol string `json:"symbol"`
Value string `json:"value"`
}
// Address holds information about address and its transactions // Address holds information about address and its transactions
type Address struct { type Address struct {
Paging Paging
AddrStr string `json:"addrStr"` AddrStr string `json:"addrStr"`
Balance string `json:"balance"` Balance string `json:"balance"`
TotalReceived string `json:"totalReceived"` TotalReceived string `json:"totalReceived"`
TotalSent string `json:"totalSent"` TotalSent string `json:"totalSent"`
UnconfirmedBalance string `json:"unconfirmedBalance"` UnconfirmedBalance string `json:"unconfirmedBalance"`
UnconfirmedTxApperances int `json:"unconfirmedTxApperances"` UnconfirmedTxApperances int `json:"unconfirmedTxApperances"`
TxApperances int `json:"txApperances"` TxApperances int `json:"txApperances"`
Transactions []*Tx `json:"txs,omitempty"` Transactions []*Tx `json:"txs,omitempty"`
Txids []string `json:"transactions,omitempty"` Txids []string `json:"transactions,omitempty"`
Erc20Tokens []*Erc20Token `json:"erc20tokens,omitempty"`
} }
// AddressUtxo holds information about address and its transactions // AddressUtxo holds information about address and its transactions

View File

@ -99,7 +99,7 @@ func (w *Worker) GetSpendingTxid(txid string, n int) (string, error) {
} }
// GetTransaction reads transaction data from txid // GetTransaction reads transaction data from txid
func (w *Worker) GetTransaction(txid string, spendingTxs bool, specificData bool) (*Tx, error) { func (w *Worker) GetTransaction(txid string, spendingTxs bool, specificJSON bool) (*Tx, error) {
start := time.Now() start := time.Now()
bchainTx, height, err := w.txCache.GetTransaction(txid) bchainTx, height, err := w.txCache.GetTransaction(txid)
if err != nil { if err != nil {
@ -211,9 +211,9 @@ func (w *Worker) GetTransaction(txid string, spendingTxs bool, specificData bool
} }
// for now do not return size, we would have to compute vsize of segwit transactions // for now do not return size, we would have to compute vsize of segwit transactions
// size:=len(bchainTx.Hex) / 2 // size:=len(bchainTx.Hex) / 2
var sd json.RawMessage var sj json.RawMessage
if specificData { if specificJSON {
sd, err = w.chain.GetTransactionSpecific(bchainTx) sj, err = w.chain.GetTransactionSpecific(bchainTx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -236,7 +236,8 @@ func (w *Worker) GetTransaction(txid string, spendingTxs bool, specificData bool
Hex: bchainTx.Hex, Hex: bchainTx.Hex,
Vin: vins, Vin: vins,
Vout: vouts, Vout: vouts,
CoinSpecificData: sd, CoinSpecificData: bchainTx.CoinSpecificData,
CoinSpecificJSON: sj,
} }
if spendingTxs { if spendingTxs {
glog.Info("GetTransaction ", txid, " finished in ", time.Since(start)) glog.Info("GetTransaction ", txid, " finished in ", time.Since(start))
@ -250,6 +251,7 @@ func (w *Worker) getAddressTxids(addrDesc bchain.AddressDescriptor, mempool bool
if !mempool { if !mempool {
err = w.db.GetAddrDescTransactions(addrDesc, 0, ^uint32(0), func(txid string, vout uint32, isOutput bool) error { err = w.db.GetAddrDescTransactions(addrDesc, 0, ^uint32(0), func(txid string, vout uint32, isOutput bool) error {
txids = append(txids, txid) txids = append(txids, txid)
// glog.Info(txid, " ", vout, " ", isOutput)
return nil return nil
}) })
if err != nil { if err != nil {
@ -374,7 +376,7 @@ func computePaging(count, page, itemsOnPage int) (Paging, int, int, int) {
} }
// GetAddress computes address value and gets transactions for given address // GetAddress computes address value and gets transactions for given address
func (w *Worker) GetAddress(address string, page int, txsOnPage int, onlyTxids bool) (*Address, error) { func (w *Worker) GetAddress(address string, page int, txsOnPage int, onlyTxids, existOnly bool) (*Address, error) {
start := time.Now() start := time.Now()
page-- page--
if page < 0 { if page < 0 {
@ -384,10 +386,26 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, onlyTxids b
if err != nil { if err != nil {
return nil, NewAPIError(fmt.Sprintf("Invalid address, %v", err), true) return nil, NewAPIError(fmt.Sprintf("Invalid address, %v", err), true)
} }
// ba can be nil if the address is only in mempool! var (
ba, err := w.db.GetAddrDescBalance(addrDesc) ba *db.AddrBalance
if err != nil { ca *db.AddrContracts
return nil, NewAPIError(fmt.Sprintf("Address not found, %v", err), true) )
if w.chainType == bchain.ChainEthereumType {
ca, err = w.db.GetAddrDescContracts(addrDesc)
if err != nil {
return nil, NewAPIError(fmt.Sprintf("Address not found, %v", err), true)
}
glog.Infof("%+v", ca)
} else {
// ba can be nil if the address is only in mempool!
ba, err = w.db.GetAddrDescBalance(addrDesc)
if err != nil {
return nil, NewAPIError(fmt.Sprintf("Address not found, %v", err), true)
}
}
// if only check that the address exist, return if we have the address
if existOnly && ba != nil {
return &Address{AddrStr: address}, nil
} }
// convert the address to the format defined by the parser // convert the address to the format defined by the parser
addresses, _, err := w.chainParser.GetAddressesFromAddrDesc(addrDesc) addresses, _, err := w.chainParser.GetAddressesFromAddrDesc(addrDesc)
@ -434,8 +452,8 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, onlyTxids b
txi := 0 txi := 0
// load mempool transactions // load mempool transactions
var uBalSat big.Int var uBalSat big.Int
for _, tx := range txm { for _, txid := range txm {
tx, err := w.GetTransaction(tx, false, false) tx, err := w.GetTransaction(txid, false, false)
// mempool transaction may fail // mempool transaction may fail
if err != nil { if err != nil {
glog.Error("GetTransaction in mempool ", tx, ": ", err) glog.Error("GetTransaction in mempool ", tx, ": ", err)
@ -460,23 +478,30 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, onlyTxids b
if onlyTxids { if onlyTxids {
txids[txi] = txid txids[txi] = txid
} else { } else {
ta, err := w.db.GetTxAddresses(txid) if w.chainType == bchain.ChainEthereumType {
if err != nil { txs[txi], err = w.GetTransaction(txid, false, true)
return nil, errors.Annotatef(err, "GetTxAddresses %v", txid) if err != nil {
return nil, errors.Annotatef(err, "GetTransaction %v", txid)
}
} else {
ta, err := w.db.GetTxAddresses(txid)
if err != nil {
return nil, errors.Annotatef(err, "GetTxAddresses %v", txid)
}
if ta == nil {
glog.Warning("DB inconsistency: tx ", txid, ": not found in txAddresses")
continue
}
bi, err := w.db.GetBlockInfo(ta.Height)
if err != nil {
return nil, errors.Annotatef(err, "GetBlockInfo %v", ta.Height)
}
if bi == nil {
glog.Warning("DB inconsistency: block height ", ta.Height, ": not found in db")
continue
}
txs[txi] = w.txFromTxAddress(txid, ta, bi, bestheight)
} }
if ta == nil {
glog.Warning("DB inconsistency: tx ", txid, ": not found in txAddresses")
continue
}
bi, err := w.db.GetBlockInfo(ta.Height)
if err != nil {
return nil, errors.Annotatef(err, "GetBlockInfo %v", ta.Height)
}
if bi == nil {
glog.Warning("DB inconsistency: block height ", ta.Height, ": not found in db")
continue
}
txs[txi] = w.txFromTxAddress(txid, ta, bi, bestheight)
} }
txi++ txi++
} }

View File

@ -445,7 +445,8 @@ func (b *EthereumRPC) getERC20EventsForBlock(blockNumber string) (map[string][]*
return nil, errors.Annotatef(err, "blockNumber %v", blockNumber) return nil, errors.Annotatef(err, "blockNumber %v", blockNumber)
} }
r := make(map[string][]*rpcLog) r := make(map[string][]*rpcLog)
for _, l := range logs { for i := range logs {
l := &logs[i]
r[l.Hash] = append(r[l.Hash], &l.rpcLog) r[l.Hash] = append(r[l.Hash], &l.rpcLog)
} }
return r, nil return r, nil

View File

@ -435,7 +435,7 @@ func (s *PublicServer) explorerAddress(w http.ResponseWriter, r *http.Request) (
if ec != nil { if ec != nil {
page = 0 page = 0
} }
address, err = s.api.GetAddress(r.URL.Path[i+1:], page, txsOnPage, false) address, err = s.api.GetAddress(r.URL.Path[i+1:], page, txsOnPage, false, false)
if err != nil { if err != nil {
return errorTpl, nil, err return errorTpl, nil, err
} }
@ -519,7 +519,7 @@ func (s *PublicServer) explorerSearch(w http.ResponseWriter, r *http.Request) (t
http.Redirect(w, r, joinURL("/tx/", tx.Txid), 302) http.Redirect(w, r, joinURL("/tx/", tx.Txid), 302)
return noTpl, nil, nil return noTpl, nil, nil
} }
address, err = s.api.GetAddress(q, 0, 1, true) address, err = s.api.GetAddress(q, 0, 1, true, true)
if err == nil { if err == nil {
http.Redirect(w, r, joinURL("/address/", address.AddrStr), 302) http.Redirect(w, r, joinURL("/address/", address.AddrStr), 302)
return noTpl, nil, nil return noTpl, nil, nil
@ -674,7 +674,7 @@ func (s *PublicServer) apiAddress(r *http.Request) (interface{}, error) {
if ec != nil { if ec != nil {
page = 0 page = 0
} }
address, err = s.api.GetAddress(r.URL.Path[i+1:], page, txsInAPI, true) address, err = s.api.GetAddress(r.URL.Path[i+1:], page, txsInAPI, true, false)
} }
return address, err return address, err
} }

View File

@ -47,7 +47,7 @@
<pre id="txSpecific"></pre> <pre id="txSpecific"></pre>
</div> </div>
<script type="text/javascript"> <script type="text/javascript">
txSpecific = {{$tx.CoinSpecificData}}; txSpecific = {{$tx.CoinSpecificJSON}};
function syntaxHighlight(json) { function syntaxHighlight(json) {
json = JSON.stringify(json, undefined, 2); json = JSON.stringify(json, undefined, 2);
json = json.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;'); json = json.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');