Update api.GetAddress to return more ethereum specific data

This commit is contained in:
Martin Boehm 2018-12-06 13:14:46 +01:00
parent 45e1e32e18
commit 70559ab9e0
7 changed files with 182 additions and 150 deletions

View File

@ -90,7 +90,7 @@ type Erc20Token struct {
Txs int `json:"txs"`
Name string `json:"name"`
Symbol string `json:"symbol"`
Decimal int `json:"decimal"`
Decimals int `json:"decimals"`
Balance string `json:"balance,omitempty"`
BalanceSat string `json:"balanceSat,omitempty"`
ContractIndex string `json:"-"`
@ -153,9 +153,11 @@ type Address struct {
Paging
AddrStr string `json:"addrStr"`
Balance string `json:"balance"`
BalanceSat string `json:"balanceSat"`
TotalReceived string `json:"totalReceived,omitempty"`
TotalSent string `json:"totalSent,omitempty"`
UnconfirmedBalance string `json:"unconfirmedBalance"`
UnconfirmedBalanceSat string `json:"unconfirmedBalanceSat"`
UnconfirmedTxApperances int `json:"unconfirmedTxApperances"`
TxApperances int `json:"txApperances"`
Transactions []*Tx `json:"txs,omitempty"`

View File

@ -433,68 +433,79 @@ func computePaging(count, page, itemsOnPage int) (Paging, int, int, int) {
}, from, to, page
}
func (w *Worker) getEthereumTypeAddressBalances(addrDesc bchain.AddressDescriptor, option GetAddressOption) (*db.AddrBalance, []Erc20Token, *bchain.Erc20Contract, error) {
func (w *Worker) getEthereumTypeAddressBalances(addrDesc bchain.AddressDescriptor, option GetAddressOption) (*db.AddrBalance, []Erc20Token, *bchain.Erc20Contract, uint64, error) {
var (
ba *db.AddrBalance
erc20t []Erc20Token
ci *bchain.Erc20Contract
n uint64
)
ca, err := w.db.GetAddrDescContracts(addrDesc)
if err != nil {
return nil, nil, nil, NewAPIError(fmt.Sprintf("Address not found, %v", err), true)
return nil, nil, nil, 0, NewAPIError(fmt.Sprintf("Address not found, %v", err), true)
}
if ca != nil {
ba = &db.AddrBalance{
Txs: uint32(ca.EthTxs),
}
// do not read balances etc in case of ExistOnly option
if option != Basic {
var b *big.Int
b, err = w.chain.EthereumTypeGetBalance(addrDesc)
var b *big.Int
b, err = w.chain.EthereumTypeGetBalance(addrDesc)
if err != nil {
return nil, nil, nil, 0, errors.Annotatef(err, "EthereumTypeGetBalance %v", addrDesc)
}
if b != nil {
ba.BalanceSat = *b
}
n, err = w.chain.EthereumTypeGetNonce(addrDesc)
if err != nil {
return nil, nil, nil, 0, errors.Annotatef(err, "EthereumTypeGetNonce %v", addrDesc)
}
erc20t = make([]Erc20Token, len(ca.Contracts))
for i, c := range ca.Contracts {
ci, err := w.chain.EthereumTypeGetErc20ContractInfo(c.Contract)
if err != nil {
return nil, nil, nil, errors.Annotatef(err, "EthereumTypeGetBalance %v", addrDesc)
return nil, nil, nil, 0, errors.Annotatef(err, "EthereumTypeGetErc20ContractInfo %v", c.Contract)
}
if b != nil {
ba.BalanceSat = *b
if ci == nil {
ci = &bchain.Erc20Contract{}
addresses, _, _ := w.chainParser.GetAddressesFromAddrDesc(c.Contract)
if len(addresses) > 0 {
ci.Contract = addresses[0]
ci.Name = addresses[0]
}
}
erc20t = make([]Erc20Token, len(ca.Contracts))
for i, c := range ca.Contracts {
ci, err := w.chain.EthereumTypeGetErc20ContractInfo(c.Contract)
if err != nil {
return nil, nil, nil, errors.Annotatef(err, "EthereumTypeGetErc20ContractInfo %v", c.Contract)
}
if ci == nil {
ci = &bchain.Erc20Contract{}
addresses, _, _ := w.chainParser.GetAddressesFromAddrDesc(c.Contract)
if len(addresses) > 0 {
ci.Contract = addresses[0]
ci.Name = addresses[0]
}
}
// do not read contract balances etc in case of Basic option
if option != Basic {
b, err = w.chain.EthereumTypeGetErc20ContractBalance(addrDesc, c.Contract)
if err != nil {
// return nil, nil, nil, errors.Annotatef(err, "EthereumTypeGetErc20ContractBalance %v %v", addrDesc, c.Contract)
glog.Warningf("EthereumTypeGetErc20ContractBalance addr %v, contract %v, %v", addrDesc, c.Contract, err)
}
if b == nil {
b = &big.Int{}
}
erc20t[i] = Erc20Token{
Balance: bchain.AmountToDecimalString(b, ci.Decimals),
Contract: ci.Contract,
Name: ci.Name,
Symbol: ci.Symbol,
Txs: int(c.Txs),
ContractIndex: strconv.Itoa(i + 1),
}
} else {
b = nil
}
ci, err = w.chain.EthereumTypeGetErc20ContractInfo(addrDesc)
if err != nil {
return nil, nil, nil, err
var balance, balanceSat string
if b != nil {
balance = bchain.AmountToDecimalString(b, ci.Decimals)
balanceSat = b.String()
}
erc20t[i] = Erc20Token{
Balance: balance,
BalanceSat: balanceSat,
Contract: ci.Contract,
Name: ci.Name,
Symbol: ci.Symbol,
Txs: int(c.Txs),
Decimals: ci.Decimals,
ContractIndex: strconv.Itoa(i + 1),
}
}
ci, err = w.chain.EthereumTypeGetErc20ContractInfo(addrDesc)
if err != nil {
return nil, nil, nil, 0, err
}
}
return ba, erc20t, ci, nil
return ba, erc20t, ci, n, nil
}
// GetAddress computes address value and gets transactions for given address
@ -509,15 +520,23 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, option GetA
return nil, NewAPIError(fmt.Sprintf("Invalid address, %v", err), true)
}
var (
ba *db.AddrBalance
erc20t []Erc20Token
erc20c *bchain.Erc20Contract
ba *db.AddrBalance
erc20t []Erc20Token
erc20c *bchain.Erc20Contract
txm []string
txs []*Tx
txids []string
pg Paging
uBalSat big.Int
totalReceived, totalSent, nonce string
)
if w.chainType == bchain.ChainEthereumType {
ba, erc20t, erc20c, err = w.getEthereumTypeAddressBalances(addrDesc, option)
var n uint64
ba, erc20t, erc20c, n, err = w.getEthereumTypeAddressBalances(addrDesc, option)
if err != nil {
return nil, err
}
nonce = strconv.Itoa(int(n))
} else {
// ba can be nil if the address is only in mempool!
ba, err = w.db.GetAddrDescBalance(addrDesc)
@ -525,132 +544,125 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, option GetA
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 option == Basic && ba != nil {
return &Address{AddrStr: address}, nil
}
// convert the address to the format defined by the parser
addresses, _, err := w.chainParser.GetAddressesFromAddrDesc(addrDesc)
if err != nil {
glog.V(2).Infof("GetAddressesFromAddrDesc error %v, %v", err, addrDesc)
}
if len(addresses) == 1 {
address = addresses[0]
}
txc, err := w.getAddressTxids(addrDesc, false, filter)
if err != nil {
return nil, errors.Annotatef(err, "getAddressTxids %v false", addrDesc)
}
txc = UniqueTxidsInReverse(txc)
var txm []string
// if there are only unconfirmed transactions, ba is nil
if ba == nil {
ba = &db.AddrBalance{}
page = 0
}
txm, err = w.getAddressTxids(addrDesc, true, filter)
if err != nil {
return nil, errors.Annotatef(err, "getAddressTxids %v true", addrDesc)
}
txm = UniqueTxidsInReverse(txm)
// check if the address exist
if len(txc)+len(txm) == 0 || option == Basic {
return &Address{
AddrStr: address,
Balance: w.chainParser.AmountToDecimalString(&ba.BalanceSat),
TotalReceived: w.chainParser.AmountToDecimalString(ba.ReceivedSat()),
TotalSent: w.chainParser.AmountToDecimalString(&ba.SentSat),
TxApperances: int(ba.Txs),
Erc20Contract: erc20c,
Erc20Tokens: erc20t,
}, nil
}
bestheight, _, err := w.db.GetBestBlock()
if err != nil {
return nil, errors.Annotatef(err, "GetBestBlock")
}
pg, from, to, page := computePaging(len(txc), page, txsOnPage)
var txs []*Tx
var txids []string
if option == TxidHistory {
txids = make([]string, len(txm)+to-from)
} else {
txs = make([]*Tx, len(txm)+to-from)
}
txi := 0
// load mempool transactions
var uBalSat big.Int
for _, txid := range txm {
tx, err := w.GetTransaction(txid, false, false)
// mempool transaction may fail
// get tx history if requested by option or check mempool if there are some transactions for a new address
if option == TxidHistory || option == TxHistory || ba == nil {
// convert the address to the format defined by the parser
addresses, _, err := w.chainParser.GetAddressesFromAddrDesc(addrDesc)
if err != nil {
glog.Error("GetTransaction in mempool ", tx, ": ", err)
} else {
uBalSat.Add(&uBalSat, tx.getAddrVoutValue(addrDesc))
uBalSat.Sub(&uBalSat, tx.getAddrVinValue(addrDesc))
if page == 0 {
if option == TxidHistory {
txids[txi] = tx.Txid
glog.V(2).Infof("GetAddressesFromAddrDesc error %v, %v", err, addrDesc)
}
if len(addresses) == 1 {
address = addresses[0]
}
txm, err = w.getAddressTxids(addrDesc, true, filter)
if err != nil {
return nil, errors.Annotatef(err, "getAddressTxids %v true", addrDesc)
}
txm = UniqueTxidsInReverse(txm)
// if there are only unconfirmed transactions, there is no paging
if ba == nil {
ba = &db.AddrBalance{}
page = 0
}
if option == TxidHistory || option == TxHistory {
txc, err := w.getAddressTxids(addrDesc, false, filter)
if err != nil {
return nil, errors.Annotatef(err, "getAddressTxids %v false", addrDesc)
}
txc = UniqueTxidsInReverse(txc)
bestheight, _, err := w.db.GetBestBlock()
if err != nil {
return nil, errors.Annotatef(err, "GetBestBlock")
}
var from, to int
pg, from, to, page = computePaging(len(txc), page, txsOnPage)
if option == TxidHistory {
txids = make([]string, len(txm)+to-from)
} else {
txs = make([]*Tx, len(txm)+to-from)
}
txi := 0
// load mempool transactions
for _, txid := range txm {
tx, err := w.GetTransaction(txid, false, false)
// mempool transaction may fail
if err != nil {
glog.Error("GetTransaction in mempool ", tx, ": ", err)
} else {
txs[txi] = tx
uBalSat.Add(&uBalSat, tx.getAddrVoutValue(addrDesc))
uBalSat.Sub(&uBalSat, tx.getAddrVinValue(addrDesc))
if page == 0 {
if option == TxidHistory {
txids[txi] = tx.Txid
} else {
txs[txi] = tx
}
txi++
}
}
}
if len(txc) != int(ba.Txs) && w.chainType == bchain.ChainBitcoinType && filter == AddressFilterNone {
glog.Warning("DB inconsistency for address ", address, ": number of txs from column addresses ", len(txc), ", from addressBalance ", ba.Txs)
}
for i := from; i < to; i++ {
txid := txc[i]
if option == TxidHistory {
txids[txi] = txid
} else {
if w.chainType == bchain.ChainEthereumType {
txs[txi], err = w.GetTransaction(txid, false, true)
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)
}
}
txi++
}
}
}
if len(txc) != int(ba.Txs) && w.chainType == bchain.ChainBitcoinType && filter == AddressFilterNone {
glog.Warning("DB inconsistency for address ", address, ": number of txs from column addresses ", len(txc), ", from addressBalance ", ba.Txs)
}
for i := from; i < to; i++ {
txid := txc[i]
if option == TxidHistory {
txids[txi] = txid
} else {
if w.chainType == bchain.ChainEthereumType {
txs[txi], err = w.GetTransaction(txid, false, true)
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 option == TxidHistory {
txids = txids[:txi]
} else if option == TxHistory {
txs = txs[:txi]
}
}
txi++
}
if option == TxidHistory {
txids = txids[:txi]
} else {
txs = txs[:txi]
if w.chainType == bchain.ChainBitcoinType {
totalReceived = w.chainParser.AmountToDecimalString(ba.ReceivedSat())
totalSent = w.chainParser.AmountToDecimalString(&ba.SentSat)
}
r := &Address{
Paging: pg,
AddrStr: address,
Balance: w.chainParser.AmountToDecimalString(&ba.BalanceSat),
TotalReceived: w.chainParser.AmountToDecimalString(ba.ReceivedSat()),
TotalSent: w.chainParser.AmountToDecimalString(&ba.SentSat),
BalanceSat: ba.BalanceSat.String(),
TotalReceived: totalReceived,
TotalSent: totalSent,
TxApperances: int(ba.Txs),
UnconfirmedBalance: w.chainParser.AmountToDecimalString(&uBalSat),
UnconfirmedBalanceSat: uBalSat.String(),
UnconfirmedTxApperances: len(txm),
Transactions: txs,
Txids: txids,
Erc20Contract: erc20c,
Erc20Tokens: erc20t,
Nonce: nonce,
}
glog.Info("GetAddress ", address, " finished in ", time.Since(start))
return r, nil

View File

@ -34,6 +34,11 @@ func (b *BaseChain) EthereumTypeGetBalance(addrDesc AddressDescriptor) (*big.Int
return nil, errors.New("Not supported")
}
// EthereumTypeGetNonce is not supported
func (b *BaseChain) EthereumTypeGetNonce(addrDesc AddressDescriptor) (uint64, error) {
return 0, errors.New("Not supported")
}
// EthereumTypeGetErc20ContractInfo is not supported
func (b *BaseChain) EthereumTypeGetErc20ContractInfo(contractDesc AddressDescriptor) (*Erc20Contract, error) {
return nil, errors.New("Not supported")

View File

@ -247,6 +247,11 @@ func (c *blockChainWithMetrics) EthereumTypeGetBalance(addrDesc bchain.AddressDe
return c.b.EthereumTypeGetBalance(addrDesc)
}
func (c *blockChainWithMetrics) EthereumTypeGetNonce(addrDesc bchain.AddressDescriptor) (v uint64, err error) {
defer func(s time.Time) { c.observeRPCLatency("EthereumTypeGetNonce", s, err) }(time.Now())
return c.b.EthereumTypeGetNonce(addrDesc)
}
func (c *blockChainWithMetrics) EthereumTypeGetErc20ContractInfo(contractDesc bchain.AddressDescriptor) (v *bchain.Erc20Contract, err error) {
defer func(s time.Time) { c.observeRPCLatency("EthereumTypeGetErc20ContractInfo", s, err) }(time.Now())
return c.b.EthereumTypeGetErc20ContractInfo(contractDesc)

View File

@ -674,6 +674,13 @@ func (b *EthereumRPC) EthereumTypeGetBalance(addrDesc bchain.AddressDescriptor)
return b.client.BalanceAt(ctx, ethcommon.BytesToAddress(addrDesc), nil)
}
// EthereumTypeGetNonce returns current balance of an address
func (b *EthereumRPC) EthereumTypeGetNonce(addrDesc bchain.AddressDescriptor) (uint64, error) {
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
defer cancel()
return b.client.NonceAt(ctx, ethcommon.BytesToAddress(addrDesc), nil)
}
// ResyncMempool gets mempool transactions and maps output scripts to transactions.
// ResyncMempool is not reentrant, it should be called from a single thread.
// Return value is number of transactions in mempool

View File

@ -215,6 +215,7 @@ type BlockChain interface {
GetChainParser() BlockChainParser
// EthereumType specific
EthereumTypeGetBalance(addrDesc AddressDescriptor) (*big.Int, error)
EthereumTypeGetNonce(addrDesc AddressDescriptor) (uint64, error)
EthereumTypeGetErc20ContractInfo(contractDesc AddressDescriptor) (*Erc20Contract, error)
EthereumTypeGetErc20ContractBalance(addrDesc, contractDesc AddressDescriptor) (*big.Int, error)
}

View File

@ -405,7 +405,7 @@ func httpTests(t *testing.T, ts *httptest.Server) {
status: http.StatusOK,
contentType: "application/json; charset=utf-8",
body: []string{
`{"page":1,"totalPages":1,"itemsOnPage":1000,"addrStr":"mv9uLThosiEnGRbVPS7Vhyw6VssbVRsiAw","balance":"0","totalReceived":"12345.67890123","totalSent":"12345.67890123","unconfirmedBalance":"0","unconfirmedTxApperances":0,"txApperances":2,"transactions":["7c3be24063f268aaa1ed81b64776798f56088757641a34fb156c4f51ed2e9d25","effd9ef509383d536b1c8af5bf434c8efbf521a4f2befd4022bbd68694b4ac75"]}`,
`{"page":1,"totalPages":1,"itemsOnPage":1000,"addrStr":"mv9uLThosiEnGRbVPS7Vhyw6VssbVRsiAw","balance":"0","balanceSat":"0","totalReceived":"12345.67890123","totalSent":"12345.67890123","unconfirmedBalance":"0","unconfirmedBalanceSat":"0","unconfirmedTxApperances":0,"txApperances":2,"transactions":["7c3be24063f268aaa1ed81b64776798f56088757641a34fb156c4f51ed2e9d25","effd9ef509383d536b1c8af5bf434c8efbf521a4f2befd4022bbd68694b4ac75"]}`,
},
},
{