From 8bdc3da694558732bbdf4fc9c2445b9a793de90a Mon Sep 17 00:00:00 2001 From: Martin Boehm Date: Mon, 25 Apr 2022 00:16:04 +0200 Subject: [PATCH] Return address aliases from API --- api/types.go | 13 +- api/worker.go | 151 +++++++++++++----- api/xpub.go | 6 +- bchain/baseparser.go | 11 ++ bchain/coins/btc/bitcoinlikeparser.go | 1 + bchain/coins/btc/bitcoinrpc.go | 1 + bchain/coins/eth/contract_test.go | 2 +- bchain/coins/eth/ethparser.go | 8 +- bchain/coins/eth/ethparser_test.go | 6 +- bchain/coins/eth/ethrpc.go | 4 +- bchain/types.go | 4 + configs/coins/ethereum_archive.json | 1 + .../ethereum_testnet_ropsten_archive.json | 1 + db/bulkconnect.go | 4 +- db/rocksdb.go | 59 ++++++- db/rocksdb_ethereumtype.go | 15 +- db/rocksdb_ethereumtype_test.go | 26 ++- server/public_ethereumtype_test.go | 6 +- tests/dbtestdata/dbtestdata_ethereumtype.go | 17 ++ 19 files changed, 276 insertions(+), 60 deletions(-) diff --git a/api/types.go b/api/types.go index 545e58ca..f801d9f4 100644 --- a/api/types.go +++ b/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 diff --git a/api/worker.go b/api/worker.go index a2ec91ee..4445d12c 100644 --- a/api/worker.go +++ b/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 } diff --git a/api/xpub.go b/api/xpub.go index b5af25d3..8737373b 100644 --- a/api/xpub.go +++ b/api/xpub.go @@ -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 diff --git a/bchain/baseparser.go b/bchain/baseparser.go index 3f342a53..e45c8f9e 100644 --- a/bchain/baseparser.go +++ b/bchain/baseparser.go @@ -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 +} diff --git a/bchain/coins/btc/bitcoinlikeparser.go b/bchain/coins/btc/bitcoinlikeparser.go index 3f168239..716877cc 100644 --- a/bchain/coins/btc/bitcoinlikeparser.go +++ b/bchain/coins/btc/bitcoinlikeparser.go @@ -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, diff --git a/bchain/coins/btc/bitcoinrpc.go b/bchain/coins/btc/bitcoinrpc.go index faac2528..b2618a03 100644 --- a/bchain/coins/btc/bitcoinrpc.go +++ b/bchain/coins/btc/bitcoinrpc.go @@ -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"` diff --git a/bchain/coins/eth/contract_test.go b/bchain/coins/eth/contract_test.go index 2efa1eae..7d609a0a 100644 --- a/bchain/coins/eth/contract_test.go +++ b/bchain/coins/eth/contract_test.go @@ -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) diff --git a/bchain/coins/eth/ethparser.go b/bchain/coins/eth/ethparser.go index 92ed0054..975b8374 100644 --- a/bchain/coins/eth/ethparser.go +++ b/bchain/coins/eth/ethparser.go @@ -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 diff --git a/bchain/coins/eth/ethparser_test.go b/bchain/coins/eth/ethparser_test.go index ac54e6b3..aaee177a 100644 --- a/bchain/coins/eth/ethparser_test.go +++ b/bchain/coins/eth/ethparser_test.go @@ -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) diff --git a/bchain/coins/eth/ethrpc.go b/bchain/coins/eth/ethrpc.go index 7d5b6770..092c835b 100644 --- a/bchain/coins/eth/ethrpc.go +++ b/bchain/coins/eth/ethrpc.go @@ -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 { diff --git a/bchain/types.go b/bchain/types.go index d2fe44c4..2f40dfe4 100644 --- a/bchain/types.go +++ b/bchain/types.go @@ -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 diff --git a/configs/coins/ethereum_archive.json b/configs/coins/ethereum_archive.json index 4f1d3cfc..313d800d 100644 --- a/configs/coins/ethereum_archive.json +++ b/configs/coins/ethereum_archive.json @@ -50,6 +50,7 @@ "mempool_sub_workers": 2, "block_addresses_to_keep": 300, "additional_params": { + "address_aliases": true, "mempoolTxTimeoutHours": 48, "processInternalTransactions": true, "queryBackendOnMempoolResync": false, diff --git a/configs/coins/ethereum_testnet_ropsten_archive.json b/configs/coins/ethereum_testnet_ropsten_archive.json index 0e3692bb..35bf48aa 100644 --- a/configs/coins/ethereum_testnet_ropsten_archive.json +++ b/configs/coins/ethereum_testnet_ropsten_archive.json @@ -49,6 +49,7 @@ "mempool_sub_workers": 2, "block_addresses_to_keep": 3000, "additional_params": { + "address_aliases": true, "mempoolTxTimeoutHours": 12, "processInternalTransactions": true, "queryBackendOnMempoolResync": false, diff --git a/db/bulkconnect.go b/db/bulkconnect.go index 0e1183a1..0bd7ac62 100644 --- a/db/bulkconnect.go +++ b/db/bulkconnect.go @@ -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 { diff --git a/db/rocksdb.go b/db/rocksdb.go index 59157013..ad02a0de 100644 --- a/db/rocksdb.go +++ b/db/rocksdb.go @@ -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 } diff --git a/db/rocksdb_ethereumtype.go b/db/rocksdb_ethereumtype.go index 3310afe3..0f7f95f1 100644 --- a/db/rocksdb_ethereumtype.go +++ b/db/rocksdb_ethereumtype.go @@ -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 diff --git a/db/rocksdb_ethereumtype_test.go b/db/rocksdb_ethereumtype_test.go index a61fc958..6631df9f 100644 --- a/db/rocksdb_ethereumtype_test.go +++ b/db/rocksdb_ethereumtype_test.go @@ -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) } diff --git a/server/public_ethereumtype_test.go b/server/public_ethereumtype_test.go index 27a6009d..bd4461c6 100644 --- a/server/public_ethereumtype_test.go +++ b/server/public_ethereumtype_test.go @@ -73,7 +73,7 @@ func httpTestsEthereumType(t *testing.T, ts *httptest.Server) { status: http.StatusOK, contentType: "application/json; charset=utf-8", body: []string{ - `{"page":1,"totalPages":1,"itemsOnPage":1000,"address":"0x7B62EB7fe80350DC7EC945C0B73242cb9877FB1b","balance":"123450123","unconfirmedBalance":"0","unconfirmedTxs":0,"txs":2,"transactions":[{"txid":"0xca7628be5c80cda77163729ec63d218ee868a399d827a4682a478c6f48a6e22a","vin":[{"n":0,"addresses":["0x837E3f699d85a4b0B99894567e9233dFB1DcB081"],"isAddress":true}],"vout":[{"value":"0","n":0,"addresses":["0xcdA9FC258358EcaA88845f19Af595e908bb7EfE9"],"isAddress":true}],"blockHeight":-1,"confirmations":0,"blockTime":0,"value":"0","fees":"87945000410410","rbf":true,"coinSpecificData":{"tx":{"nonce":"0x2","gasPrice":"0x59682f07","gas":"0x173a9","to":"0xcdA9FC258358EcaA88845f19Af595e908bb7EfE9","value":"0x0","input":"0x23b872dd000000000000000000000000837e3f699d85a4b0b99894567e9233dfb1dcb0810000000000000000000000007b62eb7fe80350dc7ec945c0b73242cb9877fb1b0000000000000000000000000000000000000000000000000000000000000001","hash":"0xca7628be5c80cda77163729ec63d218ee868a399d827a4682a478c6f48a6e22a","blockNumber":"0xb33b9f","from":"0x837E3f699d85a4b0B99894567e9233dFB1DcB081","transactionIndex":"0x1"},"receipt":{"gasUsed":"0xe506","status":"0x1","logs":[{"address":"0xcdA9FC258358EcaA88845f19Af595e908bb7EfE9","topics":["0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925","0x000000000000000000000000837e3f699d85a4b0b99894567e9233dfb1dcb081","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000001"],"data":"0x"},{"address":"0xcdA9FC258358EcaA88845f19Af595e908bb7EfE9","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x000000000000000000000000837e3f699d85a4b0b99894567e9233dfb1dcb081","0x0000000000000000000000007b62eb7fe80350dc7ec945c0b73242cb9877fb1b","0x0000000000000000000000000000000000000000000000000000000000000001"],"data":"0x"}]}},"tokenTransfers":[{"type":"ERC721","from":"0x837E3f699d85a4b0B99894567e9233dFB1DcB081","to":"0x7B62EB7fe80350DC7EC945C0B73242cb9877FB1b","token":"0xcdA9FC258358EcaA88845f19Af595e908bb7EfE9","name":"Contract 205","symbol":"S205","decimals":18,"value":"1"}],"ethereumSpecific":{"status":1,"nonce":2,"gasLimit":95145,"gasUsed":58630,"gasPrice":"1500000007","data":"0x23b872dd000000000000000000000000837e3f699d85a4b0b99894567e9233dfb1dcb0810000000000000000000000007b62eb7fe80350dc7ec945c0b73242cb9877fb1b0000000000000000000000000000000000000000000000000000000000000001","parsedData":{"methodId":"0x23b872dd","name":""}}},{"txid":"0xc92919ad24ffd58f760b18df7949f06e1190cf54a50a0e3745a385608ed3cbf2","vin":[{"n":0,"addresses":["0x4Bda106325C335dF99eab7fE363cAC8A0ba2a24D"],"isAddress":true}],"vout":[{"value":"0","n":0,"addresses":["0x479CC461fEcd078F766eCc58533D6F69580CF3AC"],"isAddress":true}],"blockHeight":-1,"confirmations":0,"blockTime":0,"value":"0","fees":"216368000000000","rbf":true,"coinSpecificData":{"tx":{"nonce":"0x1df76","gasPrice":"0x3b9aca00","gas":"0x3d090","to":"0x479CC461fEcd078F766eCc58533D6F69580CF3AC","value":"0x0","input":"0x4f15078700000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000003c00000000000000000000000000000000000000000000000000000000000000420000000000000000000000000000000000000000000000000000000000000048000000000000000000000000000000000000000000000000000000000000004e00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d0000000000000000000000000d0f936ee4c93e25944694d6c121de94d9760f110000000000000000000000004af4114f73d1c1c903ac9e0361b379d1291808a200000000000000000000000000000000000000000000000000000000000000000000000000000000000000007b62eb7fe80350dc7ec945c0b73242cb9877fb1b0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d0000000000000000000000004af4114f73d1c1c903ac9e0361b379d1291808a20000000000000000000000000d0f936ee4c93e25944694d6c121de94d9760f110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000a5ef5a7656bfb0000000000000000000000000000000000000000000000000000004ba78398d5c5000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000166cfe0b9579b4ecf7a2801880f644009a324671a79754ea57c3a103c6e70d3dbef6ba69a08000000000000000000000000000000000000000000000000004f937d86afb90000000000000000000000000000000000000000000000000ab280fd8037d500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000166cfb784b7c1f3fbe8b75484603ab8adc58aaee3a46245a6579fac7077b5570018b4e0d4eb0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000308fd0e798ac00000000000000000000000000000000000000000000000006a8313d60b1f606b0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001b000000000000000000000000000000000000000000000000000000000000001b00000000000000000000000000000000000000000000000000000000000000029de0ccec59e8948e3d905b40e5542335ebc1eb4674db517d2f6392ec7fdeb3d45f3449d313ee2589819c6c79eb1c1b047adae68565c1608e3a1d1d70823febb0000000000000000000000000000000000000000000000000000000000000000234d06fe17f1202e8b07177a30eb64d14adc08cdb3fa1b3e3e0bea0f9672c02175b77c01c51d3c7e460723b27ecbc7801fd6482559a8c9999593f9a4d149c7384","hash":"0xc92919ad24ffd58f760b18df7949f06e1190cf54a50a0e3745a385608ed3cbf2","blockNumber":"0x41eee9","from":"0x4Bda106325C335dF99eab7fE363cAC8A0ba2a24D","transactionIndex":"0x24"},"internalData":{"type":1,"contract":"0d0f936ee4c93e25944694d6c121de94d9760f11","transfers":[{"type":0,"from":"4bda106325c335df99eab7fe363cac8a0ba2a24d","to":"9f4981531fda132e83c44680787dfa7ee31e4f8d","value":1000010},{"type":2,"from":"4af4114f73d1c1c903ac9e0361b379d1291808a2","to":"9f4981531fda132e83c44680787dfa7ee31e4f8d","value":1000011}],"Error":""},"receipt":{"gasUsed":"0x34d30","status":"0x1","logs":[{"address":"0x0d0F936Ee4c93e25944694D6C121de94D9760F11","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f","0x0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d"],"data":"0x0000000000000000000000000000000000000000000000006a8313d60b1f8001"},{"address":"0x4af4114F73d1c1C903aC9E0361b379D1291808A2","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d","0x000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f"],"data":"0x000000000000000000000000000000000000000000000000000308fd0e798ac0"},{"address":"0x479CC461fEcd078F766eCc58533D6F69580CF3AC","topics":["0x0d0b9391970d9a25552f37d436d2aae2925e2bfe1b2a923754bada030c498cb3","0x000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f","0x0000000000000000000000000000000000000000000000000000000000000000","0x5af266c0a89a07c1917deaa024414577e6c3c31c8907d079e13eb448c082594f"],"data":"0x0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d0000000000000000000000000d0f936ee4c93e25944694d6c121de94d9760f110000000000000000000000004af4114f73d1c1c903ac9e0361b379d1291808a20000000000000000000000000000000000000000000000006a8313d60b1f8001000000000000000000000000000000000000000000000000000308fd0e798ac0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005e083a16f4b092c5729a49f9c3ed3cc171bb3d3d0c22e20b1de6063c32f399ac"},{"address":"0x4af4114F73d1c1C903aC9E0361b379D1291808A2","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x0000000000000000000000007b62eb7fe80350dc7ec945c0b73242cb9877fb1b","0x0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d"],"data":"0x00000000000000000000000000000000000000000000000000031855667df7a8"},{"address":"0x0d0F936Ee4c93e25944694D6C121de94D9760F11","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d","0x0000000000000000000000007b62eb7fe80350dc7ec945c0b73242cb9877fb1b"],"data":"0x0000000000000000000000000000000000000000000000006a8313d60b1f606b"},{"address":"0x479CC461fEcd078F766eCc58533D6F69580CF3AC","topics":["0x0d0b9391970d9a25552f37d436d2aae2925e2bfe1b2a923754bada030c498cb3","0x0000000000000000000000007b62eb7fe80350dc7ec945c0b73242cb9877fb1b","0x0000000000000000000000000000000000000000000000000000000000000000","0xb0b69dad58df6032c3b266e19b1045b19c87acd2c06fb0c598090f44b8e263aa"],"data":"0x0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d0000000000000000000000004af4114f73d1c1c903ac9e0361b379d1291808a20000000000000000000000000d0f936ee4c93e25944694d6c121de94d9760f1100000000000000000000000000000000000000000000000000031855667df7a80000000000000000000000000000000000000000000000006a8313d60b1f606b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f2b0d62c44ed08f2a5adef40c875d20310a42a9d4f488bd26323256fe01c7f48"}]}},"tokenTransfers":[{"type":"ERC20","from":"0x555Ee11FBDDc0E49A9bAB358A8941AD95fFDB48f","to":"0x4Bda106325C335dF99eab7fE363cAC8A0ba2a24D","token":"0x0d0F936Ee4c93e25944694D6C121de94D9760F11","name":"Contract 13","symbol":"S13","decimals":18,"value":"7675000000000000001"},{"type":"ERC20","from":"0x4Bda106325C335dF99eab7fE363cAC8A0ba2a24D","to":"0x555Ee11FBDDc0E49A9bAB358A8941AD95fFDB48f","token":"0x4af4114F73d1c1C903aC9E0361b379D1291808A2","name":"Contract 74","symbol":"S74","decimals":18,"value":"854307892726464"},{"type":"ERC20","from":"0x7B62EB7fe80350DC7EC945C0B73242cb9877FB1b","to":"0x4Bda106325C335dF99eab7fE363cAC8A0ba2a24D","token":"0x4af4114F73d1c1C903aC9E0361b379D1291808A2","name":"Contract 74","symbol":"S74","decimals":18,"value":"871180000950184"},{"type":"ERC20","from":"0x4Bda106325C335dF99eab7fE363cAC8A0ba2a24D","to":"0x7B62EB7fe80350DC7EC945C0B73242cb9877FB1b","token":"0x0d0F936Ee4c93e25944694D6C121de94D9760F11","name":"Contract 13","symbol":"S13","decimals":18,"value":"7674999999999991915"}],"ethereumSpecific":{"status":1,"nonce":122742,"gasLimit":250000,"gasUsed":216368,"gasPrice":"1000000000","data":"0x4f15078700000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000003c00000000000000000000000000000000000000000000000000000000000000420000000000000000000000000000000000000000000000000000000000000048000000000000000000000000000000000000000000000000000000000000004e00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d0000000000000000000000000d0f936ee4c93e25944694d6c121de94d9760f110000000000000000000000004af4114f73d1c1c903ac9e0361b379d1291808a200000000000000000000000000000000000000000000000000000000000000000000000000000000000000007b62eb7fe80350dc7ec945c0b73242cb9877fb1b0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d0000000000000000000000004af4114f73d1c1c903ac9e0361b379d1291808a20000000000000000000000000d0f936ee4c93e25944694d6c121de94d9760f110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000a5ef5a7656bfb0000000000000000000000000000000000000000000000000000004ba78398d5c5000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000166cfe0b9579b4ecf7a2801880f644009a324671a79754ea57c3a103c6e70d3dbef6ba69a08000000000000000000000000000000000000000000000000004f937d86afb90000000000000000000000000000000000000000000000000ab280fd8037d500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000166cfb784b7c1f3fbe8b75484603ab8adc58aaee3a46245a6579fac7077b5570018b4e0d4eb0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000308fd0e798ac00000000000000000000000000000000000000000000000006a8313d60b1f606b0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001b000000000000000000000000000000000000000000000000000000000000001b00000000000000000000000000000000000000000000000000000000000000029de0ccec59e8948e3d905b40e5542335ebc1eb4674db517d2f6392ec7fdeb3d45f3449d313ee2589819c6c79eb1c1b047adae68565c1608e3a1d1d70823febb0000000000000000000000000000000000000000000000000000000000000000234d06fe17f1202e8b07177a30eb64d14adc08cdb3fa1b3e3e0bea0f9672c02175b77c01c51d3c7e460723b27ecbc7801fd6482559a8c9999593f9a4d149c7384","parsedData":{"methodId":"0x4f150787","name":""}}}],"nonce":"123","tokens":[{"type":"ERC20","name":"Contract 74","contract":"0x4af4114F73d1c1C903aC9E0361b379D1291808A2","transfers":1,"symbol":"S74","decimals":18,"balance":"1000123074"},{"type":"ERC20","name":"Contract 13","contract":"0x0d0F936Ee4c93e25944694D6C121de94D9760F11","transfers":1,"symbol":"S13","decimals":18,"balance":"1000123013"},{"type":"ERC721","name":"Contract 205","contract":"0xcdA9FC258358EcaA88845f19Af595e908bb7EfE9","transfers":1,"symbol":"S205","decimals":18,"ids":["1"]}],"erc20Contract":{"contract":"0x7B62EB7fe80350DC7EC945C0B73242cb9877FB1b","name":"Contract 123","symbol":"S123","decimals":18}}`, + `{"page":1,"totalPages":1,"itemsOnPage":1000,"address":"0x7B62EB7fe80350DC7EC945C0B73242cb9877FB1b","balance":"123450123","unconfirmedBalance":"0","unconfirmedTxs":0,"txs":2,"transactions":[{"txid":"0xca7628be5c80cda77163729ec63d218ee868a399d827a4682a478c6f48a6e22a","vin":[{"n":0,"addresses":["0x837E3f699d85a4b0B99894567e9233dFB1DcB081"],"isAddress":true}],"vout":[{"value":"0","n":0,"addresses":["0xcdA9FC258358EcaA88845f19Af595e908bb7EfE9"],"isAddress":true}],"blockHeight":-1,"confirmations":0,"blockTime":0,"value":"0","fees":"87945000410410","rbf":true,"coinSpecificData":{"tx":{"nonce":"0x2","gasPrice":"0x59682f07","gas":"0x173a9","to":"0xcdA9FC258358EcaA88845f19Af595e908bb7EfE9","value":"0x0","input":"0x23b872dd000000000000000000000000837e3f699d85a4b0b99894567e9233dfb1dcb0810000000000000000000000007b62eb7fe80350dc7ec945c0b73242cb9877fb1b0000000000000000000000000000000000000000000000000000000000000001","hash":"0xca7628be5c80cda77163729ec63d218ee868a399d827a4682a478c6f48a6e22a","blockNumber":"0xb33b9f","from":"0x837E3f699d85a4b0B99894567e9233dFB1DcB081","transactionIndex":"0x1"},"receipt":{"gasUsed":"0xe506","status":"0x1","logs":[{"address":"0xcdA9FC258358EcaA88845f19Af595e908bb7EfE9","topics":["0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925","0x000000000000000000000000837e3f699d85a4b0b99894567e9233dfb1dcb081","0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000001"],"data":"0x"},{"address":"0xcdA9FC258358EcaA88845f19Af595e908bb7EfE9","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x000000000000000000000000837e3f699d85a4b0b99894567e9233dfb1dcb081","0x0000000000000000000000007b62eb7fe80350dc7ec945c0b73242cb9877fb1b","0x0000000000000000000000000000000000000000000000000000000000000001"],"data":"0x"}]}},"tokenTransfers":[{"type":"ERC721","from":"0x837E3f699d85a4b0B99894567e9233dFB1DcB081","to":"0x7B62EB7fe80350DC7EC945C0B73242cb9877FB1b","token":"0xcdA9FC258358EcaA88845f19Af595e908bb7EfE9","name":"Contract 205","symbol":"S205","decimals":18,"value":"1"}],"ethereumSpecific":{"status":1,"nonce":2,"gasLimit":95145,"gasUsed":58630,"gasPrice":"1500000007","data":"0x23b872dd000000000000000000000000837e3f699d85a4b0b99894567e9233dfb1dcb0810000000000000000000000007b62eb7fe80350dc7ec945c0b73242cb9877fb1b0000000000000000000000000000000000000000000000000000000000000001","parsedData":{"methodId":"0x23b872dd","name":""}}},{"txid":"0xc92919ad24ffd58f760b18df7949f06e1190cf54a50a0e3745a385608ed3cbf2","vin":[{"n":0,"addresses":["0x4Bda106325C335dF99eab7fE363cAC8A0ba2a24D"],"isAddress":true}],"vout":[{"value":"0","n":0,"addresses":["0x479CC461fEcd078F766eCc58533D6F69580CF3AC"],"isAddress":true}],"blockHeight":-1,"confirmations":0,"blockTime":0,"value":"0","fees":"216368000000000","rbf":true,"coinSpecificData":{"tx":{"nonce":"0x1df76","gasPrice":"0x3b9aca00","gas":"0x3d090","to":"0x479CC461fEcd078F766eCc58533D6F69580CF3AC","value":"0x0","input":"0x4f15078700000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000003c00000000000000000000000000000000000000000000000000000000000000420000000000000000000000000000000000000000000000000000000000000048000000000000000000000000000000000000000000000000000000000000004e00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d0000000000000000000000000d0f936ee4c93e25944694d6c121de94d9760f110000000000000000000000004af4114f73d1c1c903ac9e0361b379d1291808a200000000000000000000000000000000000000000000000000000000000000000000000000000000000000007b62eb7fe80350dc7ec945c0b73242cb9877fb1b0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d0000000000000000000000004af4114f73d1c1c903ac9e0361b379d1291808a20000000000000000000000000d0f936ee4c93e25944694d6c121de94d9760f110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000a5ef5a7656bfb0000000000000000000000000000000000000000000000000000004ba78398d5c5000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000166cfe0b9579b4ecf7a2801880f644009a324671a79754ea57c3a103c6e70d3dbef6ba69a08000000000000000000000000000000000000000000000000004f937d86afb90000000000000000000000000000000000000000000000000ab280fd8037d500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000166cfb784b7c1f3fbe8b75484603ab8adc58aaee3a46245a6579fac7077b5570018b4e0d4eb0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000308fd0e798ac00000000000000000000000000000000000000000000000006a8313d60b1f606b0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001b000000000000000000000000000000000000000000000000000000000000001b00000000000000000000000000000000000000000000000000000000000000029de0ccec59e8948e3d905b40e5542335ebc1eb4674db517d2f6392ec7fdeb3d45f3449d313ee2589819c6c79eb1c1b047adae68565c1608e3a1d1d70823febb0000000000000000000000000000000000000000000000000000000000000000234d06fe17f1202e8b07177a30eb64d14adc08cdb3fa1b3e3e0bea0f9672c02175b77c01c51d3c7e460723b27ecbc7801fd6482559a8c9999593f9a4d149c7384","hash":"0xc92919ad24ffd58f760b18df7949f06e1190cf54a50a0e3745a385608ed3cbf2","blockNumber":"0x41eee9","from":"0x4Bda106325C335dF99eab7fE363cAC8A0ba2a24D","transactionIndex":"0x24"},"internalData":{"type":1,"contract":"0d0f936ee4c93e25944694d6c121de94d9760f11","transfers":[{"type":0,"from":"4bda106325c335df99eab7fe363cac8a0ba2a24d","to":"9f4981531fda132e83c44680787dfa7ee31e4f8d","value":1000010},{"type":2,"from":"4af4114f73d1c1c903ac9e0361b379d1291808a2","to":"9f4981531fda132e83c44680787dfa7ee31e4f8d","value":1000011}],"Error":""},"receipt":{"gasUsed":"0x34d30","status":"0x1","logs":[{"address":"0x0d0F936Ee4c93e25944694D6C121de94D9760F11","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f","0x0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d"],"data":"0x0000000000000000000000000000000000000000000000006a8313d60b1f8001"},{"address":"0x4af4114F73d1c1C903aC9E0361b379D1291808A2","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d","0x000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f"],"data":"0x000000000000000000000000000000000000000000000000000308fd0e798ac0"},{"address":"0x479CC461fEcd078F766eCc58533D6F69580CF3AC","topics":["0x0d0b9391970d9a25552f37d436d2aae2925e2bfe1b2a923754bada030c498cb3","0x000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f","0x0000000000000000000000000000000000000000000000000000000000000000","0x5af266c0a89a07c1917deaa024414577e6c3c31c8907d079e13eb448c082594f"],"data":"0x0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d0000000000000000000000000d0f936ee4c93e25944694d6c121de94d9760f110000000000000000000000004af4114f73d1c1c903ac9e0361b379d1291808a20000000000000000000000000000000000000000000000006a8313d60b1f8001000000000000000000000000000000000000000000000000000308fd0e798ac0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005e083a16f4b092c5729a49f9c3ed3cc171bb3d3d0c22e20b1de6063c32f399ac"},{"address":"0x4af4114F73d1c1C903aC9E0361b379D1291808A2","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x0000000000000000000000007b62eb7fe80350dc7ec945c0b73242cb9877fb1b","0x0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d"],"data":"0x00000000000000000000000000000000000000000000000000031855667df7a8"},{"address":"0x0d0F936Ee4c93e25944694D6C121de94D9760F11","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d","0x0000000000000000000000007b62eb7fe80350dc7ec945c0b73242cb9877fb1b"],"data":"0x0000000000000000000000000000000000000000000000006a8313d60b1f606b"},{"address":"0x479CC461fEcd078F766eCc58533D6F69580CF3AC","topics":["0x0d0b9391970d9a25552f37d436d2aae2925e2bfe1b2a923754bada030c498cb3","0x0000000000000000000000007b62eb7fe80350dc7ec945c0b73242cb9877fb1b","0x0000000000000000000000000000000000000000000000000000000000000000","0xb0b69dad58df6032c3b266e19b1045b19c87acd2c06fb0c598090f44b8e263aa"],"data":"0x0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d0000000000000000000000004af4114f73d1c1c903ac9e0361b379d1291808a20000000000000000000000000d0f936ee4c93e25944694d6c121de94d9760f1100000000000000000000000000000000000000000000000000031855667df7a80000000000000000000000000000000000000000000000006a8313d60b1f606b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f2b0d62c44ed08f2a5adef40c875d20310a42a9d4f488bd26323256fe01c7f48"}]}},"tokenTransfers":[{"type":"ERC20","from":"0x555Ee11FBDDc0E49A9bAB358A8941AD95fFDB48f","to":"0x4Bda106325C335dF99eab7fE363cAC8A0ba2a24D","token":"0x0d0F936Ee4c93e25944694D6C121de94D9760F11","name":"Contract 13","symbol":"S13","decimals":18,"value":"7675000000000000001"},{"type":"ERC20","from":"0x4Bda106325C335dF99eab7fE363cAC8A0ba2a24D","to":"0x555Ee11FBDDc0E49A9bAB358A8941AD95fFDB48f","token":"0x4af4114F73d1c1C903aC9E0361b379D1291808A2","name":"Contract 74","symbol":"S74","decimals":18,"value":"854307892726464"},{"type":"ERC20","from":"0x7B62EB7fe80350DC7EC945C0B73242cb9877FB1b","to":"0x4Bda106325C335dF99eab7fE363cAC8A0ba2a24D","token":"0x4af4114F73d1c1C903aC9E0361b379D1291808A2","name":"Contract 74","symbol":"S74","decimals":18,"value":"871180000950184"},{"type":"ERC20","from":"0x4Bda106325C335dF99eab7fE363cAC8A0ba2a24D","to":"0x7B62EB7fe80350DC7EC945C0B73242cb9877FB1b","token":"0x0d0F936Ee4c93e25944694D6C121de94D9760F11","name":"Contract 13","symbol":"S13","decimals":18,"value":"7674999999999991915"}],"ethereumSpecific":{"status":1,"nonce":122742,"gasLimit":250000,"gasUsed":216368,"gasPrice":"1000000000","data":"0x4f15078700000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000003c00000000000000000000000000000000000000000000000000000000000000420000000000000000000000000000000000000000000000000000000000000048000000000000000000000000000000000000000000000000000000000000004e00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d0000000000000000000000000d0f936ee4c93e25944694d6c121de94d9760f110000000000000000000000004af4114f73d1c1c903ac9e0361b379d1291808a200000000000000000000000000000000000000000000000000000000000000000000000000000000000000007b62eb7fe80350dc7ec945c0b73242cb9877fb1b0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d0000000000000000000000004af4114f73d1c1c903ac9e0361b379d1291808a20000000000000000000000000d0f936ee4c93e25944694d6c121de94d9760f110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000a5ef5a7656bfb0000000000000000000000000000000000000000000000000000004ba78398d5c5000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000166cfe0b9579b4ecf7a2801880f644009a324671a79754ea57c3a103c6e70d3dbef6ba69a08000000000000000000000000000000000000000000000000004f937d86afb90000000000000000000000000000000000000000000000000ab280fd8037d500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000166cfb784b7c1f3fbe8b75484603ab8adc58aaee3a46245a6579fac7077b5570018b4e0d4eb0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000308fd0e798ac00000000000000000000000000000000000000000000000006a8313d60b1f606b0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001b000000000000000000000000000000000000000000000000000000000000001b00000000000000000000000000000000000000000000000000000000000000029de0ccec59e8948e3d905b40e5542335ebc1eb4674db517d2f6392ec7fdeb3d45f3449d313ee2589819c6c79eb1c1b047adae68565c1608e3a1d1d70823febb0000000000000000000000000000000000000000000000000000000000000000234d06fe17f1202e8b07177a30eb64d14adc08cdb3fa1b3e3e0bea0f9672c02175b77c01c51d3c7e460723b27ecbc7801fd6482559a8c9999593f9a4d149c7384","parsedData":{"methodId":"0x4f150787","name":""}}}],"nonce":"123","tokens":[{"type":"ERC20","name":"Contract 74","contract":"0x4af4114F73d1c1C903aC9E0361b379D1291808A2","transfers":1,"symbol":"S74","decimals":18,"balance":"1000123074"},{"type":"ERC20","name":"Contract 13","contract":"0x0d0F936Ee4c93e25944694D6C121de94D9760F11","transfers":1,"symbol":"S13","decimals":18,"balance":"1000123013"},{"type":"ERC721","name":"Contract 205","contract":"0xcdA9FC258358EcaA88845f19Af595e908bb7EfE9","transfers":1,"symbol":"S205","decimals":18,"ids":["1"]}],"erc20Contract":{"contract":"0x7B62EB7fe80350DC7EC945C0B73242cb9877FB1b","name":"Contract 123","symbol":"S123","decimals":18},"addressAliases":{"0x7B62EB7fe80350DC7EC945C0B73242cb9877FB1b":{"Type":"ENS","Alias":"address7b.eth"}}}`, }, }, { @@ -82,7 +82,7 @@ func httpTestsEthereumType(t *testing.T, ts *httptest.Server) { status: http.StatusOK, contentType: "application/json; charset=utf-8", body: []string{ - `{"txid":"0xa9cd088aba2131000da6f38a33c20169baee476218deea6b78720700b895b101","vin":[{"n":0,"addresses":["0x20cD153de35D469BA46127A0C8F18626b59a256A"],"isAddress":true}],"vout":[{"value":"0","n":0,"addresses":["0x4af4114F73d1c1C903aC9E0361b379D1291808A2"],"isAddress":true}],"blockHeight":-1,"confirmations":0,"blockTime":0,"value":"0","fees":"2081000000000000","rbf":true,"coinSpecificData":{"tx":{"nonce":"0xd0","gasPrice":"0x9502f9000","gas":"0x130d5","to":"0x4af4114F73d1c1C903aC9E0361b379D1291808A2","value":"0x0","input":"0xa9059cbb000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f00000000000000000000000000000000000000000000021e19e0c9bab2400000","hash":"0xa9cd088aba2131000da6f38a33c20169baee476218deea6b78720700b895b101","blockNumber":"0x41eee8","from":"0x20cD153de35D469BA46127A0C8F18626b59a256A","transactionIndex":"0x0"},"internalData":{"type":0,"transfers":[{"type":1,"from":"9f4981531fda132e83c44680787dfa7ee31e4f8d","to":"4af4114f73d1c1c903ac9e0361b379d1291808a2","value":1000000},{"type":0,"from":"3e3a3d69dc66ba10737f531ed088954a9ec89d97","to":"9f4981531fda132e83c44680787dfa7ee31e4f8d","value":1000001},{"type":0,"from":"3e3a3d69dc66ba10737f531ed088954a9ec89d97","to":"3e3a3d69dc66ba10737f531ed088954a9ec89d97","value":1000002}],"Error":""},"receipt":{"gasUsed":"0xcb39","status":"0x1","logs":[{"address":"0x4af4114F73d1c1C903aC9E0361b379D1291808A2","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x00000000000000000000000020cd153de35d469ba46127a0c8f18626b59a256a","0x000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f"],"data":"0x00000000000000000000000000000000000000000000021e19e0c9bab2400000"}]}},"tokenTransfers":[{"type":"ERC20","from":"0x20cD153de35D469BA46127A0C8F18626b59a256A","to":"0x555Ee11FBDDc0E49A9bAB358A8941AD95fFDB48f","token":"0x4af4114F73d1c1C903aC9E0361b379D1291808A2","name":"Contract 74","symbol":"S74","decimals":18,"value":"10000000000000000000000"}],"ethereumSpecific":{"status":1,"nonce":208,"gasLimit":78037,"gasUsed":52025,"gasPrice":"40000000000","data":"0xa9059cbb000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f00000000000000000000000000000000000000000000021e19e0c9bab2400000","parsedData":{"methodId":"0xa9059cbb","name":"Transfer","function":"transfer(address, uint256)","params":[{"type":"address","values":["0x555Ee11FBDDc0E49A9bAB358A8941AD95fFDB48f"]},{"type":"uint256","values":["10000000000000000000000"]}]}}}`, + `{"txid":"0xa9cd088aba2131000da6f38a33c20169baee476218deea6b78720700b895b101","vin":[{"n":0,"addresses":["0x20cD153de35D469BA46127A0C8F18626b59a256A"],"isAddress":true}],"vout":[{"value":"0","n":0,"addresses":["0x4af4114F73d1c1C903aC9E0361b379D1291808A2"],"isAddress":true}],"blockHeight":-1,"confirmations":0,"blockTime":0,"value":"0","fees":"2081000000000000","rbf":true,"coinSpecificData":{"tx":{"nonce":"0xd0","gasPrice":"0x9502f9000","gas":"0x130d5","to":"0x4af4114F73d1c1C903aC9E0361b379D1291808A2","value":"0x0","input":"0xa9059cbb000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f00000000000000000000000000000000000000000000021e19e0c9bab2400000","hash":"0xa9cd088aba2131000da6f38a33c20169baee476218deea6b78720700b895b101","blockNumber":"0x41eee8","from":"0x20cD153de35D469BA46127A0C8F18626b59a256A","transactionIndex":"0x0"},"internalData":{"type":0,"transfers":[{"type":1,"from":"9f4981531fda132e83c44680787dfa7ee31e4f8d","to":"4af4114f73d1c1c903ac9e0361b379d1291808a2","value":1000000},{"type":0,"from":"3e3a3d69dc66ba10737f531ed088954a9ec89d97","to":"9f4981531fda132e83c44680787dfa7ee31e4f8d","value":1000001},{"type":0,"from":"3e3a3d69dc66ba10737f531ed088954a9ec89d97","to":"3e3a3d69dc66ba10737f531ed088954a9ec89d97","value":1000002}],"Error":""},"receipt":{"gasUsed":"0xcb39","status":"0x1","logs":[{"address":"0x4af4114F73d1c1C903aC9E0361b379D1291808A2","topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x00000000000000000000000020cd153de35d469ba46127a0c8f18626b59a256a","0x000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f"],"data":"0x00000000000000000000000000000000000000000000021e19e0c9bab2400000"}]}},"tokenTransfers":[{"type":"ERC20","from":"0x20cD153de35D469BA46127A0C8F18626b59a256A","to":"0x555Ee11FBDDc0E49A9bAB358A8941AD95fFDB48f","token":"0x4af4114F73d1c1C903aC9E0361b379D1291808A2","name":"Contract 74","symbol":"S74","decimals":18,"value":"10000000000000000000000"}],"ethereumSpecific":{"status":1,"nonce":208,"gasLimit":78037,"gasUsed":52025,"gasPrice":"40000000000","data":"0xa9059cbb000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f00000000000000000000000000000000000000000000021e19e0c9bab2400000","parsedData":{"methodId":"0xa9059cbb","name":"Transfer","function":"transfer(address, uint256)","params":[{"type":"address","values":["0x555Ee11FBDDc0E49A9bAB358A8941AD95fFDB48f"]},{"type":"uint256","values":["10000000000000000000000"]}]}},"addressAliases":{"0x20cD153de35D469BA46127A0C8F18626b59a256A":{"Type":"ENS","Alias":"address20.eth"}}}`, }, }, } @@ -104,7 +104,7 @@ func initEthereumTypeDB(d *db.RocksDB) error { } func Test_PublicServer_EthereumType(t *testing.T) { - parser := eth.NewEthereumParser(1) + parser := eth.NewEthereumParser(1, true) chain, err := dbtestdata.NewFakeBlockChainEthereumType(parser) if err != nil { glog.Fatal("fakechain: ", err) diff --git a/tests/dbtestdata/dbtestdata_ethereumtype.go b/tests/dbtestdata/dbtestdata_ethereumtype.go index 03e72f56..e34012bd 100644 --- a/tests/dbtestdata/dbtestdata_ethereumtype.go +++ b/tests/dbtestdata/dbtestdata_ethereumtype.go @@ -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, } }