Store contract info in DB

This commit is contained in:
Martin Boehm 2022-05-01 02:41:56 +02:00 committed by Martin
parent 2507e12057
commit db91824dc3
21 changed files with 616 additions and 360 deletions

View File

@ -135,58 +135,40 @@ type Vout struct {
Type string `json:"type,omitempty"`
}
// TokenType specifies type of token
type TokenType string
// Token types
const (
// Ethereum token types
ERC20TokenType TokenType = "ERC20"
ERC771TokenType TokenType = "ERC721"
ERC1155TokenType TokenType = "ERC1155"
// XPUBAddressTokenType is address derived from xpub
XPUBAddressTokenType TokenType = "XPUBAddress"
)
// TokenTypeMap maps bchain.TokenTransferType to TokenType
// the map must match all bchain.TokenTransferTypes to avoid index out of range panic
var TokenTypeMap []TokenType = []TokenType{ERC20TokenType, ERC771TokenType, ERC1155TokenType}
// TokenTransferValues contains values for ERC1155 contract
type TokenTransferValues struct {
// MultiTokenValue contains values for contract with id and value (like ERC1155)
type MultiTokenValue struct {
Id *Amount `json:"id,omitempty"`
Value *Amount `json:"value,omitempty"`
}
// Token contains info about tokens held by an address
type Token struct {
Type TokenType `json:"type"`
Name string `json:"name"`
Path string `json:"path,omitempty"`
Contract string `json:"contract,omitempty"`
Transfers int `json:"transfers"`
Symbol string `json:"symbol,omitempty"`
Decimals int `json:"decimals,omitempty"`
BalanceSat *Amount `json:"balance,omitempty"`
Ids []Amount `json:"ids,omitempty"` // multiple ERC721 tokens
IdValues []TokenTransferValues `json:"idValues,omitempty"` // multiple ERC1155 tokens
TotalReceivedSat *Amount `json:"totalReceived,omitempty"`
TotalSentSat *Amount `json:"totalSent,omitempty"`
ContractIndex string `json:"-"`
Type bchain.TokenTypeName `json:"type"`
Name string `json:"name"`
Path string `json:"path,omitempty"`
Contract string `json:"contract,omitempty"`
Transfers int `json:"transfers"`
Symbol string `json:"symbol,omitempty"`
Decimals int `json:"decimals,omitempty"`
BalanceSat *Amount `json:"balance,omitempty"`
Ids []Amount `json:"ids,omitempty"` // multiple ERC721 tokens
MultiTokenValues []MultiTokenValue `json:"multiTokenValues,omitempty"` // multiple ERC1155 tokens
TotalReceivedSat *Amount `json:"totalReceived,omitempty"`
TotalSentSat *Amount `json:"totalSent,omitempty"`
ContractIndex string `json:"-"`
}
// TokenTransfer contains info about a token transfer done in a transaction
type TokenTransfer struct {
Type TokenType `json:"type"`
From string `json:"from"`
To string `json:"to"`
Token string `json:"token"`
Name string `json:"name"`
Symbol string `json:"symbol"`
Decimals int `json:"decimals"`
Value *Amount `json:"value,omitempty"`
Values []TokenTransferValues `json:"values,omitempty"`
Type bchain.TokenTypeName `json:"type"`
From string `json:"from"`
To string `json:"to"`
Token string `json:"token"`
Name string `json:"name"`
Symbol string `json:"symbol"`
Decimals int `json:"decimals"`
Value *Amount `json:"value,omitempty"`
MultiTokenValues []MultiTokenValue `json:"multiTokenValues,omitempty"`
}
type EthereumInternalTransfer struct {
@ -290,22 +272,22 @@ type AddressFilter struct {
// Address holds information about address and its transactions
type Address struct {
Paging
AddrStr string `json:"address"`
BalanceSat *Amount `json:"balance"`
TotalReceivedSat *Amount `json:"totalReceived,omitempty"`
TotalSentSat *Amount `json:"totalSent,omitempty"`
UnconfirmedBalanceSat *Amount `json:"unconfirmedBalance"`
UnconfirmedTxs int `json:"unconfirmedTxs"`
Txs int `json:"txs"`
NonTokenTxs int `json:"nonTokenTxs,omitempty"`
InternalTxs int `json:"internalTxs,omitempty"`
Transactions []*Tx `json:"transactions,omitempty"`
Txids []string `json:"txids,omitempty"`
Nonce string `json:"nonce,omitempty"`
UsedTokens int `json:"usedTokens,omitempty"`
Tokens []Token `json:"tokens,omitempty"`
Erc20Contract *bchain.Erc20Contract `json:"erc20Contract,omitempty"`
AddressAliases AddressAliasesMap `json:"addressAliases,omitempty"`
AddrStr string `json:"address"`
BalanceSat *Amount `json:"balance"`
TotalReceivedSat *Amount `json:"totalReceived,omitempty"`
TotalSentSat *Amount `json:"totalSent,omitempty"`
UnconfirmedBalanceSat *Amount `json:"unconfirmedBalance"`
UnconfirmedTxs int `json:"unconfirmedTxs"`
Txs int `json:"txs"`
NonTokenTxs int `json:"nonTokenTxs,omitempty"`
InternalTxs int `json:"internalTxs,omitempty"`
Transactions []*Tx `json:"transactions,omitempty"`
Txids []string `json:"txids,omitempty"`
Nonce string `json:"nonce,omitempty"`
UsedTokens int `json:"usedTokens,omitempty"`
Tokens []Token `json:"tokens,omitempty"`
ContractInfo *bchain.ContractInfo `json:"contractInfo,omitempty"`
AddressAliases AddressAliasesMap `json:"addressAliases,omitempty"`
// helpers for explorer
Filter string `json:"-"`
XPubAddresses map[string]struct{} `json:"-"`

View File

@ -151,7 +151,10 @@ func (w *Worker) getAddressAliases(addresses map[string]struct{}) AddressAliases
}
for a := range addresses {
if w.chainType == bchain.ChainEthereumType {
// TODO get contract name
ci, err := w.db.GetContractInfoForAddress(a)
if err == nil && ci != nil && ci.Name != "" {
aliases[a] = AddressAlias{Type: "Contract", Alias: ci.Name}
}
}
n := w.db.GetAddressAlias(a)
if len(n) > 0 {
@ -535,20 +538,28 @@ func (w *Worker) getEthereumTokensTransfers(transfers bchain.TokenTransfers, add
glog.Errorf("GetAddrDescFromAddress error %v, contract %v", err, t.Contract)
continue
}
erc20c, err := w.chain.EthereumTypeGetErc20ContractInfo(cd)
typeName := bchain.EthereumTokenTypeMap[t.Type]
contractInfo, err := w.db.GetContractInfo(cd, typeName)
if err != nil {
glog.Errorf("GetErc20ContractInfo error %v, contract %v", err, t.Contract)
glog.Errorf("GetContractInfo error %v, contract %v", err, t.Contract)
}
if erc20c == nil {
erc20c = &bchain.Erc20Contract{Name: t.Contract}
if contractInfo == nil {
glog.Warningf("Contract %v %v not found in DB", t.Contract, typeName)
contractInfo, err = w.chain.GetContractInfo(cd)
if err != nil {
glog.Errorf("GetContractInfo from chain error %v, contract %v", err, t.Contract)
}
if contractInfo == nil {
contractInfo = &bchain.ContractInfo{Name: t.Contract, Type: bchain.UnknownTokenType}
}
}
var value *Amount
var values []TokenTransferValues
if t.Type == bchain.ERC1155 {
values = make([]TokenTransferValues, len(t.IdValues))
var values []MultiTokenValue
if t.Type == bchain.MultiToken {
values = make([]MultiTokenValue, len(t.MultiTokenValues))
for j := range values {
values[j].Id = (*Amount)(&t.IdValues[j].Id)
values[j].Value = (*Amount)(&t.IdValues[j].Value)
values[j].Id = (*Amount)(&t.MultiTokenValues[j].Id)
values[j].Value = (*Amount)(&t.MultiTokenValues[j].Value)
}
} else {
value = (*Amount)(&t.Value)
@ -556,15 +567,15 @@ func (w *Worker) getEthereumTokensTransfers(transfers bchain.TokenTransfers, add
aggregateAddress(addresses, t.From)
aggregateAddress(addresses, t.To)
tokens[i] = TokenTransfer{
Type: TokenTypeMap[t.Type],
Token: t.Contract,
From: t.From,
To: t.To,
Value: value,
Values: values,
Decimals: erc20c.Decimals,
Name: erc20c.Name,
Symbol: erc20c.Symbol,
Type: typeName,
Token: t.Contract,
From: t.From,
To: t.To,
Value: value,
MultiTokenValues: values,
Decimals: contractInfo.Decimals,
Name: contractInfo.Name,
Symbol: contractInfo.Symbol,
}
}
return tokens
@ -751,35 +762,41 @@ func computePaging(count, page, itemsOnPage int) (Paging, int, int, int) {
}
func (w *Worker) getEthereumContractBalance(addrDesc bchain.AddressDescriptor, index int, c *db.AddrContract, details AccountDetails) (*Token, error) {
// TODO use db.contracts
validContract := true
ci, err := w.chain.EthereumTypeGetErc20ContractInfo(c.Contract)
typeName := bchain.EthereumTokenTypeMap[c.Type]
ci, err := w.db.GetContractInfo(c.Contract, typeName)
if err != nil {
return nil, errors.Annotatef(err, "EthereumTypeGetErc20ContractInfo %v", c.Contract)
return nil, errors.Annotatef(err, "GetContractInfo %v", c.Contract)
}
if ci == nil {
ci = &bchain.Erc20Contract{}
addresses, _, _ := w.chainParser.GetAddressesFromAddrDesc(c.Contract)
if len(addresses) > 0 {
ci.Contract = addresses[0]
ci.Name = addresses[0]
glog.Warningf("Contract %v %v not found in DB", c.Contract, typeName)
ci, err = w.chain.GetContractInfo(c.Contract)
if err != nil {
glog.Errorf("GetContractInfo from chain error %v, contract %v", err, c.Contract)
}
if ci == nil {
ci = &bchain.ContractInfo{Type: bchain.UnknownTokenType}
addresses, _, _ := w.chainParser.GetAddressesFromAddrDesc(c.Contract)
if len(addresses) > 0 {
ci.Contract = addresses[0]
ci.Name = addresses[0]
}
validContract = false
}
validContract = false
}
t := Token{
Contract: ci.Contract,
Name: ci.Name,
Symbol: ci.Symbol,
Type: typeName,
Transfers: int(c.Txs),
Decimals: ci.Decimals,
ContractIndex: strconv.Itoa(index),
}
// return contract balances/values only at or above AccountDetailsTokenBalances
if details >= AccountDetailsTokenBalances && validContract {
if c.Type == bchain.ERC20 {
t.Type = ERC20TokenType
if c.Type == bchain.FungibleToken {
// get Erc20 Contract Balance from blockchain, balance obtained from adding and subtracting transfers is not correct
b, err := w.chain.EthereumTypeGetErc20ContractBalance(addrDesc, c.Contract)
if err != nil {
@ -789,11 +806,6 @@ func (w *Worker) getEthereumContractBalance(addrDesc bchain.AddressDescriptor, i
t.BalanceSat = (*Amount)(b)
}
} else {
if c.Type == bchain.ERC721 {
t.Type = ERC771TokenType
} else {
t.Type = ERC1155TokenType
}
if len(c.Ids) > 0 {
ids := make([]Amount, len(c.Ids))
for j := range ids {
@ -801,13 +813,13 @@ func (w *Worker) getEthereumContractBalance(addrDesc bchain.AddressDescriptor, i
}
t.Ids = ids
}
if len(c.IdValues) > 0 {
idValues := make([]TokenTransferValues, len(c.IdValues))
if len(c.MultiTokenValues) > 0 {
idValues := make([]MultiTokenValue, len(c.MultiTokenValues))
for j := range idValues {
idValues[j].Id = (*Amount)(&c.IdValues[j].Id)
idValues[j].Value = (*Amount)(&c.IdValues[j].Value)
idValues[j].Id = (*Amount)(&c.MultiTokenValues[j].Id)
idValues[j].Value = (*Amount)(&c.MultiTokenValues[j].Value)
}
t.IdValues = idValues
t.MultiTokenValues = idValues
}
}
}
@ -819,18 +831,25 @@ func (w *Worker) getEthereumContractBalance(addrDesc bchain.AddressDescriptor, i
func (w *Worker) getEthereumContractBalanceFromBlockchain(addrDesc, contract bchain.AddressDescriptor, details AccountDetails) (*Token, error) {
var b *big.Int
validContract := true
ci, err := w.chain.EthereumTypeGetErc20ContractInfo(contract)
ci, err := w.db.GetContractInfo(contract, "")
if err != nil {
return nil, errors.Annotatef(err, "EthereumTypeGetErc20ContractInfo %v", contract)
return nil, errors.Annotatef(err, "GetContractInfo %v", contract)
}
if ci == nil {
ci = &bchain.Erc20Contract{}
addresses, _, _ := w.chainParser.GetAddressesFromAddrDesc(contract)
if len(addresses) > 0 {
ci.Contract = addresses[0]
ci.Name = addresses[0]
glog.Warningf("Contract %v not found in DB", contract)
ci, err = w.chain.GetContractInfo(contract)
if err != nil {
glog.Errorf("GetContractInfo from chain error %v, contract %v", err, contract)
}
if ci == nil {
ci = &bchain.ContractInfo{Type: bchain.UnknownTokenType}
addresses, _, _ := w.chainParser.GetAddressesFromAddrDesc(contract)
if len(addresses) > 0 {
ci.Contract = addresses[0]
ci.Name = addresses[0]
}
validContract = false
}
validContract = false
}
// do not read contract balances etc in case of Basic option
if details >= AccountDetailsTokenBalances && validContract {
@ -843,7 +862,7 @@ func (w *Worker) getEthereumContractBalanceFromBlockchain(addrDesc, contract bch
b = nil
}
return &Token{
Type: ERC20TokenType,
Type: ci.Type,
BalanceSat: (*Amount)(b),
Contract: ci.Contract,
Name: ci.Name,
@ -854,11 +873,11 @@ func (w *Worker) getEthereumContractBalanceFromBlockchain(addrDesc, contract bch
}, nil
}
func (w *Worker) getEthereumTypeAddressBalances(addrDesc bchain.AddressDescriptor, details AccountDetails, filter *AddressFilter) (*db.AddrBalance, []Token, *bchain.Erc20Contract, uint64, int, int, int, error) {
func (w *Worker) getEthereumTypeAddressBalances(addrDesc bchain.AddressDescriptor, details AccountDetails, filter *AddressFilter) (*db.AddrBalance, []Token, *bchain.ContractInfo, uint64, int, int, int, error) {
var (
ba *db.AddrBalance
tokens []Token
ci *bchain.Erc20Contract
ci *bchain.ContractInfo
n uint64
nonContractTxs int
internalTxs int
@ -912,7 +931,7 @@ func (w *Worker) getEthereumTypeAddressBalances(addrDesc bchain.AddressDescripto
}
tokens = tokens[:j]
}
ci, err = w.chain.EthereumTypeGetErc20ContractInfo(addrDesc)
ci, err = w.db.GetContractInfo(addrDesc, "")
if err != nil {
return nil, nil, nil, 0, 0, 0, 0, err
}
@ -1043,7 +1062,7 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, option Acco
var (
ba *db.AddrBalance
tokens []Token
erc20c *bchain.Erc20Contract
contractInfo *bchain.ContractInfo
txm []string
txs []*Tx
txids []string
@ -1062,7 +1081,7 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, option Acco
}
if w.chainType == bchain.ChainEthereumType {
var n uint64
ba, tokens, erc20c, n, nonTokenTxs, internalTxs, totalResults, err = w.getEthereumTypeAddressBalances(addrDesc, option, filter)
ba, tokens, contractInfo, n, nonTokenTxs, internalTxs, totalResults, err = w.getEthereumTypeAddressBalances(addrDesc, option, filter)
if err != nil {
return nil, err
}
@ -1173,7 +1192,7 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, option Acco
Transactions: txs,
Txids: txids,
Tokens: tokens,
Erc20Contract: erc20c,
ContractInfo: contractInfo,
Nonce: nonce,
AddressAliases: w.getAddressAliases(addresses),
}

View File

@ -266,7 +266,7 @@ func (w *Worker) tokenFromXpubAddress(data *xpubData, ad *xpubAddress, changeInd
}
}
return Token{
Type: XPUBAddressTokenType,
Type: bchain.XPUBAddressTokenType,
Name: address,
Decimals: w.chainParser.AmountDecimals(),
BalanceSat: (*Amount)(balance),

View File

@ -54,8 +54,8 @@ func (b *BaseChain) EthereumTypeEstimateGas(params map[string]interface{}) (uint
return 0, errors.New("Not supported")
}
// EthereumTypeGetErc20ContractInfo is not supported
func (b *BaseChain) EthereumTypeGetErc20ContractInfo(contractDesc AddressDescriptor) (*Erc20Contract, error) {
// GetContractInfo is not supported
func (b *BaseChain) GetContractInfo(contractDesc AddressDescriptor) (*ContractInfo, error) {
return nil, errors.New("Not supported")
}

View File

@ -321,13 +321,13 @@ func (c *blockChainWithMetrics) EthereumTypeEstimateGas(params map[string]interf
return c.b.EthereumTypeEstimateGas(params)
}
func (c *blockChainWithMetrics) EthereumTypeGetErc20ContractInfo(contractDesc bchain.AddressDescriptor) (v *bchain.Erc20Contract, err error) {
defer func(s time.Time) { c.observeRPCLatency("EthereumTypeGetErc20ContractInfo", s, err) }(time.Now())
return c.b.EthereumTypeGetErc20ContractInfo(contractDesc)
func (c *blockChainWithMetrics) GetContractInfo(contractDesc bchain.AddressDescriptor) (v *bchain.ContractInfo, err error) {
defer func(s time.Time) { c.observeRPCLatency("GetContractInfo", s, err) }(time.Now())
return c.b.GetContractInfo(contractDesc)
}
func (c *blockChainWithMetrics) EthereumTypeGetErc20ContractBalance(addrDesc, contractDesc bchain.AddressDescriptor) (v *big.Int, err error) {
defer func(s time.Time) { c.observeRPCLatency("EthereumTypeGetErc20ContractInfo", s, err) }(time.Now())
defer func(s time.Time) { c.observeRPCLatency("EthereumTypeGetErc20ContractBalance", s, err) }(time.Now())
return c.b.EthereumTypeGetErc20ContractBalance(addrDesc, contractDesc)
}

View File

@ -4,10 +4,8 @@ import (
"context"
"math/big"
"strings"
"sync"
ethcommon "github.com/ethereum/go-ethereum/common"
"github.com/golang/glog"
"github.com/juju/errors"
"github.com/trezor/blockbook/bchain"
)
@ -28,9 +26,6 @@ const contractSymbolSignature = "0x95d89b41"
const contractDecimalsSignature = "0x313ce567"
const contractBalanceOf = "0x70a08231"
var cachedContracts = make(map[string]*bchain.Erc20Contract)
var cachedContractsMux sync.Mutex
func addressFromPaddedHex(s string) (string, error) {
var t big.Int
var ok bool
@ -48,16 +43,16 @@ func addressFromPaddedHex(s string) (string, error) {
func processTransferEvent(l *bchain.RpcLog) (*bchain.TokenTransfer, error) {
tl := len(l.Topics)
var ttt bchain.TokenTransferType
var ttt bchain.TokenType
var value big.Int
if tl == 3 {
ttt = bchain.ERC20
ttt = bchain.FungibleToken
_, ok := value.SetString(l.Data, 0)
if !ok {
return nil, errors.New("ERC20 log Data is not a number")
}
} else if tl == 4 {
ttt = bchain.ERC721
ttt = bchain.NonFungibleToken
_, ok := value.SetString(l.Topics[3], 0)
if !ok {
return nil, errors.New("ERC721 log Topics[3] is not a number")
@ -105,11 +100,11 @@ func processERC1155TransferSingleEvent(l *bchain.RpcLog) (*bchain.TokenTransfer,
return nil, errors.New("ERC1155 log Data value is not a number")
}
return &bchain.TokenTransfer{
Type: bchain.ERC1155,
Contract: EIP55AddressFromAddress(l.Address),
From: EIP55AddressFromAddress(from),
To: EIP55AddressFromAddress(to),
IdValues: []bchain.TokenTransferIdValue{{Id: id, Value: value}},
Type: bchain.MultiToken,
Contract: EIP55AddressFromAddress(l.Address),
From: EIP55AddressFromAddress(from),
To: EIP55AddressFromAddress(to),
MultiTokenValues: []bchain.MultiTokenValue{{Id: id, Value: value}},
}, nil
}
@ -150,7 +145,7 @@ func processERC1155TransferBatchEvent(l *bchain.RpcLog) (*bchain.TokenTransfer,
if countIds != countValues {
return nil, errors.New("ERC1155 TransferBatch, count values and ids does not match")
}
idValues := make([]bchain.TokenTransferIdValue, countValues)
idValues := make([]bchain.MultiTokenValue, countValues)
for i := 0; i < countValues; i++ {
var id, value big.Int
o := offsetIds + 64 + 64*i
@ -163,14 +158,14 @@ func processERC1155TransferBatchEvent(l *bchain.RpcLog) (*bchain.TokenTransfer,
if !ok {
return nil, errors.New("ERC1155 log Data value is not a number")
}
idValues[i] = bchain.TokenTransferIdValue{Id: id, Value: value}
idValues[i] = bchain.MultiTokenValue{Id: id, Value: value}
}
return &bchain.TokenTransfer{
Type: bchain.ERC1155,
Contract: EIP55AddressFromAddress(l.Address),
From: EIP55AddressFromAddress(from),
To: EIP55AddressFromAddress(to),
IdValues: idValues,
Type: bchain.MultiToken,
Contract: EIP55AddressFromAddress(l.Address),
From: EIP55AddressFromAddress(from),
To: EIP55AddressFromAddress(to),
MultiTokenValues: idValues,
}, nil
}
func contractGetTransfersFromLog(logs []*bchain.RpcLog) (bchain.TokenTransfers, error) {
@ -214,7 +209,7 @@ func contractGetTransfersFromTx(tx *bchain.RpcTransaction) (bchain.TokenTransfer
return nil, errors.New("Data is not a number")
}
r = append(r, &bchain.TokenTransfer{
Type: bchain.ERC20,
Type: bchain.FungibleToken,
Contract: EIP55AddressFromAddress(tx.To),
From: EIP55AddressFromAddress(tx.From),
To: EIP55AddressFromAddress(to),
@ -238,7 +233,7 @@ func contractGetTransfersFromTx(tx *bchain.RpcTransaction) (bchain.TokenTransfer
return nil, errors.New("Data is not a number")
}
r = append(r, &bchain.TokenTransfer{
Type: bchain.ERC721,
Type: bchain.NonFungibleToken,
Contract: EIP55AddressFromAddress(tx.To),
From: EIP55AddressFromAddress(from),
To: EIP55AddressFromAddress(to),
@ -262,56 +257,52 @@ func (b *EthereumRPC) ethCall(data, to string) (string, error) {
return r, nil
}
// EthereumTypeGetErc20ContractInfo returns information about ERC20 contract
func (b *EthereumRPC) EthereumTypeGetErc20ContractInfo(contractDesc bchain.AddressDescriptor) (*bchain.Erc20Contract, error) {
cds := string(contractDesc)
cachedContractsMux.Lock()
contract, found := cachedContracts[cds]
cachedContractsMux.Unlock()
if !found {
address := EIP55Address(contractDesc)
data, err := b.ethCall(contractNameSignature, address)
if err != nil {
// ignore the error from the eth_call - since geth v1.9.15 they changed the behavior
// and returning error "execution reverted" for some non contract addresses
// https://github.com/ethereum/go-ethereum/issues/21249#issuecomment-648647672
glog.Warning(errors.Annotatef(err, "erc20NameSignature %v", address))
return nil, nil
// return nil, errors.Annotatef(err, "erc20NameSignature %v", address)
}
name := parseSimpleStringProperty(data)
if name != "" {
data, err = b.ethCall(contractSymbolSignature, address)
if err != nil {
glog.Warning(errors.Annotatef(err, "erc20SymbolSignature %v", address))
return nil, nil
// return nil, errors.Annotatef(err, "erc20SymbolSignature %v", address)
}
symbol := parseSimpleStringProperty(data)
data, err = b.ethCall(contractDecimalsSignature, address)
if err != nil {
glog.Warning(errors.Annotatef(err, "erc20DecimalsSignature %v", address))
// return nil, errors.Annotatef(err, "erc20DecimalsSignature %v", address)
}
contract = &bchain.Erc20Contract{
Contract: address,
Name: name,
Symbol: symbol,
}
d := parseSimpleNumericProperty(data)
if d != nil {
contract.Decimals = int(uint8(d.Uint64()))
} else {
contract.Decimals = EtherAmountDecimalPoint
}
} else {
contract = nil
}
cachedContractsMux.Lock()
cachedContracts[cds] = contract
cachedContractsMux.Unlock()
func (b *EthereumRPC) fetchContractInfo(address string) (*bchain.ContractInfo, error) {
var contract bchain.ContractInfo
data, err := b.ethCall(contractNameSignature, address)
if err != nil {
// ignore the error from the eth_call - since geth v1.9.15 they changed the behavior
// and returning error "execution reverted" for some non contract addresses
// https://github.com/ethereum/go-ethereum/issues/21249#issuecomment-648647672
// glog.Warning(errors.Annotatef(err, "Contract NameSignature %v", address))
return nil, nil
// return nil, errors.Annotatef(err, "erc20NameSignature %v", address)
}
return contract, nil
name := parseSimpleStringProperty(data)
if name != "" {
data, err = b.ethCall(contractSymbolSignature, address)
if err != nil {
// glog.Warning(errors.Annotatef(err, "Contract SymbolSignature %v", address))
return nil, nil
// return nil, errors.Annotatef(err, "erc20SymbolSignature %v", address)
}
symbol := parseSimpleStringProperty(data)
data, _ = b.ethCall(contractDecimalsSignature, address)
// if err != nil {
// glog.Warning(errors.Annotatef(err, "Contract DecimalsSignature %v", address))
// // return nil, errors.Annotatef(err, "erc20DecimalsSignature %v", address)
// }
contract = bchain.ContractInfo{
Contract: address,
Name: name,
Symbol: symbol,
}
d := parseSimpleNumericProperty(data)
if d != nil {
contract.Decimals = int(uint8(d.Uint64()))
} else {
contract.Decimals = EtherAmountDecimalPoint
}
} else {
return nil, nil
}
return &contract, nil
}
// GetContractInfo returns information about a contract
func (b *EthereumRPC) GetContractInfo(contractDesc bchain.AddressDescriptor) (*bchain.ContractInfo, error) {
address := EIP55Address(contractDesc)
return b.fetchContractInfo(address)
}
// EthereumTypeGetErc20ContractBalance returns balance of ERC20 contract for given address

View File

@ -133,7 +133,7 @@ func Test_contractGetTransfersFromLog(t *testing.T) {
},
want: bchain.TokenTransfers{
{
Type: bchain.ERC721,
Type: bchain.NonFungibleToken,
Contract: "0x5689b918D34C038901870105A6C7fc24744D31eB",
From: "0x0a206d4d5ff79cb5069def7fe3598421cff09391",
To: "0x6a016d7eec560549ffa0fbdb7f15c2b27302087f",
@ -171,11 +171,11 @@ func Test_contractGetTransfersFromLog(t *testing.T) {
},
want: bchain.TokenTransfers{
{
Type: bchain.ERC1155,
Contract: "0x6Fd712E3A5B556654044608F9129040A4839E36c",
From: "0xa3950b823cb063dd9afc0d27f35008b805b3ed53",
To: "0x4392faf3bb96b5694ecc6ef64726f61cdd4bb0ec",
IdValues: []bchain.TokenTransferIdValue{{Id: *big.NewInt(150), Value: *big.NewInt(0x11)}},
Type: bchain.MultiToken,
Contract: "0x6Fd712E3A5B556654044608F9129040A4839E36c",
From: "0xa3950b823cb063dd9afc0d27f35008b805b3ed53",
To: "0x4392faf3bb96b5694ecc6ef64726f61cdd4bb0ec",
MultiTokenValues: []bchain.MultiTokenValue{{Id: *big.NewInt(150), Value: *big.NewInt(0x11)}},
},
},
},
@ -195,11 +195,11 @@ func Test_contractGetTransfersFromLog(t *testing.T) {
},
want: bchain.TokenTransfers{
{
Type: bchain.ERC1155,
Type: bchain.MultiToken,
Contract: "0x6c42c26a081c2f509f8bb68fb7ac3062311ccfb7",
From: "0x0000000000000000000000000000000000000000",
To: "0x5dc6288b35e0807a3d6feb89b3a2ff4ab773168e",
IdValues: []bchain.TokenTransferIdValue{
MultiTokenValues: []bchain.MultiTokenValue{
{Id: *big.NewInt(1776), Value: *big.NewInt(1)},
{Id: *big.NewInt(1898), Value: *big.NewInt(10)},
},
@ -247,7 +247,7 @@ func Test_contractGetTransfersFromTx(t *testing.T) {
args: (b1.Txs[1].CoinSpecificData.(bchain.EthereumSpecificData)).Tx,
want: bchain.TokenTransfers{
{
Type: bchain.ERC20,
Type: bchain.FungibleToken,
Contract: "0x4af4114f73d1c1c903ac9e0361b379d1291808a2",
From: "0x20cd153de35d469ba46127a0c8f18626b59a256a",
To: "0x555ee11fbddc0e49a9bab358a8941ad95ffdb48f",
@ -260,7 +260,7 @@ func Test_contractGetTransfersFromTx(t *testing.T) {
args: (b2.Txs[2].CoinSpecificData.(bchain.EthereumSpecificData)).Tx,
want: bchain.TokenTransfers{
{
Type: bchain.ERC721,
Type: bchain.NonFungibleToken,
Contract: "0xcda9fc258358ecaa88845f19af595e908bb7efe9",
From: "0x837e3f699d85a4b0b99894567e9233dfb1dcb081",
To: "0x7b62eb7fe80350dc7ec945c0b73242cb9877fb1b",

View File

@ -39,8 +39,8 @@ func parseSimpleStringProperty(data string) string {
if len(data) > 128 {
n := parseSimpleNumericProperty(data[64:128])
if n != nil {
l := n.Uint64()
if l > 0 && 2*int(l) <= len(data)-128 {
l := n.Int64()
if l > 0 && int(l) <= ((len(data)-128)>>1) {
b, err := hex.DecodeString(data[128 : 128+2*l])
if err == nil {
return string(b)

View File

@ -45,6 +45,11 @@ func Test_parseSimpleStringProperty(t *testing.T) {
args: "0x2234880850896048596206002535425366538144616734015984380565810000",
want: "",
},
{
name: "garbage",
args: "6080604052600436106100225760003560e01c80630cbcae701461003957610031565b366100315761002f610077565b005b61002f610077565b34801561004557600080fd5b5061004e61014e565b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200160405180910390f35b7f000000000000000000000000000000000000000000000000000000000000000061011c565b60043560601b60601c6bca11c0de15dead10cced00006000195460a01c036100e9577f696d706c6f63000000000000000000000000000000000000000000000000000060005260206000fd5b8060001955005b60405136810160405236600082376000803683600019545af43d6000833e80610117573d82fd5b503d81f35b80330361014357602436036101435763ca11c0de60003560e01c036101435761014361009d565b61014b6100f0565b50565b600073ffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff541660005260206000f3fea2646970667358221220f27ad3f3b75609baa5d26d65ec1001c4a59f38e89088d6b47517c1cd1faf22ab64736f6c634300080d0033",
want: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {

View File

@ -537,23 +537,37 @@ type rpcTraceResult struct {
Result rpcCallTrace `json:"result"`
}
func (b *EthereumRPC) processCallTrace(call *rpcCallTrace, d *bchain.EthereumInternalData) {
func (b *EthereumRPC) getCreationContractInfo(contract string, height uint32) *bchain.ContractInfo {
ci, err := b.fetchContractInfo(contract)
if ci == nil || err != nil {
ci = &bchain.ContractInfo{
Contract: contract,
}
}
ci.Type = bchain.UnknownTokenType
ci.CreatedInBlock = height
return ci
}
func (b *EthereumRPC) processCallTrace(call *rpcCallTrace, d *bchain.EthereumInternalData, contracts []bchain.ContractInfo, blockHeight uint32) []bchain.ContractInfo {
value, err := hexutil.DecodeBig(call.Value)
if call.Type == "CREATE" {
d.Transfers = append(d.Transfers, bchain.EthereumInternalTransfer{
Type: bchain.CREATE,
Value: *value,
From: call.From,
To: call.To,
To: call.To, // new contract address
})
contracts = append(contracts, *b.getCreationContractInfo(call.To, blockHeight))
} else if call.Type == "SELFDESTRUCT" {
d.Transfers = append(d.Transfers, bchain.EthereumInternalTransfer{
Type: bchain.SELFDESTRUCT,
Value: *value,
From: call.From,
From: call.From, // destroyed contract address
To: call.To,
})
contracts = append(contracts, bchain.ContractInfo{Contract: call.From, DestructedInBlock: blockHeight})
} else if err == nil && (value.BitLen() > 0 || b.ChainConfig.ProcessZeroInternalTransactions) {
d.Transfers = append(d.Transfers, bchain.EthereumInternalTransfer{
Value: *value,
@ -565,13 +579,15 @@ func (b *EthereumRPC) processCallTrace(call *rpcCallTrace, d *bchain.EthereumInt
d.Error = call.Error
}
for i := range call.Calls {
b.processCallTrace(&call.Calls[i], d)
contracts = b.processCallTrace(&call.Calls[i], d, contracts, blockHeight)
}
return contracts
}
// getInternalDataForBlock fetches debug trace using callTracer, extracts internal transfers and creations and destructions of contracts
func (b *EthereumRPC) getInternalDataForBlock(blockHash string, transactions []bchain.RpcTransaction) ([]bchain.EthereumInternalData, error) {
func (b *EthereumRPC) getInternalDataForBlock(blockHash string, blockHeight uint32, transactions []bchain.RpcTransaction) ([]bchain.EthereumInternalData, []bchain.ContractInfo, error) {
data := make([]bchain.EthereumInternalData, len(transactions))
contracts := make([]bchain.ContractInfo, 0)
if ProcessInternalTransactions {
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
defer cancel()
@ -579,11 +595,11 @@ func (b *EthereumRPC) getInternalDataForBlock(blockHash string, transactions []b
err := b.rpc.CallContext(ctx, &trace, "debug_traceBlockByHash", blockHash, map[string]interface{}{"tracer": "callTracer"})
if err != nil {
glog.Error("debug_traceBlockByHash block ", blockHash, ", error ", err)
return data, err
return data, contracts, err
}
if len(trace) != len(data) {
glog.Error("debug_traceBlockByHash block ", blockHash, ", error: trace length does not match block length ", len(trace), "!=", len(data))
return data, err
return data, contracts, err
}
for i, result := range trace {
r := &result.Result
@ -591,11 +607,12 @@ func (b *EthereumRPC) getInternalDataForBlock(blockHash string, transactions []b
if r.Type == "CREATE" {
d.Type = bchain.CREATE
d.Contract = r.To
contracts = append(contracts, *b.getCreationContractInfo(d.Contract, blockHeight))
} else if r.Type == "SELFDESTRUCT" {
d.Type = bchain.SELFDESTRUCT
}
for j := range r.Calls {
b.processCallTrace(&r.Calls[j], d)
contracts = b.processCallTrace(&r.Calls[j], d, contracts, blockHeight)
}
if r.Error != "" {
baseError := PackInternalTransactionError(r.Error)
@ -620,7 +637,7 @@ func (b *EthereumRPC) getInternalDataForBlock(blockHash string, transactions []b
}
}
}
return data, nil
return data, contracts, nil
}
// GetBlock returns block with given hash or height, hash has precedence if both passed
@ -642,15 +659,16 @@ func (b *EthereumRPC) GetBlock(hash string, height uint32) (*bchain.Block, error
return nil, errors.Annotatef(err, "hash %v, height %v", hash, height)
}
// get block events
// TODO - could be possibly done in parallel to getInternalDataForBlock
logs, ens, err := b.processEventsForBlock(head.Number)
if err != nil {
return nil, err
}
// error fetching internal data does not stop the block processing
var blockSpecificData *bchain.EthereumBlockSpecificData
internalData, err := b.getInternalDataForBlock(head.Hash, body.Transactions)
internalData, contracts, err := b.getInternalDataForBlock(head.Hash, bbh.Height, body.Transactions)
// pass internalData error and ENS records in blockSpecificData to be stored
if err != nil || len(ens) > 0 {
if err != nil || len(ens) > 0 || len(contracts) > 0 {
blockSpecificData = &bchain.EthereumBlockSpecificData{}
if err != nil {
blockSpecificData.InternalDataError = err.Error()
@ -658,7 +676,11 @@ func (b *EthereumRPC) GetBlock(hash string, height uint32) (*bchain.Block, error
}
if len(ens) > 0 {
blockSpecificData.AddressAliasRecords = ens
glog.Info("ENS", ens)
// glog.Info("ENS", ens)
}
if len(contracts) > 0 {
blockSpecificData.Contracts = contracts
// glog.Info("Contracts", contracts)
}
}

View File

@ -113,6 +113,27 @@ type MempoolTx struct {
CoinSpecificData interface{} `json:"-"`
}
// TokenType - type of token
type TokenType int
// TokenType enumeration
const (
FungibleToken = TokenType(iota) // ERC20
NonFungibleToken // ERC721
MultiToken // ERC1155
)
// TokenTypeName specifies type of token
type TokenTypeName string
// Token types
const (
UnknownTokenType TokenTypeName = ""
// XPUBAddressTokenType is address derived from xpub
XPUBAddressTokenType TokenTypeName = "XPUBAddress"
)
// TokenTransfers is array of TokenTransfer
type TokenTransfers []*TokenTransfer
@ -286,13 +307,13 @@ type BlockChain interface {
EstimateFee(blocks int) (big.Int, error)
SendRawTransaction(tx string) (string, error)
GetMempoolEntry(txid string) (*MempoolEntry, error)
GetContractInfo(contractDesc AddressDescriptor) (*ContractInfo, error)
// parser
GetChainParser() BlockChainParser
// EthereumType specific
EthereumTypeGetBalance(addrDesc AddressDescriptor) (*big.Int, error)
EthereumTypeGetNonce(addrDesc AddressDescriptor) (uint64, error)
EthereumTypeEstimateGas(params map[string]interface{}) (uint64, error)
EthereumTypeGetErc20ContractInfo(contractDesc AddressDescriptor) (*Erc20Contract, error)
EthereumTypeGetErc20ContractBalance(addrDesc, contractDesc AddressDescriptor) (*big.Int, error)
}

View File

@ -47,16 +47,6 @@ const (
SELFDESTRUCT
)
// TokenTransferType - type of token transfer
type TokenTransferType int
// TokenTransferType enumeration
const (
ERC20 = TokenTransferType(iota)
ERC721
ERC1155
)
// EthereumInternalTransaction contains internal transfers
type EthereumInternalData struct {
Type EthereumInternalTransactionType `json:"type"`
@ -65,27 +55,41 @@ type EthereumInternalData struct {
Error string
}
// Erc20Contract contains info about ERC20 contract
type Erc20Contract struct {
Contract string `json:"contract"`
Name string `json:"name"`
Symbol string `json:"symbol"`
Decimals int `json:"decimals"`
// ContractInfo contains info about ERC20 contract
type ContractInfo struct {
Type TokenTypeName `json:"type"`
Contract string `json:"contract"`
Name string `json:"name"`
Symbol string `json:"symbol"`
Decimals int `json:"decimals"`
CreatedInBlock uint32 `json:"createdInBlock,omitempty"`
DestructedInBlock uint32 `json:"destructedInBlock,omitempty"`
}
type TokenTransferIdValue struct {
// Ethereum token type names
const (
ERC20TokenType TokenTypeName = "ERC20"
ERC771TokenType TokenTypeName = "ERC721"
ERC1155TokenType TokenTypeName = "ERC1155"
)
// EthereumTokenTypeMap maps bchain.TokenType to TokenTypeName
// the map must match all bchain.TokenType to avoid index out of range panic
var EthereumTokenTypeMap []TokenTypeName = []TokenTypeName{ERC20TokenType, ERC771TokenType, ERC1155TokenType}
type MultiTokenValue struct {
Id big.Int
Value big.Int
}
// TokenTransfer contains a single ERC20/ERC721/ERC1155 token transfer
// TokenTransfer contains a single token transfer
type TokenTransfer struct {
Type TokenTransferType
Contract string
From string
To string
Value big.Int
IdValues []TokenTransferIdValue
Type TokenType
Contract string
From string
To string
Value big.Int
MultiTokenValues []MultiTokenValue
}
// RpcTransaction is returned by eth_getTransactionByHash
@ -138,4 +142,5 @@ type AddressAliasRecord struct {
type EthereumBlockSpecificData struct {
InternalDataError string
AddressAliasRecords []AddressAliasRecord
Contracts []ContractInfo
}

View File

@ -6,6 +6,7 @@ import (
"math/big"
"sync"
vlq "github.com/bsm/go-vlq"
"github.com/flier/gorocksdb"
"github.com/golang/glog"
"github.com/juju/errors"
@ -18,12 +19,12 @@ const ContractIndexOffset = 2
// AddrContract is Contract address with number of transactions done by given address
type AddrContract struct {
Type bchain.TokenTransferType
Contract bchain.AddressDescriptor
Txs uint
Value big.Int // single value of ERC20
Ids []big.Int // multiple ERC721 tokens
IdValues []bchain.TokenTransferIdValue // multiple ERC1155 tokens
Type bchain.TokenType
Contract bchain.AddressDescriptor
Txs uint
Value big.Int // single value of ERC20
Ids []big.Int // multiple ERC721 tokens
MultiTokenValues []bchain.MultiTokenValue // multiple ERC1155 tokens
}
// AddrContracts contains number of transactions and contracts for an address
@ -48,10 +49,10 @@ func packAddrContracts(acs *AddrContracts) []byte {
buf = append(buf, ac.Contract...)
l = packVaruint(uint(ac.Type)+ac.Txs<<2, varBuf)
buf = append(buf, varBuf[:l]...)
if ac.Type == bchain.ERC20 {
if ac.Type == bchain.FungibleToken {
l = packBigint(&ac.Value, varBuf)
buf = append(buf, varBuf[:l]...)
} else if ac.Type == bchain.ERC721 {
} else if ac.Type == bchain.NonFungibleToken {
l = packVaruint(uint(len(ac.Ids)), varBuf)
buf = append(buf, varBuf[:l]...)
for i := range ac.Ids {
@ -59,12 +60,12 @@ func packAddrContracts(acs *AddrContracts) []byte {
buf = append(buf, varBuf[:l]...)
}
} else { // bchain.ERC1155
l = packVaruint(uint(len(ac.IdValues)), varBuf)
l = packVaruint(uint(len(ac.MultiTokenValues)), varBuf)
buf = append(buf, varBuf[:l]...)
for i := range ac.IdValues {
l = packBigint(&ac.IdValues[i].Id, varBuf)
for i := range ac.MultiTokenValues {
l = packBigint(&ac.MultiTokenValues[i].Id, varBuf)
buf = append(buf, varBuf[:l]...)
l = packBigint(&ac.IdValues[i].Value, varBuf)
l = packBigint(&ac.MultiTokenValues[i].Value, varBuf)
buf = append(buf, varBuf[:l]...)
}
}
@ -87,21 +88,21 @@ func unpackAddrContracts(buf []byte, addrDesc bchain.AddressDescriptor) (*AddrCo
contract := append(bchain.AddressDescriptor(nil), buf[:eth.EthereumTypeAddressDescriptorLen]...)
txs, l := unpackVaruint(buf[eth.EthereumTypeAddressDescriptorLen:])
buf = buf[eth.EthereumTypeAddressDescriptorLen+l:]
ttt := bchain.TokenTransferType(txs & 3)
ttt := bchain.TokenType(txs & 3)
txs >>= 2
ac := AddrContract{
Type: ttt,
Contract: contract,
Txs: txs,
}
if ttt == bchain.ERC20 {
if ttt == bchain.FungibleToken {
b, ll := unpackBigint(buf)
buf = buf[ll:]
ac.Value = b
} else {
len, ll := unpackVaruint(buf)
buf = buf[ll:]
if ttt == bchain.ERC721 {
if ttt == bchain.NonFungibleToken {
ac.Ids = make([]big.Int, len)
for i := uint(0); i < len; i++ {
b, ll := unpackBigint(buf)
@ -109,14 +110,14 @@ func unpackAddrContracts(buf []byte, addrDesc bchain.AddressDescriptor) (*AddrCo
ac.Ids[i] = b
}
} else {
ac.IdValues = make([]bchain.TokenTransferIdValue, len)
ac.MultiTokenValues = make([]bchain.MultiTokenValue, len)
for i := uint(0); i < len; i++ {
b, ll := unpackBigint(buf)
buf = buf[ll:]
ac.IdValues[i].Id = b
ac.MultiTokenValues[i].Id = b
b, ll = unpackBigint(buf)
buf = buf[ll:]
ac.IdValues[i].Value = b
ac.MultiTokenValues[i].Value = b
}
}
}
@ -226,9 +227,9 @@ func addToContract(c *AddrContract, contractIndex int, index int32, contract bch
s.Add(s, v)
}
}
if transfer.Type == bchain.ERC20 {
if transfer.Type == bchain.FungibleToken {
aggregate(&c.Value, &transfer.Value)
} else if transfer.Type == bchain.ERC721 {
} else if transfer.Type == bchain.NonFungibleToken {
if index < 0 {
// remove token from the list
for i := range c.Ids {
@ -242,14 +243,14 @@ func addToContract(c *AddrContract, contractIndex int, index int32, contract bch
c.Ids = append(c.Ids, transfer.Value)
}
} else { // bchain.ERC1155
for _, t := range transfer.IdValues {
for i := range c.IdValues {
for _, t := range transfer.MultiTokenValues {
for i := range c.MultiTokenValues {
// find the token in the list
if c.IdValues[i].Id.Cmp(&t.Id) == 0 {
aggregate(&c.IdValues[i].Value, &t.Value)
if c.MultiTokenValues[i].Id.Cmp(&t.Id) == 0 {
aggregate(&c.MultiTokenValues[i].Value, &t.Value)
// if transfer from, remove if the value is zero
if index < 0 && len(c.IdValues[i].Value.Bits()) == 0 {
c.IdValues = append(c.IdValues[:i], c.IdValues[i+1:]...)
if index < 0 && len(c.MultiTokenValues[i].Value.Bits()) == 0 {
c.MultiTokenValues = append(c.MultiTokenValues[:i], c.MultiTokenValues[i+1:]...)
}
goto nextTransfer
}
@ -257,7 +258,7 @@ func addToContract(c *AddrContract, contractIndex int, index int32, contract bch
// if not found and transfer to, add to the list
// it is necessary to add a copy of the value so that subsequent calls to addToContract do not change the transfer value
if index >= 0 {
c.IdValues = append(c.IdValues, bchain.TokenTransferIdValue{
c.MultiTokenValues = append(c.MultiTokenValues, bchain.MultiTokenValue{
Id: t.Id,
Value: *new(big.Int).Set(&t.Value),
})
@ -327,9 +328,9 @@ func (d *RocksDB) addToAddressesAndContractsEthereumType(addrDesc bchain.Address
type ethBlockTxContract struct {
from, to, contract bchain.AddressDescriptor
transferType bchain.TokenTransferType
transferType bchain.TokenType
value big.Int
idValues []bchain.TokenTransferIdValue
idValues []bchain.MultiTokenValue
}
type ethInternalTransfer struct {
@ -476,7 +477,7 @@ func (d *RocksDB) processContractTransfers(blockTx *ethBlockTx, tx *bchain.Tx, a
bc.to = to
bc.contract = contract
bc.value = t.Value
bc.idValues = t.IdValues
bc.idValues = t.MultiTokenValues
}
return nil
}
@ -699,6 +700,113 @@ func (d *RocksDB) storeInternalDataEthereumType(wb *gorocksdb.WriteBatch, blockT
return nil
}
var cachedContracts = make(map[string]*bchain.ContractInfo)
var cachedContractsMux sync.Mutex
func packContractInfo(contractInfo *bchain.ContractInfo) []byte {
buf := packString(contractInfo.Name)
buf = append(buf, packString(contractInfo.Symbol)...)
buf = append(buf, packString(string(contractInfo.Type))...)
varBuf := make([]byte, vlq.MaxLen64)
l := packVaruint(uint(contractInfo.Decimals), varBuf)
buf = append(buf, varBuf[:l]...)
l = packVaruint(uint(contractInfo.CreatedInBlock), varBuf)
buf = append(buf, varBuf[:l]...)
l = packVaruint(uint(contractInfo.DestructedInBlock), varBuf)
buf = append(buf, varBuf[:l]...)
return buf
}
func unpackContractInfo(buf []byte) (*bchain.ContractInfo, error) {
var contractInfo bchain.ContractInfo
var s string
var l int
var ui uint
contractInfo.Name, l = unpackString(buf)
buf = buf[l:]
contractInfo.Symbol, l = unpackString(buf)
buf = buf[l:]
s, l = unpackString(buf)
contractInfo.Type = bchain.TokenTypeName(s)
buf = buf[l:]
ui, l = unpackVaruint(buf)
contractInfo.Decimals = int(ui)
buf = buf[l:]
ui, l = unpackVaruint(buf)
contractInfo.CreatedInBlock = uint32(ui)
buf = buf[l:]
ui, l = unpackVaruint(buf)
contractInfo.DestructedInBlock = uint32(ui)
return &contractInfo, nil
}
func (d *RocksDB) GetContractInfoForAddress(address string) (*bchain.ContractInfo, error) {
contract, err := d.chainParser.GetAddrDescFromAddress(address)
if err != nil || contract == nil {
return nil, err
}
return d.GetContractInfo(contract, "")
}
// GetContractInfo gets contract from cache or DB and possibly updates the type from typeFromContext
// this is because it is hard to guess the type of the contract using API, it is easier to set it the first time its usage is detected in tx
func (d *RocksDB) GetContractInfo(contract bchain.AddressDescriptor, typeFromContext bchain.TokenTypeName) (*bchain.ContractInfo, error) {
cacheKey := string(contract)
cachedContractsMux.Lock()
contractInfo, found := cachedContracts[cacheKey]
cachedContractsMux.Unlock()
if !found {
val, err := d.db.GetCF(d.ro, d.cfh[cfContracts], contract)
if err != nil {
return nil, err
}
defer val.Free()
buf := val.Data()
if len(buf) == 0 {
return nil, nil
}
contractInfo, err = unpackContractInfo(buf)
addresses, _, _ := d.chainParser.GetAddressesFromAddrDesc(contract)
if len(addresses) > 0 {
contractInfo.Contract = addresses[0]
}
// if the type is specified and stored contractInfo has unknown type, set and store it
if typeFromContext != bchain.UnknownTokenType && contractInfo.Type == bchain.UnknownTokenType {
contractInfo.Type = typeFromContext
err = d.db.PutCF(d.wo, d.cfh[cfContracts], contract, packContractInfo(contractInfo))
}
cachedContractsMux.Lock()
cachedContracts[cacheKey] = contractInfo
cachedContractsMux.Unlock()
}
return contractInfo, nil
}
// StoreContractInfo stores contractInfo in DB
// if CreatedInBlock==0 and DestructedInBlock!=0, it is evaluated as a desctruction of a contract, the contract info is updated
// in all other cases the contractInfo overwrites previously stored data in DB (however it should not really happen as contract is created only once)
func (d *RocksDB) StoreContractInfo(wb *gorocksdb.WriteBatch, contractInfo *bchain.ContractInfo) error {
if contractInfo.Contract != "" {
key, err := d.chainParser.GetAddrDescFromAddress(contractInfo.Contract)
if err != nil {
return err
}
if contractInfo.CreatedInBlock == 0 && contractInfo.DestructedInBlock != 0 {
storedCI, err := d.GetContractInfo(key, "")
if err != nil {
return err
}
if storedCI == nil {
return nil
}
storedCI.DestructedInBlock = contractInfo.DestructedInBlock
contractInfo = storedCI
}
wb.PutCF(d.cfh[cfContracts], key, packContractInfo(contractInfo))
}
return nil
}
func packBlockTx(buf []byte, blockTx *ethBlockTx) []byte {
varBuf := make([]byte, maxPackedBigintBytes)
buf = append(buf, blockTx.btxID...)
@ -715,7 +823,7 @@ func packBlockTx(buf []byte, blockTx *ethBlockTx) []byte {
buf = appendAddress(buf, c.contract)
l = packVaruint(uint(c.transferType), varBuf)
buf = append(buf, varBuf[:l]...)
if c.transferType == bchain.ERC1155 {
if c.transferType == bchain.MultiToken {
l = packVaruint(uint(len(c.idValues)), varBuf)
buf = append(buf, varBuf[:l]...)
for i := range c.idValues {
@ -773,6 +881,11 @@ func (d *RocksDB) storeBlockSpecificDataEthereumType(wb *gorocksdb.WriteBatch, b
return err
}
}
for i := range blockSpecificData.Contracts {
if err := d.StoreContractInfo(wb, &blockSpecificData.Contracts[i]); err != nil {
return err
}
}
}
return nil
}
@ -823,12 +936,12 @@ func unpackBlockTx(buf []byte, pos int) (*ethBlockTx, int, error) {
return nil, 0, err
}
cc, l = unpackVaruint(buf[pos:])
c.transferType = bchain.TokenTransferType(cc)
c.transferType = bchain.TokenType(cc)
pos += l
if c.transferType == bchain.ERC1155 {
if c.transferType == bchain.MultiToken {
cc, l = unpackVaruint(buf[pos:])
pos += l
c.idValues = make([]bchain.TokenTransferIdValue, cc)
c.idValues = make([]bchain.MultiTokenValue, cc)
for i := range c.idValues {
c.idValues[i].Id, l = unpackBigint(buf[pos:])
pos += l
@ -938,9 +1051,9 @@ func (d *RocksDB) disconnectAddress(btxID []byte, internal bool, addrDesc bchain
index = transferTo
}
addToContract(addrContract, contractIndex, index, btxContract.contract, &bchain.TokenTransfer{
Type: btxContract.transferType,
Value: btxContract.value,
IdValues: btxContract.idValues,
Type: btxContract.transferType,
Value: btxContract.value,
MultiTokenValues: btxContract.idValues,
}, false)
}
} else {

View File

@ -58,11 +58,11 @@ func verifyAfterEthereumTypeBlock1(t *testing.T, d *RocksDB, afterDisconnect boo
{dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr3e, d.chainParser), "020102", nil},
{
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser),
"020100" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + varuintToHex(1<<2+uint(bchain.ERC20)) + bigintFromStringToHex("10000000000000000000000"), nil,
"020100" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + varuintToHex(1<<2+uint(bchain.FungibleToken)) + bigintFromStringToHex("10000000000000000000000"), nil,
},
{
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr20, d.chainParser),
"010100" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + varuintToHex(1<<2+uint(bchain.ERC20)) + bigintToHex(big.NewInt(0)), nil,
"010100" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + varuintToHex(1<<2+uint(bchain.FungibleToken)) + bigintToHex(big.NewInt(0)), nil,
},
{dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr9f, d.chainParser), "010002", nil},
{dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser), "010101", nil},
@ -72,6 +72,25 @@ func verifyAfterEthereumTypeBlock1(t *testing.T, d *RocksDB, afterDisconnect boo
}
}
var destructedInBlock uint
if afterDisconnect {
destructedInBlock = 44445
}
if err := checkColumn(d, cfContracts, []keyPair{
{
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser),
"0b436f6e7472616374203734" + // Contract 74
"03533734" + // S74
"054552433230" + // ERC20
varuintToHex(12) + varuintToHex(44444) + varuintToHex(destructedInBlock),
nil,
},
}); err != nil {
{
t.Fatal(err)
}
}
if err := checkColumn(d, cfInternalData, []keyPair{
{
dbtestdata.EthTxidB1T2,
@ -98,7 +117,7 @@ func verifyAfterEthereumTypeBlock1(t *testing.T, d *RocksDB, afterDisconnect boo
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr3e, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser) + "00" +
dbtestdata.EthTxidB1T2 +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr20, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) +
"01" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr20, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + varuintToHex(uint(bchain.ERC20)) + bigintFromStringToHex("10000000000000000000000"),
"01" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr20, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + varuintToHex(uint(bchain.FungibleToken)) + bigintFromStringToHex("10000000000000000000000"),
nil,
},
}
@ -158,43 +177,43 @@ func verifyAfterEthereumTypeBlock2(t *testing.T, d *RocksDB, wantBlockInternalDa
if err := checkColumn(d, cfAddressContracts, []keyPair{
{
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr20, d.chainParser),
"010100" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + varuintToHex(1<<2+uint(bchain.ERC20)) + bigintToHex(big.NewInt(0)), nil,
"010100" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + varuintToHex(1<<2+uint(bchain.FungibleToken)) + bigintToHex(big.NewInt(0)), nil,
},
{
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr3e, d.chainParser),
"030202" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract6f, d.chainParser) + varuintToHex(1<<2+uint(bchain.ERC1155)) + varuintToHex(1) + bigintFromStringToHex("150") + bigintFromStringToHex("1"), nil,
"030202" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract6f, d.chainParser) + varuintToHex(1<<2+uint(bchain.MultiToken)) + varuintToHex(1) + bigintFromStringToHex("150") + bigintFromStringToHex("1"), nil,
},
{
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr4b, d.chainParser),
"010101" +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract0d, d.chainParser) + varuintToHex(2<<2+uint(bchain.ERC20)) + bigintFromStringToHex("8086") +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + varuintToHex(2<<2+uint(bchain.ERC20)) + bigintFromStringToHex("871180000950184"), nil,
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract0d, d.chainParser) + varuintToHex(2<<2+uint(bchain.FungibleToken)) + bigintFromStringToHex("8086") +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + varuintToHex(2<<2+uint(bchain.FungibleToken)) + bigintFromStringToHex("871180000950184"), nil,
},
{
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser),
"050300" +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + varuintToHex(2<<2+uint(bchain.ERC20)) + bigintFromStringToHex("10000000854307892726464") +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract0d, d.chainParser) + varuintToHex(1<<2+uint(bchain.ERC20)) + bigintFromStringToHex("0") +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser) + varuintToHex(1<<2+uint(bchain.ERC20)) + bigintFromStringToHex("0"), nil,
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + varuintToHex(2<<2+uint(bchain.FungibleToken)) + bigintFromStringToHex("10000000854307892726464") +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract0d, d.chainParser) + varuintToHex(1<<2+uint(bchain.FungibleToken)) + bigintFromStringToHex("0") +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser) + varuintToHex(1<<2+uint(bchain.FungibleToken)) + bigintFromStringToHex("0"), nil,
},
{
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr5d, d.chainParser),
"010100" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract6f, d.chainParser) + varuintToHex(1<<2+uint(bchain.ERC1155)) + varuintToHex(2) + bigintFromStringToHex("1776") + bigintFromStringToHex("1") + bigintFromStringToHex("1898") + bigintFromStringToHex("10"), nil,
"010100" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract6f, d.chainParser) + varuintToHex(1<<2+uint(bchain.MultiToken)) + varuintToHex(2) + bigintFromStringToHex("1776") + bigintFromStringToHex("1") + bigintFromStringToHex("1898") + bigintFromStringToHex("10"), nil,
},
{
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr7b, d.chainParser),
"020000" +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + varuintToHex(1<<2+uint(bchain.ERC20)) + bigintFromStringToHex("0") +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract0d, d.chainParser) + varuintToHex(1<<2+uint(bchain.ERC20)) + bigintFromStringToHex("7674999999999991915") +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContractCd, d.chainParser) + varuintToHex(1<<2+uint(bchain.ERC721)) + varuintToHex(1) + bigintFromStringToHex("1"), nil,
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + varuintToHex(1<<2+uint(bchain.FungibleToken)) + bigintFromStringToHex("0") +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract0d, d.chainParser) + varuintToHex(1<<2+uint(bchain.FungibleToken)) + bigintFromStringToHex("7674999999999991915") +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContractCd, d.chainParser) + varuintToHex(1<<2+uint(bchain.NonFungibleToken)) + varuintToHex(1) + bigintFromStringToHex("1"), nil,
},
{
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr83, d.chainParser),
"010100" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContractCd, d.chainParser) + varuintToHex(1<<2+uint(bchain.ERC721)) + varuintToHex(0), nil,
"010100" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContractCd, d.chainParser) + varuintToHex(1<<2+uint(bchain.NonFungibleToken)) + varuintToHex(0), nil,
},
{
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrA3, d.chainParser),
"010000" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract6f, d.chainParser) + varuintToHex(1<<2+uint(bchain.ERC1155)) + varuintToHex(0), nil,
"010000" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract6f, d.chainParser) + varuintToHex(1<<2+uint(bchain.MultiToken)) + varuintToHex(0), nil,
},
{dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr92, d.chainParser), "010100", nil},
{dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr9f, d.chainParser), "030104", nil},
@ -209,6 +228,21 @@ func verifyAfterEthereumTypeBlock2(t *testing.T, d *RocksDB, wantBlockInternalDa
}
}
if err := checkColumn(d, cfContracts, []keyPair{
{
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser),
"0b436f6e7472616374203734" + // Contract 74
"03533734" + // S74
"054552433230" + // ERC20
varuintToHex(12) + varuintToHex(44444) + varuintToHex(44445),
nil,
},
}); err != nil {
{
t.Fatal(err)
}
}
if err := checkColumn(d, cfInternalData, []keyPair{
{
dbtestdata.EthTxidB1T2,
@ -243,22 +277,22 @@ func verifyAfterEthereumTypeBlock2(t *testing.T, d *RocksDB, wantBlockInternalDa
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr9f, d.chainParser) + "00" +
dbtestdata.EthTxidB2T2 +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr4b, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract47, d.chainParser) +
"04" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr4b, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract0d, d.chainParser) + varuintToHex(uint(bchain.ERC20)) + bigintFromStringToHex("7675000000000000001") +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr4b, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + varuintToHex(uint(bchain.ERC20)) + bigintFromStringToHex("854307892726464") +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr7b, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr4b, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + varuintToHex(uint(bchain.ERC20)) + bigintFromStringToHex("871180000950184") +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr4b, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr7b, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract0d, d.chainParser) + varuintToHex(uint(bchain.ERC20)) + bigintFromStringToHex("7674999999999991915") +
"04" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr4b, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract0d, d.chainParser) + varuintToHex(uint(bchain.FungibleToken)) + bigintFromStringToHex("7675000000000000001") +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr4b, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + varuintToHex(uint(bchain.FungibleToken)) + bigintFromStringToHex("854307892726464") +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr7b, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr4b, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + varuintToHex(uint(bchain.FungibleToken)) + bigintFromStringToHex("871180000950184") +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr4b, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr7b, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract0d, d.chainParser) + varuintToHex(uint(bchain.FungibleToken)) + bigintFromStringToHex("7674999999999991915") +
dbtestdata.EthTxidB2T3 +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr83, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContractCd, d.chainParser) +
"01" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr83, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr7b, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContractCd, d.chainParser) + varuintToHex(uint(bchain.ERC721)) + bigintFromStringToHex("1") +
"01" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr83, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr7b, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContractCd, d.chainParser) + varuintToHex(uint(bchain.NonFungibleToken)) + bigintFromStringToHex("1") +
dbtestdata.EthTxidB2T4 +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr3e, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr92, d.chainParser) +
"01" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrA3, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr3e, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract6f, d.chainParser) + varuintToHex(uint(bchain.ERC1155)) + "01" + bigintFromStringToHex("150") + bigintFromStringToHex("1") +
"01" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrA3, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr3e, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract6f, d.chainParser) + varuintToHex(uint(bchain.MultiToken)) + "01" + bigintFromStringToHex("150") + bigintFromStringToHex("1") +
dbtestdata.EthTxidB2T5 +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr5d, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract6f, d.chainParser) +
"01" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrZero, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr5d, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract6f, d.chainParser) + varuintToHex(uint(bchain.ERC1155)) + "02" + bigintFromStringToHex("1776") + bigintFromStringToHex("1") + bigintFromStringToHex("1898") + bigintFromStringToHex("10") +
"01" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrZero, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr5d, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract6f, d.chainParser) + varuintToHex(uint(bchain.MultiToken)) + "02" + bigintFromStringToHex("1776") + bigintFromStringToHex("1") + bigintFromStringToHex("1898") + bigintFromStringToHex("10") +
dbtestdata.EthTxidB2T6 +
dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser) +
"01" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser) + varuintToHex(uint(bchain.ERC20)) + bigintFromStringToHex("10000000000000000000000"),
"01" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser) + varuintToHex(uint(bchain.FungibleToken)) + bigintFromStringToHex("10000000000000000000000"),
nil,
},
}); err != nil {
@ -722,13 +756,13 @@ func Test_packUnpackAddrContracts(t *testing.T) {
InternalTxs: 8873,
Contracts: []AddrContract{
{
Type: bchain.ERC20,
Type: bchain.FungibleToken,
Contract: addressToAddrDesc(dbtestdata.EthAddrContract0d, parser),
Txs: 8,
Value: *big.NewInt(793201132),
},
{
Type: bchain.ERC721,
Type: bchain.NonFungibleToken,
Contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser),
Txs: 41235,
Ids: []big.Int{
@ -740,10 +774,10 @@ func Test_packUnpackAddrContracts(t *testing.T) {
},
},
{
Type: bchain.ERC1155,
Type: bchain.MultiToken,
Contract: addressToAddrDesc(dbtestdata.EthAddrContract4a, parser),
Txs: 64,
IdValues: []bchain.TokenTransferIdValue{
MultiTokenValues: []bchain.MultiTokenValue{
{
Id: *big.NewInt(1),
Value: *big.NewInt(1412341234),
@ -796,7 +830,7 @@ func Test_addToContracts(t *testing.T) {
index: 1,
contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser),
transfer: &bchain.TokenTransfer{
Type: bchain.ERC20,
Type: bchain.FungibleToken,
Value: *big.NewInt(123456),
},
addTxCount: true,
@ -805,7 +839,7 @@ func Test_addToContracts(t *testing.T) {
wantAddrContracts: &AddrContracts{
Contracts: []AddrContract{
{
Type: bchain.ERC20,
Type: bchain.FungibleToken,
Contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser),
Txs: 1,
Value: *big.NewInt(123456),
@ -819,7 +853,7 @@ func Test_addToContracts(t *testing.T) {
index: ^1,
contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser),
transfer: &bchain.TokenTransfer{
Type: bchain.ERC20,
Type: bchain.FungibleToken,
Value: *big.NewInt(23456),
},
addTxCount: true,
@ -828,7 +862,7 @@ func Test_addToContracts(t *testing.T) {
wantAddrContracts: &AddrContracts{
Contracts: []AddrContract{
{
Type: bchain.ERC20,
Type: bchain.FungibleToken,
Contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser),
Value: *big.NewInt(100000),
Txs: 2,
@ -842,7 +876,7 @@ func Test_addToContracts(t *testing.T) {
index: 1,
contract: addressToAddrDesc(dbtestdata.EthAddrContract6f, parser),
transfer: &bchain.TokenTransfer{
Type: bchain.ERC721,
Type: bchain.NonFungibleToken,
Value: *big.NewInt(1),
},
addTxCount: true,
@ -851,13 +885,13 @@ func Test_addToContracts(t *testing.T) {
wantAddrContracts: &AddrContracts{
Contracts: []AddrContract{
{
Type: bchain.ERC20,
Type: bchain.FungibleToken,
Contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser),
Value: *big.NewInt(100000),
Txs: 2,
},
{
Type: bchain.ERC721,
Type: bchain.NonFungibleToken,
Contract: addressToAddrDesc(dbtestdata.EthAddrContract6f, parser),
Txs: 1,
Ids: []big.Int{*big.NewInt(1)},
@ -871,7 +905,7 @@ func Test_addToContracts(t *testing.T) {
index: 1,
contract: addressToAddrDesc(dbtestdata.EthAddrContract6f, parser),
transfer: &bchain.TokenTransfer{
Type: bchain.ERC721,
Type: bchain.NonFungibleToken,
Value: *big.NewInt(2),
},
addTxCount: true,
@ -880,13 +914,13 @@ func Test_addToContracts(t *testing.T) {
wantAddrContracts: &AddrContracts{
Contracts: []AddrContract{
{
Type: bchain.ERC20,
Type: bchain.FungibleToken,
Contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser),
Value: *big.NewInt(100000),
Txs: 2,
},
{
Type: bchain.ERC721,
Type: bchain.NonFungibleToken,
Contract: addressToAddrDesc(dbtestdata.EthAddrContract6f, parser),
Txs: 2,
Ids: []big.Int{*big.NewInt(1), *big.NewInt(2)},
@ -900,7 +934,7 @@ func Test_addToContracts(t *testing.T) {
index: ^1,
contract: addressToAddrDesc(dbtestdata.EthAddrContract6f, parser),
transfer: &bchain.TokenTransfer{
Type: bchain.ERC721,
Type: bchain.NonFungibleToken,
Value: *big.NewInt(1),
},
addTxCount: false,
@ -909,13 +943,13 @@ func Test_addToContracts(t *testing.T) {
wantAddrContracts: &AddrContracts{
Contracts: []AddrContract{
{
Type: bchain.ERC20,
Type: bchain.FungibleToken,
Contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser),
Value: *big.NewInt(100000),
Txs: 2,
},
{
Type: bchain.ERC721,
Type: bchain.NonFungibleToken,
Contract: addressToAddrDesc(dbtestdata.EthAddrContract6f, parser),
Txs: 2,
Ids: []big.Int{*big.NewInt(2)},
@ -929,8 +963,8 @@ func Test_addToContracts(t *testing.T) {
index: 1,
contract: addressToAddrDesc(dbtestdata.EthAddrContractCd, parser),
transfer: &bchain.TokenTransfer{
Type: bchain.ERC1155,
IdValues: []bchain.TokenTransferIdValue{
Type: bchain.MultiToken,
MultiTokenValues: []bchain.MultiTokenValue{
{
Id: *big.NewInt(11),
Value: *big.NewInt(56789),
@ -943,22 +977,22 @@ func Test_addToContracts(t *testing.T) {
wantAddrContracts: &AddrContracts{
Contracts: []AddrContract{
{
Type: bchain.ERC20,
Type: bchain.FungibleToken,
Contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser),
Value: *big.NewInt(100000),
Txs: 2,
},
{
Type: bchain.ERC721,
Type: bchain.NonFungibleToken,
Contract: addressToAddrDesc(dbtestdata.EthAddrContract6f, parser),
Txs: 2,
Ids: []big.Int{*big.NewInt(2)},
},
{
Type: bchain.ERC1155,
Type: bchain.MultiToken,
Contract: addressToAddrDesc(dbtestdata.EthAddrContractCd, parser),
Txs: 1,
IdValues: []bchain.TokenTransferIdValue{
MultiTokenValues: []bchain.MultiTokenValue{
{
Id: *big.NewInt(11),
Value: *big.NewInt(56789),
@ -974,8 +1008,8 @@ func Test_addToContracts(t *testing.T) {
index: 1,
contract: addressToAddrDesc(dbtestdata.EthAddrContractCd, parser),
transfer: &bchain.TokenTransfer{
Type: bchain.ERC1155,
IdValues: []bchain.TokenTransferIdValue{
Type: bchain.MultiToken,
MultiTokenValues: []bchain.MultiTokenValue{
{
Id: *big.NewInt(11),
Value: *big.NewInt(111),
@ -992,22 +1026,22 @@ func Test_addToContracts(t *testing.T) {
wantAddrContracts: &AddrContracts{
Contracts: []AddrContract{
{
Type: bchain.ERC20,
Type: bchain.FungibleToken,
Contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser),
Value: *big.NewInt(100000),
Txs: 2,
},
{
Type: bchain.ERC721,
Type: bchain.NonFungibleToken,
Contract: addressToAddrDesc(dbtestdata.EthAddrContract6f, parser),
Txs: 2,
Ids: []big.Int{*big.NewInt(2)},
},
{
Type: bchain.ERC1155,
Type: bchain.MultiToken,
Contract: addressToAddrDesc(dbtestdata.EthAddrContractCd, parser),
Txs: 2,
IdValues: []bchain.TokenTransferIdValue{
MultiTokenValues: []bchain.MultiTokenValue{
{
Id: *big.NewInt(11),
Value: *big.NewInt(56900),
@ -1027,8 +1061,8 @@ func Test_addToContracts(t *testing.T) {
index: ^1,
contract: addressToAddrDesc(dbtestdata.EthAddrContractCd, parser),
transfer: &bchain.TokenTransfer{
Type: bchain.ERC1155,
IdValues: []bchain.TokenTransferIdValue{
Type: bchain.MultiToken,
MultiTokenValues: []bchain.MultiTokenValue{
{
Id: *big.NewInt(11),
Value: *big.NewInt(112),
@ -1045,22 +1079,22 @@ func Test_addToContracts(t *testing.T) {
wantAddrContracts: &AddrContracts{
Contracts: []AddrContract{
{
Type: bchain.ERC20,
Type: bchain.FungibleToken,
Contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser),
Value: *big.NewInt(100000),
Txs: 2,
},
{
Type: bchain.ERC721,
Type: bchain.NonFungibleToken,
Contract: addressToAddrDesc(dbtestdata.EthAddrContract6f, parser),
Txs: 2,
Ids: []big.Int{*big.NewInt(2)},
},
{
Type: bchain.ERC1155,
Type: bchain.MultiToken,
Contract: addressToAddrDesc(dbtestdata.EthAddrContractCd, parser),
Txs: 3,
IdValues: []bchain.TokenTransferIdValue{
MultiTokenValues: []bchain.MultiTokenValue{
{
Id: *big.NewInt(11),
Value: *big.NewInt(56788),
@ -1119,7 +1153,7 @@ func Test_packUnpackBlockTx(t *testing.T) {
from: addressToAddrDesc(dbtestdata.EthAddr20, parser),
to: addressToAddrDesc(dbtestdata.EthAddr5d, parser),
contract: addressToAddrDesc(dbtestdata.EthAddrContract4a, parser),
transferType: bchain.ERC20,
transferType: bchain.FungibleToken,
value: *big.NewInt(10000),
},
},
@ -1137,22 +1171,22 @@ func Test_packUnpackBlockTx(t *testing.T) {
from: addressToAddrDesc(dbtestdata.EthAddr20, parser),
to: addressToAddrDesc(dbtestdata.EthAddr3e, parser),
contract: addressToAddrDesc(dbtestdata.EthAddrContract4a, parser),
transferType: bchain.ERC20,
transferType: bchain.FungibleToken,
value: *big.NewInt(987654321),
},
{
from: addressToAddrDesc(dbtestdata.EthAddr4b, parser),
to: addressToAddrDesc(dbtestdata.EthAddr55, parser),
contract: addressToAddrDesc(dbtestdata.EthAddrContract6f, parser),
transferType: bchain.ERC721,
transferType: bchain.NonFungibleToken,
value: *big.NewInt(13),
},
{
from: addressToAddrDesc(dbtestdata.EthAddr5d, parser),
to: addressToAddrDesc(dbtestdata.EthAddr7b, parser),
contract: addressToAddrDesc(dbtestdata.EthAddrContractCd, parser),
transferType: bchain.ERC1155,
idValues: []bchain.TokenTransferIdValue{
transferType: bchain.MultiToken,
idValues: []bchain.MultiTokenValue{
{
Id: *big.NewInt(1234),
Value: *big.NewInt(98765),
@ -1222,3 +1256,45 @@ func Test_packUnpackFourByteSignature(t *testing.T) {
})
}
}
func Test_packUnpackContractInfo(t *testing.T) {
tests := []struct {
name string
contractInfo bchain.ContractInfo
}{
{
name: "empty",
contractInfo: bchain.ContractInfo{},
},
{
name: "unknown",
contractInfo: bchain.ContractInfo{
Type: bchain.UnknownTokenType,
Name: "Test contract",
Symbol: "TCT",
Decimals: 18,
CreatedInBlock: 1234567,
DestructedInBlock: 234567890,
},
},
{
name: "ERC20",
contractInfo: bchain.ContractInfo{
Type: bchain.ERC20TokenType,
Name: "GreenContract🟢",
Symbol: "🟢",
Decimals: 0,
CreatedInBlock: 1,
DestructedInBlock: 2,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
buf := packContractInfo(&tt.contractInfo)
if got, err := unpackContractInfo(buf); !reflect.DeepEqual(*got, tt.contractInfo) || err != nil {
t.Errorf("packUnpackContractInfo() = %v, want %v, error %v", *got, tt.contractInfo, err)
}
})
}
}

View File

@ -562,7 +562,7 @@ func isOwnAddress(td *TemplateData, a string) bool {
}
// called from template, returns count of token transfers of given type in a tx
func tokenTransfersCount(tx *api.Tx, t api.TokenType) int {
func tokenTransfersCount(tx *api.Tx, t bchain.TokenTypeName) int {
count := 0
for i := range tx.TokenTransfers {
if tx.TokenTransfers[i].Type == t {
@ -573,7 +573,7 @@ func tokenTransfersCount(tx *api.Tx, t api.TokenType) int {
}
// called from template, returns count of tokens in array of given type
func tokenCount(tokens []api.Token, t api.TokenType) int {
func tokenCount(tokens []api.Token, t bchain.TokenTypeName) int {
count := 0
for i := range tokens {
if tokens[i].Type == t {

File diff suppressed because one or more lines are too long

View File

@ -896,7 +896,7 @@ func (s *WebsocketServer) sendOnNewTxAddr(stringAddressDescriptor string, tx *ap
}
func (s *WebsocketServer) getNewTxSubscriptions(tx *bchain.MempoolTx) map[string]struct{} {
// check if there is any subscription in inputs, outputs and erc20
// check if there is any subscription in inputs, outputs and token transfers
s.addressSubscriptionsLock.Lock()
defer s.addressSubscriptionsLock.Unlock()
subscribed := make(map[string]struct{})

View File

@ -1,5 +1,5 @@
{{define "specific"}}{{$cs := .CoinShortcut}}{{$addr := .Address}}{{$data := .}}
<h1>{{if $addr.Erc20Contract}}Contract {{$addr.Erc20Contract.Name}} ({{$addr.Erc20Contract.Symbol}}){{else}}Address{{end}} <small class="text-muted">{{formatAmount $addr.BalanceSat}} {{$cs}}</small>
<h1>{{if $addr.ContractInfo}}Contract {{$addr.ContractInfo.Name}} ({{$addr.ContractInfo.Symbol}}){{else}}Address{{end}} <small class="text-muted">{{formatAmount $addr.BalanceSat}} {{$cs}}</small>
</h1>
<div class="alert alert-data ellipsis">
<span class="data">{{$addr.AddrStr}}</span>
@ -98,7 +98,7 @@
<tr>
<td class="data ellipsis">{{if $t.Contract}}<a href="/address/{{$t.Contract}}">{{$t.Name}}</a>{{else}}{{$t.Name}}{{end}}</td>
<td class="data">
{{range $i, $iv := $t.IdValues}}{{if $i}}, {{end}}{{formatAmountWithDecimals $iv.Id 0}}:{{formatAmountWithDecimals $iv.Value 0}} {{$t.Symbol}}{{end}}
{{range $i, $iv := $t.MultiTokenValues}}{{if $i}}, {{end}}{{formatAmountWithDecimals $iv.Id 0}}:{{formatAmountWithDecimals $iv.Value 0}} {{$t.Symbol}}{{end}}
</td>
<td class="data">{{$t.Transfers}}</td>
</tr>

View File

@ -267,7 +267,7 @@
</div>
</div>
<div class="col-md-3 text-right" style="padding: .4rem 0;">
{{- range $iv := $tt.Values -}}
{{- range $iv := $tt.MultiTokenValues -}}
{{formatAmountWithDecimals $iv.Id 0}}:{{formatAmountWithDecimals $iv.Value 0}} {{$tt.Symbol}}
{{- end -}}
</div>

View File

@ -128,6 +128,19 @@ var EthTx4InternalData = &bchain.EthereumInternalData{
},
}
var Block1SpecificData = &bchain.EthereumBlockSpecificData{
Contracts: []bchain.ContractInfo{
{
Contract: EthAddrContract4a,
Type: bchain.ERC20TokenType,
Name: "Contract 74",
Symbol: "S74",
Decimals: 12,
CreatedInBlock: 44444,
},
},
}
var Block2SpecificData = &bchain.EthereumBlockSpecificData{
InternalDataError: "test error",
AddressAliasRecords: []bchain.AddressAliasRecord{
@ -140,6 +153,12 @@ var Block2SpecificData = &bchain.EthereumBlockSpecificData{
Name: "address20",
},
},
Contracts: []bchain.ContractInfo{
{
Contract: EthAddrContract4a,
DestructedInBlock: 44445,
},
},
}
type packedAndInternal struct {
@ -182,6 +201,7 @@ func GetTestEthereumTypeBlock1(parser bchain.BlockChainParser) *bchain.Block {
packed: EthTx2Packed,
internal: EthTx2InternalData,
}}, parser),
CoinSpecificData: Block1SpecificData,
}
}

View File

@ -117,13 +117,15 @@ func (c *fakeBlockChainEthereumType) EthereumTypeGetNonce(addrDesc bchain.Addres
return uint64(addrDesc[0]), nil
}
func (c *fakeBlockChainEthereumType) EthereumTypeGetErc20ContractInfo(contractDesc bchain.AddressDescriptor) (*bchain.Erc20Contract, error) {
func (c *fakeBlockChainEthereumType) GetContractInfo(contractDesc bchain.AddressDescriptor) (*bchain.ContractInfo, error) {
addresses, _, _ := c.Parser.GetAddressesFromAddrDesc(contractDesc)
return &bchain.Erc20Contract{
Contract: addresses[0],
Name: "Contract " + strconv.Itoa(int(contractDesc[0])),
Symbol: "S" + strconv.Itoa(int(contractDesc[0])),
Decimals: 18,
return &bchain.ContractInfo{
Type: bchain.ERC20TokenType,
Contract: addresses[0],
Name: "Contract " + strconv.Itoa(int(contractDesc[0])),
Symbol: "S" + strconv.Itoa(int(contractDesc[0])),
Decimals: 18,
CreatedInBlock: 12345,
}, nil
}