Return address aliases from API
This commit is contained in:
parent
77561e3567
commit
8bdc3da694
13
api/types.go
13
api/types.go
@ -211,6 +211,12 @@ type EthereumSpecific struct {
|
||||
InternalTransfers []EthereumInternalTransfer `json:"internalTransfers,omitempty"`
|
||||
}
|
||||
|
||||
type AddressAlias struct {
|
||||
Type string
|
||||
Alias string
|
||||
}
|
||||
type AddressAliasesMap map[string]AddressAlias
|
||||
|
||||
// Tx holds information about a transaction
|
||||
type Tx struct {
|
||||
Txid string `json:"txid"`
|
||||
@ -231,6 +237,7 @@ type Tx struct {
|
||||
CoinSpecificData json.RawMessage `json:"coinSpecificData,omitempty"`
|
||||
TokenTransfers []TokenTransfer `json:"tokenTransfers,omitempty"`
|
||||
EthereumSpecific *EthereumSpecific `json:"ethereumSpecific,omitempty"`
|
||||
AddressAliases AddressAliasesMap `json:"addressAliases,omitempty"`
|
||||
}
|
||||
|
||||
// FeeStats contains detailed block fee statistics
|
||||
@ -298,6 +305,7 @@ type Address struct {
|
||||
UsedTokens int `json:"usedTokens,omitempty"`
|
||||
Tokens []Token `json:"tokens,omitempty"`
|
||||
Erc20Contract *bchain.Erc20Contract `json:"erc20Contract,omitempty"`
|
||||
AddressAliases AddressAliasesMap `json:"addressAliases,omitempty"`
|
||||
// helpers for explorer
|
||||
Filter string `json:"-"`
|
||||
XPubAddresses map[string]struct{} `json:"-"`
|
||||
@ -428,8 +436,9 @@ type BlockInfo struct {
|
||||
type Block struct {
|
||||
Paging
|
||||
BlockInfo
|
||||
TxCount int `json:"txCount"`
|
||||
Transactions []*Tx `json:"txs,omitempty"`
|
||||
TxCount int `json:"txCount"`
|
||||
Transactions []*Tx `json:"txs,omitempty"`
|
||||
AddressAliases AddressAliasesMap `json:"addressAliases,omitempty"`
|
||||
}
|
||||
|
||||
// BlockRaw contains raw block in hex
|
||||
|
||||
151
api/worker.go
151
api/worker.go
@ -23,27 +23,29 @@ import (
|
||||
|
||||
// Worker is handle to api worker
|
||||
type Worker struct {
|
||||
db *db.RocksDB
|
||||
txCache *db.TxCache
|
||||
chain bchain.BlockChain
|
||||
chainParser bchain.BlockChainParser
|
||||
chainType bchain.ChainType
|
||||
mempool bchain.Mempool
|
||||
is *common.InternalState
|
||||
metrics *common.Metrics
|
||||
db *db.RocksDB
|
||||
txCache *db.TxCache
|
||||
chain bchain.BlockChain
|
||||
chainParser bchain.BlockChainParser
|
||||
chainType bchain.ChainType
|
||||
useAddressAliases bool
|
||||
mempool bchain.Mempool
|
||||
is *common.InternalState
|
||||
metrics *common.Metrics
|
||||
}
|
||||
|
||||
// NewWorker creates new api worker
|
||||
func NewWorker(db *db.RocksDB, chain bchain.BlockChain, mempool bchain.Mempool, txCache *db.TxCache, metrics *common.Metrics, is *common.InternalState) (*Worker, error) {
|
||||
w := &Worker{
|
||||
db: db,
|
||||
txCache: txCache,
|
||||
chain: chain,
|
||||
chainParser: chain.GetChainParser(),
|
||||
chainType: chain.GetChainParser().GetChainType(),
|
||||
mempool: mempool,
|
||||
is: is,
|
||||
metrics: metrics,
|
||||
db: db,
|
||||
txCache: txCache,
|
||||
chain: chain,
|
||||
chainParser: chain.GetChainParser(),
|
||||
chainType: chain.GetChainParser().GetChainType(),
|
||||
useAddressAliases: chain.GetChainParser().UseAddressAliases(),
|
||||
mempool: mempool,
|
||||
is: is,
|
||||
metrics: metrics,
|
||||
}
|
||||
if w.chainType == bchain.ChainBitcoinType {
|
||||
w.initXpubCache()
|
||||
@ -100,7 +102,7 @@ func (w *Worker) setSpendingTxToVout(vout *Vout, txid string, height uint32) err
|
||||
// GetSpendingTxid returns transaction id of transaction that spent given output
|
||||
func (w *Worker) GetSpendingTxid(txid string, n int) (string, error) {
|
||||
start := time.Now()
|
||||
tx, err := w.GetTransaction(txid, false, false)
|
||||
tx, err := w.getTransaction(txid, false, false, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@ -115,8 +117,65 @@ func (w *Worker) GetSpendingTxid(txid string, n int) (string, error) {
|
||||
return tx.Vout[n].SpentTxID, nil
|
||||
}
|
||||
|
||||
func aggregateAddress(m map[string]struct{}, a string) {
|
||||
if m != nil && len(a) > 0 {
|
||||
m[a] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
func aggregateAddresses(m map[string]struct{}, addresses []string, isAddress bool) {
|
||||
if m != nil && isAddress {
|
||||
for _, a := range addresses {
|
||||
if len(a) > 0 {
|
||||
m[a] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Worker) newAddressesMapForAliases() map[string]struct{} {
|
||||
if w.useAddressAliases {
|
||||
return make(map[string]struct{})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Worker) getAddressAliases(addresses map[string]struct{}) AddressAliasesMap {
|
||||
if len(addresses) > 0 {
|
||||
aliases := make(AddressAliasesMap)
|
||||
var t string
|
||||
if w.chainType == bchain.ChainEthereumType {
|
||||
t = "ENS"
|
||||
} else {
|
||||
t = "Alias"
|
||||
}
|
||||
for a := range addresses {
|
||||
if w.chainType == bchain.ChainEthereumType {
|
||||
// TODO get contract name
|
||||
}
|
||||
n := w.db.GetAddressAlias(a)
|
||||
if len(n) > 0 {
|
||||
aliases[a] = AddressAlias{Type: t, Alias: n}
|
||||
}
|
||||
}
|
||||
return aliases
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetTransaction reads transaction data from txid
|
||||
func (w *Worker) GetTransaction(txid string, spendingTxs bool, specificJSON bool) (*Tx, error) {
|
||||
addresses := w.newAddressesMapForAliases()
|
||||
tx, err := w.getTransaction(txid, spendingTxs, specificJSON, addresses)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tx.AddressAliases = w.getAddressAliases(addresses)
|
||||
return tx, nil
|
||||
}
|
||||
|
||||
// getTransaction reads transaction data from txid
|
||||
func (w *Worker) getTransaction(txid string, spendingTxs bool, specificJSON bool, addresses map[string]struct{}) (*Tx, error) {
|
||||
bchainTx, height, err := w.txCache.GetTransaction(txid)
|
||||
if err != nil {
|
||||
if err == bchain.ErrTxNotFound {
|
||||
@ -124,7 +183,7 @@ func (w *Worker) GetTransaction(txid string, spendingTxs bool, specificJSON bool
|
||||
}
|
||||
return nil, NewAPIError(fmt.Sprintf("Transaction '%v' not found (%v)", txid, err), true)
|
||||
}
|
||||
return w.GetTransactionFromBchainTx(bchainTx, height, spendingTxs, specificJSON)
|
||||
return w.getTransactionFromBchainTx(bchainTx, height, spendingTxs, specificJSON, addresses)
|
||||
}
|
||||
|
||||
func (w *Worker) getParsedEthereumInputData(data string) *bchain.EthereumParsedInputData {
|
||||
@ -144,8 +203,8 @@ func (w *Worker) getParsedEthereumInputData(data string) *bchain.EthereumParsedI
|
||||
return eth.ParseInputData(signatures, data)
|
||||
}
|
||||
|
||||
// GetTransactionFromBchainTx reads transaction data from txid
|
||||
func (w *Worker) GetTransactionFromBchainTx(bchainTx *bchain.Tx, height int, spendingTxs bool, specificJSON bool) (*Tx, error) {
|
||||
// getTransactionFromBchainTx reads transaction data from txid
|
||||
func (w *Worker) getTransactionFromBchainTx(bchainTx *bchain.Tx, height int, spendingTxs bool, specificJSON bool, addresses map[string]struct{}) (*Tx, error) {
|
||||
var err error
|
||||
var ta *db.TxAddresses
|
||||
var tokens []TokenTransfer
|
||||
@ -199,6 +258,7 @@ func (w *Worker) GetTransactionFromBchainTx(bchainTx *bchain.Tx, height int, spe
|
||||
if err != nil {
|
||||
glog.Warning("GetAddressesFromAddrDesc tx ", bchainVin.Txid, ", addrDesc ", vin.AddrDesc, ": ", err)
|
||||
}
|
||||
aggregateAddresses(addresses, vin.Addresses, vin.IsAddress)
|
||||
continue
|
||||
}
|
||||
return nil, errors.Annotatef(err, "txCache.GetTransaction %v", bchainVin.Txid)
|
||||
@ -215,6 +275,7 @@ func (w *Worker) GetTransactionFromBchainTx(bchainTx *bchain.Tx, height int, spe
|
||||
if err != nil {
|
||||
glog.Errorf("getAddressesFromVout error %v, vout %+v", err, vout)
|
||||
}
|
||||
aggregateAddresses(addresses, vin.Addresses, vin.IsAddress)
|
||||
}
|
||||
} else {
|
||||
if len(tas.Outputs) > int(vin.Vout) {
|
||||
@ -225,6 +286,7 @@ func (w *Worker) GetTransactionFromBchainTx(bchainTx *bchain.Tx, height int, spe
|
||||
if err != nil {
|
||||
glog.Errorf("output.Addresses error %v, tx %v, output %v", err, bchainVin.Txid, i)
|
||||
}
|
||||
aggregateAddresses(addresses, vin.Addresses, vin.IsAddress)
|
||||
}
|
||||
}
|
||||
if vin.ValueSat != nil {
|
||||
@ -239,6 +301,7 @@ func (w *Worker) GetTransactionFromBchainTx(bchainTx *bchain.Tx, height int, spe
|
||||
}
|
||||
vin.Addresses = bchainVin.Addresses
|
||||
vin.IsAddress = true
|
||||
aggregateAddresses(addresses, vin.Addresses, vin.IsAddress)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -254,6 +317,7 @@ func (w *Worker) GetTransactionFromBchainTx(bchainTx *bchain.Tx, height int, spe
|
||||
if err != nil {
|
||||
glog.V(2).Infof("getAddressesFromVout error %v, %v, output %v", err, bchainTx.Txid, bchainVout.N)
|
||||
}
|
||||
aggregateAddresses(addresses, vout.Addresses, vout.IsAddress)
|
||||
if ta != nil {
|
||||
vout.Spent = ta.Outputs[i].Spent
|
||||
if spendingTxs && vout.Spent {
|
||||
@ -276,7 +340,7 @@ func (w *Worker) GetTransactionFromBchainTx(bchainTx *bchain.Tx, height int, spe
|
||||
if err != nil {
|
||||
glog.Errorf("GetTokenTransfersFromTx error %v, %v", err, bchainTx)
|
||||
}
|
||||
tokens = w.getEthereumTokensTransfers(tokenTransfers)
|
||||
tokens = w.getEthereumTokensTransfers(tokenTransfers, addresses)
|
||||
ethTxData := eth.GetEthereumTxData(bchainTx)
|
||||
|
||||
var internalData *bchain.EthereumInternalData
|
||||
@ -314,7 +378,9 @@ func (w *Worker) GetTransactionFromBchainTx(bchainTx *bchain.Tx, height int, spe
|
||||
f := &internalData.Transfers[i]
|
||||
t := ðSpecific.InternalTransfers[i]
|
||||
t.From = f.From
|
||||
aggregateAddress(addresses, t.From)
|
||||
t.To = f.To
|
||||
aggregateAddress(addresses, t.To)
|
||||
t.Type = f.Type
|
||||
t.Value = (*Amount)(&f.Value)
|
||||
}
|
||||
@ -365,6 +431,7 @@ func (w *Worker) GetTransactionFromMempoolTx(mempoolTx *bchain.MempoolTx) (*Tx,
|
||||
var pValInSat *big.Int
|
||||
var tokens []TokenTransfer
|
||||
var ethSpecific *EthereumSpecific
|
||||
addresses := w.newAddressesMapForAliases()
|
||||
vins := make([]Vin, len(mempoolTx.Vin))
|
||||
rbf := false
|
||||
for i := range mempoolTx.Vin {
|
||||
@ -389,6 +456,7 @@ func (w *Worker) GetTransactionFromMempoolTx(mempoolTx *bchain.MempoolTx) (*Tx,
|
||||
if vin.ValueSat != nil {
|
||||
valInSat.Add(&valInSat, (*big.Int)(vin.ValueSat))
|
||||
}
|
||||
aggregateAddresses(addresses, vin.Addresses, vin.IsAddress)
|
||||
}
|
||||
} else if w.chainType == bchain.ChainEthereumType {
|
||||
if len(bchainVin.Addresses) > 0 {
|
||||
@ -398,6 +466,7 @@ func (w *Worker) GetTransactionFromMempoolTx(mempoolTx *bchain.MempoolTx) (*Tx,
|
||||
}
|
||||
vin.Addresses = bchainVin.Addresses
|
||||
vin.IsAddress = true
|
||||
aggregateAddresses(addresses, vin.Addresses, vin.IsAddress)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -413,6 +482,7 @@ func (w *Worker) GetTransactionFromMempoolTx(mempoolTx *bchain.MempoolTx) (*Tx,
|
||||
if err != nil {
|
||||
glog.V(2).Infof("getAddressesFromVout error %v, %v, output %v", err, mempoolTx.Txid, bchainVout.N)
|
||||
}
|
||||
aggregateAddresses(addresses, vout.Addresses, vout.IsAddress)
|
||||
}
|
||||
if w.chainType == bchain.ChainBitcoinType {
|
||||
// for coinbase transactions valIn is 0
|
||||
@ -425,7 +495,7 @@ func (w *Worker) GetTransactionFromMempoolTx(mempoolTx *bchain.MempoolTx) (*Tx,
|
||||
if len(mempoolTx.Vout) > 0 {
|
||||
valOutSat = mempoolTx.Vout[0].ValueSat
|
||||
}
|
||||
tokens = w.getEthereumTokensTransfers(mempoolTx.TokenTransfers)
|
||||
tokens = w.getEthereumTokensTransfers(mempoolTx.TokenTransfers, addresses)
|
||||
ethTxData := eth.GetEthereumTxDataFromSpecificData(mempoolTx.CoinSpecificData)
|
||||
ethSpecific = &EthereumSpecific{
|
||||
GasLimit: ethTxData.GasLimit,
|
||||
@ -450,11 +520,12 @@ func (w *Worker) GetTransactionFromMempoolTx(mempoolTx *bchain.MempoolTx) (*Tx,
|
||||
Vout: vouts,
|
||||
TokenTransfers: tokens,
|
||||
EthereumSpecific: ethSpecific,
|
||||
AddressAliases: w.getAddressAliases(addresses),
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (w *Worker) getEthereumTokensTransfers(transfers bchain.TokenTransfers) []TokenTransfer {
|
||||
func (w *Worker) getEthereumTokensTransfers(transfers bchain.TokenTransfers, addresses map[string]struct{}) []TokenTransfer {
|
||||
sort.Sort(transfers)
|
||||
tokens := make([]TokenTransfer, len(transfers))
|
||||
for i := range transfers {
|
||||
@ -482,6 +553,8 @@ func (w *Worker) getEthereumTokensTransfers(transfers bchain.TokenTransfers) []T
|
||||
} else {
|
||||
value = (*Amount)(&t.Value)
|
||||
}
|
||||
aggregateAddress(addresses, t.From)
|
||||
aggregateAddress(addresses, t.To)
|
||||
tokens[i] = TokenTransfer{
|
||||
Type: TokenTypeMap[t.Type],
|
||||
Token: t.Contract,
|
||||
@ -606,7 +679,7 @@ func GetUniqueTxids(txids []string) []string {
|
||||
return ut[0:i]
|
||||
}
|
||||
|
||||
func (w *Worker) txFromTxAddress(txid string, ta *db.TxAddresses, bi *db.BlockInfo, bestheight uint32) *Tx {
|
||||
func (w *Worker) txFromTxAddress(txid string, ta *db.TxAddresses, bi *db.BlockInfo, bestheight uint32, addresses map[string]struct{}) *Tx {
|
||||
var err error
|
||||
var valInSat, valOutSat, feesSat big.Int
|
||||
vins := make([]Vin, len(ta.Inputs))
|
||||
@ -620,6 +693,7 @@ func (w *Worker) txFromTxAddress(txid string, ta *db.TxAddresses, bi *db.BlockIn
|
||||
if err != nil {
|
||||
glog.Errorf("tai.Addresses error %v, tx %v, input %v, tai %+v", err, txid, i, tai)
|
||||
}
|
||||
aggregateAddresses(addresses, vin.Addresses, vin.IsAddress)
|
||||
}
|
||||
vouts := make([]Vout, len(ta.Outputs))
|
||||
for i := range ta.Outputs {
|
||||
@ -633,6 +707,7 @@ func (w *Worker) txFromTxAddress(txid string, ta *db.TxAddresses, bi *db.BlockIn
|
||||
glog.Errorf("tai.Addresses error %v, tx %v, output %v, tao %+v", err, txid, i, tao)
|
||||
}
|
||||
vout.Spent = tao.Spent
|
||||
aggregateAddresses(addresses, vout.Addresses, vout.IsAddress)
|
||||
}
|
||||
// for coinbase transactions valIn is 0
|
||||
feesSat.Sub(&valInSat, &valOutSat)
|
||||
@ -876,7 +951,7 @@ func (w *Worker) getEthereumTypeAddressBalances(addrDesc bchain.AddressDescripto
|
||||
return ba, tokens, ci, n, nonContractTxs, internalTxs, totalResults, nil
|
||||
}
|
||||
|
||||
func (w *Worker) txFromTxid(txid string, bestheight uint32, option AccountDetails, blockInfo *db.BlockInfo) (*Tx, error) {
|
||||
func (w *Worker) txFromTxid(txid string, bestheight uint32, option AccountDetails, blockInfo *db.BlockInfo, addresses map[string]struct{}) (*Tx, error) {
|
||||
var tx *Tx
|
||||
var err error
|
||||
// only ChainBitcoinType supports TxHistoryLight
|
||||
@ -888,9 +963,9 @@ func (w *Worker) txFromTxid(txid string, bestheight uint32, option AccountDetail
|
||||
if ta == nil {
|
||||
glog.Warning("DB inconsistency: tx ", txid, ": not found in txAddresses")
|
||||
// as fallback, get tx from backend
|
||||
tx, err = w.GetTransaction(txid, false, false)
|
||||
tx, err = w.getTransaction(txid, false, false, addresses)
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "GetTransaction %v", txid)
|
||||
return nil, errors.Annotatef(err, "getTransaction %v", txid)
|
||||
}
|
||||
} else {
|
||||
if blockInfo == nil {
|
||||
@ -904,12 +979,12 @@ func (w *Worker) txFromTxid(txid string, bestheight uint32, option AccountDetail
|
||||
blockInfo = &db.BlockInfo{}
|
||||
}
|
||||
}
|
||||
tx = w.txFromTxAddress(txid, ta, blockInfo, bestheight)
|
||||
tx = w.txFromTxAddress(txid, ta, blockInfo, bestheight, addresses)
|
||||
}
|
||||
} else {
|
||||
tx, err = w.GetTransaction(txid, false, false)
|
||||
tx, err = w.getTransaction(txid, false, false, addresses)
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "GetTransaction %v", txid)
|
||||
return nil, errors.Annotatef(err, "getTransaction %v", txid)
|
||||
}
|
||||
}
|
||||
return tx, nil
|
||||
@ -1012,6 +1087,7 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, option Acco
|
||||
ba = &db.AddrBalance{}
|
||||
page = 0
|
||||
}
|
||||
addresses := w.newAddressesMapForAliases()
|
||||
// process mempool, only if toHeight is not specified
|
||||
if filter.ToHeight == 0 && !filter.OnlyConfirmed {
|
||||
txm, err = w.getAddressTxids(addrDesc, true, filter, maxInt)
|
||||
@ -1019,7 +1095,7 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, option Acco
|
||||
return nil, errors.Annotatef(err, "getAddressTxids %v true", addrDesc)
|
||||
}
|
||||
for _, txid := range txm {
|
||||
tx, err := w.GetTransaction(txid, false, true)
|
||||
tx, err := w.getTransaction(txid, false, true, addresses)
|
||||
// mempool transaction may fail
|
||||
if err != nil || tx == nil {
|
||||
glog.Warning("GetTransaction in mempool: ", err)
|
||||
@ -1070,7 +1146,7 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, option Acco
|
||||
if option == AccountDetailsTxidHistory {
|
||||
txids = append(txids, txid)
|
||||
} else {
|
||||
tx, err := w.txFromTxid(txid, bestheight, option, nil)
|
||||
tx, err := w.txFromTxid(txid, bestheight, option, nil, addresses)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -1099,6 +1175,7 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, option Acco
|
||||
Tokens: tokens,
|
||||
Erc20Contract: erc20c,
|
||||
Nonce: nonce,
|
||||
AddressAliases: w.getAddressAliases(addresses),
|
||||
}
|
||||
glog.Info("GetAddress ", address, ", ", time.Since(start))
|
||||
return r, nil
|
||||
@ -1786,8 +1863,9 @@ func (w *Worker) GetBlock(bid string, page int, txsOnPage int) (*Block, error) {
|
||||
pg, from, to, page := computePaging(txCount, page, txsOnPage)
|
||||
txs := make([]*Tx, to-from)
|
||||
txi := 0
|
||||
addresses := w.newAddressesMapForAliases()
|
||||
for i := from; i < to; i++ {
|
||||
txs[txi], err = w.txFromTxid(bi.Txids[i], bestheight, AccountDetailsTxHistoryLight, dbi)
|
||||
txs[txi], err = w.txFromTxid(bi.Txids[i], bestheight, AccountDetailsTxHistoryLight, dbi, addresses)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -1819,8 +1897,9 @@ func (w *Worker) GetBlock(bid string, page int, txsOnPage int) (*Block, error) {
|
||||
Txids: bi.Txids,
|
||||
Version: bi.Version,
|
||||
},
|
||||
TxCount: txCount,
|
||||
Transactions: txs,
|
||||
TxCount: txCount,
|
||||
Transactions: txs,
|
||||
AddressAliases: w.getAddressAliases(addresses),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -1875,7 +1954,7 @@ func (w *Worker) ComputeFeeStats(blockFrom, blockTo int, stopCompute chan os.Sig
|
||||
glog.Info("ComputeFeeStats interrupted at height ", block)
|
||||
return db.ErrOperationInterrupted
|
||||
default:
|
||||
tx, err := w.txFromTxid(txid, bestheight, AccountDetailsTxHistoryLight, dbi)
|
||||
tx, err := w.txFromTxid(txid, bestheight, AccountDetailsTxHistoryLight, dbi, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -437,6 +437,7 @@ func (w *Worker) GetXpubAddress(xpub string, page int, txsOnPage int, option Acc
|
||||
}
|
||||
filtered = true
|
||||
}
|
||||
addresses := w.newAddressesMapForAliases()
|
||||
// process mempool, only if ToHeight is not specified
|
||||
if filter.ToHeight == 0 && !filter.OnlyConfirmed {
|
||||
txmMap = make(map[string]*Tx)
|
||||
@ -452,7 +453,7 @@ func (w *Worker) GetXpubAddress(xpub string, page int, txsOnPage int, option Acc
|
||||
// the same tx can have multiple addresses from the same xpub, get it from backend it only once
|
||||
tx, foundTx := txmMap[txid.txid]
|
||||
if !foundTx {
|
||||
tx, err = w.GetTransaction(txid.txid, false, true)
|
||||
tx, err = w.getTransaction(txid.txid, false, true, addresses)
|
||||
// mempool transaction may fail
|
||||
if err != nil || tx == nil {
|
||||
glog.Warning("GetTransaction in mempool: ", err)
|
||||
@ -529,7 +530,7 @@ func (w *Worker) GetXpubAddress(xpub string, page int, txsOnPage int, option Acc
|
||||
if option == AccountDetailsTxidHistory {
|
||||
txids = append(txids, xpubTxid.txid)
|
||||
} else {
|
||||
tx, err := w.txFromTxid(xpubTxid.txid, bestheight, option, nil)
|
||||
tx, err := w.txFromTxid(xpubTxid.txid, bestheight, option, nil, addresses)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -580,6 +581,7 @@ func (w *Worker) GetXpubAddress(xpub string, page int, txsOnPage int, option Acc
|
||||
UsedTokens: usedTokens,
|
||||
Tokens: tokens,
|
||||
XPubAddresses: xpubAddresses,
|
||||
AddressAliases: w.getAddressAliases(addresses),
|
||||
}
|
||||
glog.Info("GetXpubAddress ", xpub[:xpubLogPrefix], ", cache ", inCache, ", ", txCount, " txs, ", time.Since(start))
|
||||
return &addr, nil
|
||||
|
||||
@ -16,6 +16,7 @@ import (
|
||||
type BaseParser struct {
|
||||
BlockAddressesToKeep int
|
||||
AmountDecimalPoint int
|
||||
AddressAliases bool
|
||||
}
|
||||
|
||||
// ParseBlock parses raw block to our Block struct - currently not implemented
|
||||
@ -103,6 +104,11 @@ func (p *BaseParser) AmountDecimals() int {
|
||||
return p.AmountDecimalPoint
|
||||
}
|
||||
|
||||
// UseAddressAliases returns true if address aliases are enabled
|
||||
func (p *BaseParser) UseAddressAliases() bool {
|
||||
return p.AddressAliases
|
||||
}
|
||||
|
||||
// ParseTxFromJson parses JSON message containing transaction and returns Tx struct
|
||||
func (p *BaseParser) ParseTxFromJson(msg json.RawMessage) (*Tx, error) {
|
||||
var tx Tx
|
||||
@ -304,3 +310,8 @@ func (p *BaseParser) DeriveAddressDescriptorsFromTo(descriptor *XpubDescriptor,
|
||||
func (p *BaseParser) EthereumTypeGetTokenTransfersFromTx(tx *Tx) (TokenTransfers, error) {
|
||||
return nil, errors.New("Not supported")
|
||||
}
|
||||
|
||||
// FormatAddressAlias makes possible to do coin specific formatting to an address alias
|
||||
func (p *BaseParser) FormatAddressAlias(address string, name string) string {
|
||||
return name
|
||||
}
|
||||
|
||||
@ -44,6 +44,7 @@ func NewBitcoinLikeParser(params *chaincfg.Params, c *Configuration) *BitcoinLik
|
||||
BaseParser: &bchain.BaseParser{
|
||||
BlockAddressesToKeep: c.BlockAddressesToKeep,
|
||||
AmountDecimalPoint: 8,
|
||||
AddressAliases: c.AddressAliases,
|
||||
},
|
||||
Params: params,
|
||||
XPubMagic: c.XPubMagic,
|
||||
|
||||
@ -43,6 +43,7 @@ type Configuration struct {
|
||||
RPCUser string `json:"rpc_user"`
|
||||
RPCPass string `json:"rpc_pass"`
|
||||
RPCTimeout int `json:"rpc_timeout"`
|
||||
AddressAliases bool `json:"address_aliases,omitempty"`
|
||||
Parse bool `json:"parse"`
|
||||
MessageQueueBinding string `json:"message_queue_binding"`
|
||||
Subversion string `json:"subversion"`
|
||||
|
||||
@ -228,7 +228,7 @@ func Test_contractGetTransfersFromLog(t *testing.T) {
|
||||
}
|
||||
|
||||
func Test_contractGetTransfersFromTx(t *testing.T) {
|
||||
p := NewEthereumParser(1)
|
||||
p := NewEthereumParser(1, false)
|
||||
b1 := dbtestdata.GetTestEthereumTypeBlock1(p)
|
||||
b2 := dbtestdata.GetTestEthereumTypeBlock2(p)
|
||||
bn, _ := new(big.Int).SetString("21e19e0c9bab2400000", 16)
|
||||
|
||||
@ -28,10 +28,11 @@ type EthereumParser struct {
|
||||
}
|
||||
|
||||
// NewEthereumParser returns new EthereumParser instance
|
||||
func NewEthereumParser(b int) *EthereumParser {
|
||||
func NewEthereumParser(b int, addressAliases bool) *EthereumParser {
|
||||
return &EthereumParser{&bchain.BaseParser{
|
||||
BlockAddressesToKeep: b,
|
||||
AmountDecimalPoint: EtherAmountDecimalPoint,
|
||||
AddressAliases: addressAliases,
|
||||
}}
|
||||
}
|
||||
|
||||
@ -458,6 +459,11 @@ func (p *EthereumParser) EthereumTypeGetTokenTransfersFromTx(tx *bchain.Tx) (bch
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// FormatAddressAlias adds .eth to a name alias
|
||||
func (p *EthereumParser) FormatAddressAlias(address string, name string) string {
|
||||
return name + ".eth"
|
||||
}
|
||||
|
||||
// TxStatus is status of transaction
|
||||
type TxStatus int
|
||||
|
||||
|
||||
@ -54,7 +54,7 @@ func TestEthParser_GetAddrDescFromAddress(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
p := NewEthereumParser(1)
|
||||
p := NewEthereumParser(1, false)
|
||||
got, err := p.GetAddrDescFromAddress(tt.args.address)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("EthParser.GetAddrDescFromAddress() error = %v, wantErr %v", err, tt.wantErr)
|
||||
@ -285,7 +285,7 @@ func TestEthereumParser_PackTx(t *testing.T) {
|
||||
want: dbtestdata.EthTx1NoStatusPacked,
|
||||
},
|
||||
}
|
||||
p := NewEthereumParser(1)
|
||||
p := NewEthereumParser(1, false)
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := p.PackTx(tt.args.tx, tt.args.height, tt.args.blockTime)
|
||||
@ -338,7 +338,7 @@ func TestEthereumParser_UnpackTx(t *testing.T) {
|
||||
want1: 4321000,
|
||||
},
|
||||
}
|
||||
p := NewEthereumParser(1)
|
||||
p := NewEthereumParser(1, false)
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
b, err := hex.DecodeString(tt.args.hex)
|
||||
|
||||
@ -41,6 +41,7 @@ type Configuration struct {
|
||||
RPCURL string `json:"rpc_url"`
|
||||
RPCTimeout int `json:"rpc_timeout"`
|
||||
BlockAddressesToKeep int `json:"block_addresses_to_keep"`
|
||||
AddressAliases bool `json:"address_aliases,omitempty"`
|
||||
MempoolTxTimeoutHours int `json:"mempoolTxTimeoutHours"`
|
||||
QueryBackendOnMempoolResync bool `json:"queryBackendOnMempoolResync"`
|
||||
ProcessInternalTransactions bool `json:"processInternalTransactions"`
|
||||
@ -97,7 +98,7 @@ func NewEthereumRPC(config json.RawMessage, pushHandler func(bchain.Notification
|
||||
ProcessInternalTransactions = c.ProcessInternalTransactions
|
||||
|
||||
// always create parser
|
||||
s.Parser = NewEthereumParser(c.BlockAddressesToKeep)
|
||||
s.Parser = NewEthereumParser(c.BlockAddressesToKeep, c.AddressAliases)
|
||||
s.timeout = time.Duration(c.RPCTimeout) * time.Second
|
||||
|
||||
// new blocks notifications handling
|
||||
@ -648,6 +649,7 @@ func (b *EthereumRPC) GetBlock(hash string, height uint32) (*bchain.Block, error
|
||||
// error fetching internal data does not stop the block processing
|
||||
var blockSpecificData *bchain.EthereumBlockSpecificData
|
||||
internalData, err := b.getInternalDataForBlock(head.Hash, body.Transactions)
|
||||
// pass internalData error and ENS records in blockSpecificData to be stored
|
||||
if err != nil || len(ens) > 0 {
|
||||
blockSpecificData = &bchain.EthereumBlockSpecificData{}
|
||||
if err != nil {
|
||||
|
||||
@ -305,6 +305,8 @@ type BlockChainParser interface {
|
||||
KeepBlockAddresses() int
|
||||
// AmountDecimals returns number of decimal places in coin amounts
|
||||
AmountDecimals() int
|
||||
// UseAddressAliases returns true if address aliases are enabled
|
||||
UseAddressAliases() bool
|
||||
// MinimumCoinbaseConfirmations returns minimum number of confirmations a coinbase transaction must have before it can be spent
|
||||
MinimumCoinbaseConfirmations() int
|
||||
// AmountToDecimalString converts amount in big.Int to string with decimal point in the correct place
|
||||
@ -338,6 +340,8 @@ type BlockChainParser interface {
|
||||
DeriveAddressDescriptorsFromTo(descriptor *XpubDescriptor, change uint32, fromIndex uint32, toIndex uint32) ([]AddressDescriptor, error)
|
||||
// EthereumType specific
|
||||
EthereumTypeGetTokenTransfersFromTx(tx *Tx) (TokenTransfers, error)
|
||||
// AddressAlias
|
||||
FormatAddressAlias(address string, name string) string
|
||||
}
|
||||
|
||||
// Mempool defines common interface to mempool
|
||||
|
||||
@ -50,6 +50,7 @@
|
||||
"mempool_sub_workers": 2,
|
||||
"block_addresses_to_keep": 300,
|
||||
"additional_params": {
|
||||
"address_aliases": true,
|
||||
"mempoolTxTimeoutHours": 48,
|
||||
"processInternalTransactions": true,
|
||||
"queryBackendOnMempoolResync": false,
|
||||
|
||||
@ -49,6 +49,7 @@
|
||||
"mempool_sub_workers": 2,
|
||||
"block_addresses_to_keep": 3000,
|
||||
"additional_params": {
|
||||
"address_aliases": true,
|
||||
"mempoolTxTimeoutHours": 12,
|
||||
"processInternalTransactions": true,
|
||||
"queryBackendOnMempoolResync": false,
|
||||
|
||||
@ -330,9 +330,9 @@ func (b *BulkConnect) connectBlockEthereumType(block *bchain.Block, storeBlockTx
|
||||
glog.Info("rocksdb: height ", b.height, ", stored ", bac, " addresses, done in ", time.Since(start))
|
||||
}
|
||||
} else {
|
||||
// if there is InternalDataError, store it
|
||||
// if there are blockSpecificData, store them
|
||||
blockSpecificData, _ := block.CoinSpecificData.(*bchain.EthereumBlockSpecificData)
|
||||
if blockSpecificData != nil && blockSpecificData.InternalDataError != "" {
|
||||
if blockSpecificData != nil {
|
||||
wb := gorocksdb.NewWriteBatch()
|
||||
defer wb.Destroy()
|
||||
if err = b.d.storeBlockSpecificDataEthereumType(wb, block); err != nil {
|
||||
|
||||
@ -10,6 +10,7 @@ import (
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
@ -90,6 +91,9 @@ const (
|
||||
cfContracts
|
||||
cfFunctionSignatures
|
||||
cfBlockInternalDataErrors
|
||||
|
||||
// TODO move to common section
|
||||
cfAddressAliases
|
||||
)
|
||||
|
||||
// common columns
|
||||
@ -98,7 +102,7 @@ var cfBaseNames = []string{"default", "height", "addresses", "blockTxs", "transa
|
||||
|
||||
// type specific columns
|
||||
var cfNamesBitcoinType = []string{"addressBalance", "txAddresses"}
|
||||
var cfNamesEthereumType = []string{"addressContracts", "internalData", "contracts", "functionSignatures", "blockInternalDataErrors"}
|
||||
var cfNamesEthereumType = []string{"addressContracts", "internalData", "contracts", "functionSignatures", "blockInternalDataErrors", "addressAliases"}
|
||||
|
||||
func openDB(path string, c *gorocksdb.Cache, openFiles int) (*gorocksdb.DB, []*gorocksdb.ColumnFamilyHandle, error) {
|
||||
// opts with bloom filter
|
||||
@ -1248,6 +1252,50 @@ func (d *RocksDB) writeHeight(wb *gorocksdb.WriteBatch, height uint32, bi *Block
|
||||
return nil
|
||||
}
|
||||
|
||||
// address alias support
|
||||
var cachedAddressAliasRecords = make(map[string]string)
|
||||
var cachedAddressAliasRecordsMux sync.Mutex
|
||||
|
||||
// InitAddressAliasRecords loads all records to cache
|
||||
func (d *RocksDB) InitAddressAliasRecords() (int, error) {
|
||||
count := 0
|
||||
cachedAddressAliasRecordsMux.Lock()
|
||||
defer cachedAddressAliasRecordsMux.Unlock()
|
||||
it := d.db.NewIteratorCF(d.ro, d.cfh[cfAddressAliases])
|
||||
defer it.Close()
|
||||
for it.SeekToFirst(); it.Valid(); it.Next() {
|
||||
address := string(it.Key().Data())
|
||||
name := string(it.Value().Data())
|
||||
if address != "" && name != "" {
|
||||
cachedAddressAliasRecords[address] = d.chainParser.FormatAddressAlias(address, name)
|
||||
count++
|
||||
}
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (d *RocksDB) GetAddressAlias(address string) string {
|
||||
cachedAddressAliasRecordsMux.Lock()
|
||||
name := cachedAddressAliasRecords[address]
|
||||
cachedAddressAliasRecordsMux.Unlock()
|
||||
return name
|
||||
}
|
||||
|
||||
func (d *RocksDB) storeAddressAliasRecords(wb *gorocksdb.WriteBatch, records []bchain.AddressAliasRecord) error {
|
||||
if d.chainParser.UseAddressAliases() {
|
||||
for i := range records {
|
||||
r := &records[i]
|
||||
if len(r.Name) > 0 {
|
||||
wb.PutCF(d.cfh[cfAddressAliases], []byte(r.Address), []byte(r.Name))
|
||||
cachedAddressAliasRecordsMux.Lock()
|
||||
cachedAddressAliasRecords[r.Address] = d.chainParser.FormatAddressAlias(r.Address, r.Name)
|
||||
cachedAddressAliasRecordsMux.Unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Disconnect blocks
|
||||
|
||||
func (d *RocksDB) disconnectTxAddressesInputs(wb *gorocksdb.WriteBatch, btxID []byte, inputs []outpoint, txa *TxAddresses, txAddressesToUpdate map[string]*TxAddresses,
|
||||
@ -1642,6 +1690,15 @@ func (d *RocksDB) LoadInternalState(rpcCoin string) (*common.InternalState, erro
|
||||
var t time.Time
|
||||
is.LastMempoolSync = t
|
||||
is.SyncMode = false
|
||||
|
||||
if d.chainParser.UseAddressAliases() {
|
||||
recordsCount, err := d.InitAddressAliasRecords()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
glog.Infof("loaded %d address alias records", recordsCount)
|
||||
}
|
||||
|
||||
return is, nil
|
||||
}
|
||||
|
||||
|
||||
@ -761,10 +761,17 @@ func (d *RocksDB) storeBlockInternalDataErrorEthereumType(wb *gorocksdb.WriteBat
|
||||
|
||||
func (d *RocksDB) storeBlockSpecificDataEthereumType(wb *gorocksdb.WriteBatch, block *bchain.Block) error {
|
||||
blockSpecificData, _ := block.CoinSpecificData.(*bchain.EthereumBlockSpecificData)
|
||||
if blockSpecificData != nil && blockSpecificData.InternalDataError != "" {
|
||||
glog.Info("storeBlockSpecificDataEthereumType ", block.Height, ": ", blockSpecificData.InternalDataError)
|
||||
if err := d.storeBlockInternalDataErrorEthereumType(wb, block, blockSpecificData.InternalDataError); err != nil {
|
||||
return err
|
||||
if blockSpecificData != nil {
|
||||
if blockSpecificData.InternalDataError != "" {
|
||||
glog.Info("storeBlockSpecificDataEthereumType ", block.Height, ": ", blockSpecificData.InternalDataError)
|
||||
if err := d.storeBlockInternalDataErrorEthereumType(wb, block, blockSpecificData.InternalDataError); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if len(blockSpecificData.AddressAliasRecords) > 0 {
|
||||
if err := d.storeAddressAliasRecords(wb, blockSpecificData.AddressAliasRecords); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
@ -21,7 +21,7 @@ type testEthereumParser struct {
|
||||
}
|
||||
|
||||
func ethereumTestnetParser() *eth.EthereumParser {
|
||||
return eth.NewEthereumParser(1)
|
||||
return eth.NewEthereumParser(1, true)
|
||||
}
|
||||
|
||||
func bigintFromStringToHex(s string) string {
|
||||
@ -267,6 +267,25 @@ func verifyAfterEthereumTypeBlock2(t *testing.T, d *RocksDB, wantBlockInternalDa
|
||||
}
|
||||
}
|
||||
|
||||
var addressAliases []keyPair
|
||||
addressAliases = []keyPair{
|
||||
{
|
||||
hex.EncodeToString([]byte(dbtestdata.EthAddr7bEIP55)),
|
||||
hex.EncodeToString([]byte("address7b")),
|
||||
nil,
|
||||
},
|
||||
{
|
||||
hex.EncodeToString([]byte(dbtestdata.EthAddr20EIP55)),
|
||||
hex.EncodeToString([]byte("address20")),
|
||||
nil,
|
||||
},
|
||||
}
|
||||
if err := checkColumn(d, cfAddressAliases, addressAliases); err != nil {
|
||||
{
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
var internalDataError []keyPair
|
||||
if wantBlockInternalDataError {
|
||||
internalDataError = []keyPair{
|
||||
@ -282,6 +301,7 @@ func verifyAfterEthereumTypeBlock2(t *testing.T, d *RocksDB, wantBlockInternalDa
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func formatInternalData(in *bchain.EthereumInternalData) *bchain.EthereumInternalData {
|
||||
@ -359,9 +379,8 @@ func TestRocksDB_Index_EthereumType(t *testing.T) {
|
||||
t.Fatal("Expecting is.BlockTimes 1, got ", len(d.is.BlockTimes))
|
||||
}
|
||||
|
||||
// connect 2nd block, simulate InternalDataError
|
||||
// connect 2nd block, simulate InternalDataError and AddressAlias
|
||||
block2 := dbtestdata.GetTestEthereumTypeBlock2(d.chainParser)
|
||||
block2.CoinSpecificData = &bchain.EthereumBlockSpecificData{InternalDataError: "test error"}
|
||||
if err := d.ConnectBlock(block2); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -544,7 +563,6 @@ func Test_BulkConnect_EthereumType(t *testing.T) {
|
||||
|
||||
// connect 2nd block, simulate InternalDataError
|
||||
block2 := dbtestdata.GetTestEthereumTypeBlock2(d.chainParser)
|
||||
block2.CoinSpecificData = &bchain.EthereumBlockSpecificData{InternalDataError: "test error"}
|
||||
if err := bc.ConnectBlock(block2, true); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -13,9 +13,11 @@ const (
|
||||
EthAddr3e = "3e3a3d69dc66ba10737f531ed088954a9ec89d97"
|
||||
EthAddr55 = "555ee11fbddc0e49a9bab358a8941ad95ffdb48f"
|
||||
EthAddr20 = "20cd153de35d469ba46127a0c8f18626b59a256a"
|
||||
EthAddr20EIP55 = "0x20cD153de35D469BA46127A0C8F18626b59a256A"
|
||||
EthAddr9f = "9f4981531fda132e83c44680787dfa7ee31e4f8d"
|
||||
EthAddr4b = "4bda106325c335df99eab7fe363cac8a0ba2a24d"
|
||||
EthAddr7b = "7b62eb7fe80350dc7ec945c0b73242cb9877fb1b"
|
||||
EthAddr7bEIP55 = "0x7B62EB7fe80350DC7EC945C0B73242cb9877FB1b"
|
||||
EthAddr83 = "837e3f699d85a4b0b99894567e9233dfb1dcb081"
|
||||
EthAddrA3 = "a3950b823cb063dd9afc0d27f35008b805b3ed53"
|
||||
EthAddr5d = "5dc6288b35e0807a3d6feb89b3a2ff4ab773168e"
|
||||
@ -126,6 +128,20 @@ var EthTx4InternalData = &bchain.EthereumInternalData{
|
||||
},
|
||||
}
|
||||
|
||||
var Block2SpecificData = &bchain.EthereumBlockSpecificData{
|
||||
InternalDataError: "test error",
|
||||
AddressAliasRecords: []bchain.AddressAliasRecord{
|
||||
{
|
||||
Address: EthAddr7bEIP55,
|
||||
Name: "address7b",
|
||||
},
|
||||
{
|
||||
Address: EthAddr20EIP55,
|
||||
Name: "address20",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
type packedAndInternal struct {
|
||||
packed string
|
||||
internal *bchain.EthereumInternalData
|
||||
@ -194,5 +210,6 @@ func GetTestEthereumTypeBlock2(parser bchain.BlockChainParser) *bchain.Block {
|
||||
}, {
|
||||
packed: EthTx8Packed,
|
||||
}}, parser),
|
||||
CoinSpecificData: Block2SpecificData,
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user