From 70559ab9e0dc065ac9f8649b28afbe34c048b972 Mon Sep 17 00:00:00 2001 From: Martin Boehm Date: Thu, 6 Dec 2018 13:14:46 +0100 Subject: [PATCH] Update api.GetAddress to return more ethereum specific data --- api/types.go | 4 +- api/worker.go | 308 +++++++++++++++++++------------------ bchain/basechain.go | 5 + bchain/coins/blockchain.go | 5 + bchain/coins/eth/ethrpc.go | 7 + bchain/types.go | 1 + server/public_test.go | 2 +- 7 files changed, 182 insertions(+), 150 deletions(-) diff --git a/api/types.go b/api/types.go index a701f8e3..644fc87e 100644 --- a/api/types.go +++ b/api/types.go @@ -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"` diff --git a/api/worker.go b/api/worker.go index 85f79738..78932c92 100644 --- a/api/worker.go +++ b/api/worker.go @@ -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 diff --git a/bchain/basechain.go b/bchain/basechain.go index f7c1d1fa..8fbd2023 100644 --- a/bchain/basechain.go +++ b/bchain/basechain.go @@ -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") diff --git a/bchain/coins/blockchain.go b/bchain/coins/blockchain.go index c9b015f7..52799ced 100644 --- a/bchain/coins/blockchain.go +++ b/bchain/coins/blockchain.go @@ -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) diff --git a/bchain/coins/eth/ethrpc.go b/bchain/coins/eth/ethrpc.go index 517594f3..0d7c575e 100644 --- a/bchain/coins/eth/ethrpc.go +++ b/bchain/coins/eth/ethrpc.go @@ -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 diff --git a/bchain/types.go b/bchain/types.go index a90c939b..4c280d54 100644 --- a/bchain/types.go +++ b/bchain/types.go @@ -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) } diff --git a/server/public_test.go b/server/public_test.go index 5b937231..5e782227 100644 --- a/server/public_test.go +++ b/server/public_test.go @@ -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"]}`, }, }, {