Implement explorer for EthereumType coin - WIP
This commit is contained in:
parent
8886256d0b
commit
7a990f9b5b
67
api/types.go
67
api/types.go
@ -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
|
||||||
|
|||||||
@ -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++
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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, '&').replace(/</g, '<').replace(/>/g, '>');
|
json = json.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user