Return address aliases from API

This commit is contained in:
Martin Boehm 2022-04-25 00:16:04 +02:00 committed by Martin
parent 77561e3567
commit 8bdc3da694
19 changed files with 276 additions and 60 deletions

View File

@ -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

View File

@ -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 := &ethSpecific.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
}

View File

@ -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

View File

@ -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
}

View File

@ -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,

View File

@ -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"`

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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 {

View File

@ -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

View File

@ -50,6 +50,7 @@
"mempool_sub_workers": 2,
"block_addresses_to_keep": 300,
"additional_params": {
"address_aliases": true,
"mempoolTxTimeoutHours": 48,
"processInternalTransactions": true,
"queryBackendOnMempoolResync": false,

View File

@ -49,6 +49,7 @@
"mempool_sub_workers": 2,
"block_addresses_to_keep": 3000,
"additional_params": {
"address_aliases": true,
"mempoolTxTimeoutHours": 12,
"processInternalTransactions": true,
"queryBackendOnMempoolResync": false,

View File

@ -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 {

View File

@ -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
}

View File

@ -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

View File

@ -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

View File

@ -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,
}
}