diff --git a/api/types.go b/api/types.go index ce1ecd47..3e7ffed5 100644 --- a/api/types.go +++ b/api/types.go @@ -138,11 +138,20 @@ type Vout struct { // TokenType specifies type of token type TokenType string -// ERC20TokenType is Ethereum ERC20 token -const ERC20TokenType TokenType = "ERC20" +// Token types +const ( + // Ethereum token types + ERC20TokenType TokenType = "ERC20" + ERC771TokenType TokenType = "ERC721" + ERC1155TokenType TokenType = "ERC1155" -// XPUBAddressTokenType is address derived from xpub -const XPUBAddressTokenType TokenType = "XPUBAddress" + // 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} // Token contains info about tokens held by an address type Token struct { @@ -159,16 +168,23 @@ type Token struct { ContractIndex string `json:"-"` } +// TokenTransferValues contains values for ERC1155 contract +type TokenTransferValues struct { + Id *Amount `json:"id,omitempty"` + Value *Amount `json:"value,omitempty"` +} + // 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"` + 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"` } // EthereumSpecific contains ethereum specific transaction data diff --git a/api/worker.go b/api/worker.go index ccd54a43..19583aa9 100644 --- a/api/worker.go +++ b/api/worker.go @@ -255,11 +255,11 @@ func (w *Worker) GetTransactionFromBchainTx(bchainTx *bchain.Tx, height int, spe } pValInSat = &valInSat } else if w.chainType == bchain.ChainEthereumType { - ets, err := w.chainParser.EthereumTypeGetErc20FromTx(bchainTx) + tokenTransfers, err := w.chainParser.EthereumTypeGetTokenTransfersFromTx(bchainTx) if err != nil { - glog.Errorf("GetErc20FromTx error %v, %v", err, bchainTx) + glog.Errorf("GetTokenTransfersFromTx error %v, %v", err, bchainTx) } - tokens = w.getTokensFromErc20(ets) + tokens = w.getEthereumTokensTransfers(tokenTransfers) ethTxData := eth.GetEthereumTxData(bchainTx) // mempool txs do not have fees yet if ethTxData.GasUsed != nil { @@ -381,7 +381,7 @@ func (w *Worker) GetTransactionFromMempoolTx(mempoolTx *bchain.MempoolTx) (*Tx, if len(mempoolTx.Vout) > 0 { valOutSat = mempoolTx.Vout[0].ValueSat } - tokens = w.getTokensFromErc20(mempoolTx.Erc20) + tokens = w.getEthereumTokensTransfers(mempoolTx.TokenTransfers) ethTxData := eth.GetEthereumTxDataFromSpecificData(mempoolTx.CoinSpecificData) ethSpecific = &EthereumSpecific{ GasLimit: ethTxData.GasLimit, @@ -410,29 +410,30 @@ func (w *Worker) GetTransactionFromMempoolTx(mempoolTx *bchain.MempoolTx) (*Tx, return r, nil } -func (w *Worker) getTokensFromErc20(erc20 []bchain.Erc20Transfer) []TokenTransfer { - tokens := make([]TokenTransfer, len(erc20)) - for i := range erc20 { - e := &erc20[i] - cd, err := w.chainParser.GetAddrDescFromAddress(e.Contract) +func (w *Worker) getEthereumTokensTransfers(transfers bchain.TokenTransfers) []TokenTransfer { + sort.Sort(transfers) + tokens := make([]TokenTransfer, len(transfers)) + for i := range transfers { + t := transfers[i] + cd, err := w.chainParser.GetAddrDescFromAddress(t.Contract) if err != nil { - glog.Errorf("GetAddrDescFromAddress error %v, contract %v", err, e.Contract) + glog.Errorf("GetAddrDescFromAddress error %v, contract %v", err, t.Contract) continue } erc20c, err := w.chain.EthereumTypeGetErc20ContractInfo(cd) if err != nil { - glog.Errorf("GetErc20ContractInfo error %v, contract %v", err, e.Contract) + glog.Errorf("GetErc20ContractInfo error %v, contract %v", err, t.Contract) } if erc20c == nil { - erc20c = &bchain.Erc20Contract{Name: e.Contract} + erc20c = &bchain.Erc20Contract{Name: t.Contract} } tokens[i] = TokenTransfer{ - Type: ERC20TokenType, - Token: e.Contract, - From: e.From, - To: e.To, + Type: TokenTypeMap[t.Type], + Token: t.Contract, + From: t.From, + To: t.To, Decimals: erc20c.Decimals, - Value: (*Amount)(&e.Tokens), + Value: (*Amount)(&t.Value), Name: erc20c.Name, Symbol: erc20c.Symbol, } diff --git a/bchain/baseparser.go b/bchain/baseparser.go index 0f1ebe57..3f342a53 100644 --- a/bchain/baseparser.go +++ b/bchain/baseparser.go @@ -300,7 +300,7 @@ func (p *BaseParser) DeriveAddressDescriptorsFromTo(descriptor *XpubDescriptor, return nil, errors.New("Not supported") } -// EthereumTypeGetErc20FromTx is unsupported -func (p *BaseParser) EthereumTypeGetErc20FromTx(tx *Tx) ([]Erc20Transfer, error) { +// EthereumTypeGetTokenTransfersFromTx is unsupported +func (p *BaseParser) EthereumTypeGetTokenTransfersFromTx(tx *Tx) (TokenTransfers, error) { return nil, errors.New("Not supported") } diff --git a/bchain/coins/eth/contract.go b/bchain/coins/eth/contract.go new file mode 100644 index 00000000..11fe89d0 --- /dev/null +++ b/bchain/coins/eth/contract.go @@ -0,0 +1,329 @@ +package eth + +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" +) + +const erc20TransferMethodSignature = "0xa9059cbb" // transfer(address,uint256) +const erc721TransferFromMethodSignature = "0x23b872dd" // transferFrom(address,address,uint256) +const erc721SafeTransferFromMethodSignature = "0x42842e0e" // safeTransferFrom(address,address,uint256) +const erc721SafeTransferFromWithDataMethodSignature = "0xb88d4fde" // safeTransferFrom(address,address,uint256,bytes) + +const tokenTransferEventSignature = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" +const tokenERC1155TransferSingleEventSignature = "0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62" +const tokenERC1155TransferBatchEventSignature = "0x4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb" + +const contractNameSignature = "0x06fdde03" +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 + if has0xPrefix(s) { + _, ok = t.SetString(s[2:], 16) + } else { + _, ok = t.SetString(s, 16) + } + if !ok { + return "", errors.New("Data is not a number") + } + a := ethcommon.BigToAddress(&t) + return a.String(), nil +} + +func processTransferEvent(l *bchain.RpcLog) (*bchain.TokenTransfer, error) { + tl := len(l.Topics) + var ttt bchain.TokenTransferType + var value big.Int + if tl == 3 { + ttt = bchain.ERC20 + _, 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 + _, ok := value.SetString(l.Topics[3], 0) + if !ok { + return nil, errors.New("ERC721 log Topics[3] is not a number") + } + } else { + return nil, nil + } + from, err := addressFromPaddedHex(l.Topics[1]) + if err != nil { + return nil, err + } + to, err := addressFromPaddedHex(l.Topics[2]) + if err != nil { + return nil, err + } + return &bchain.TokenTransfer{ + Type: ttt, + Contract: EIP55AddressFromAddress(l.Address), + From: EIP55AddressFromAddress(from), + To: EIP55AddressFromAddress(to), + Value: value, + }, nil +} + +func processERC1155TransferSingleEvent(l *bchain.RpcLog) (*bchain.TokenTransfer, error) { + from, err := addressFromPaddedHex(l.Topics[2]) + if err != nil { + return nil, err + } + to, err := addressFromPaddedHex(l.Topics[3]) + if err != nil { + return nil, err + } + var id, value big.Int + data := l.Data + if has0xPrefix(l.Data) { + data = data[2:] + } + _, ok := id.SetString(data[:64], 16) + if !ok { + return nil, errors.New("ERC1155 log Data id is not a number") + } + _, ok = value.SetString(data[64:128], 16) + if !ok { + 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}}, + }, nil +} + +func processERC1155TransferBatchEvent(l *bchain.RpcLog) (*bchain.TokenTransfer, error) { + from, err := addressFromPaddedHex(l.Topics[2]) + if err != nil { + return nil, err + } + to, err := addressFromPaddedHex(l.Topics[3]) + if err != nil { + return nil, err + } + data := l.Data + if has0xPrefix(l.Data) { + data = data[2:] + } + var b big.Int + _, ok := b.SetString(data[:64], 16) + if !ok || !b.IsInt64() { + return nil, errors.New("ERC1155 TransferBatch, not a number") + } + offsetIds := int(b.Int64()) * 2 + _, ok = b.SetString(data[64:128], 16) + if !ok || !b.IsInt64() { + return nil, errors.New("ERC1155 TransferBatch, not a number") + } + offsetValues := int(b.Int64()) * 2 + _, ok = b.SetString(data[offsetIds:offsetIds+64], 16) + if !ok || !b.IsInt64() { + return nil, errors.New("ERC1155 TransferBatch, not a number") + } + countIds := int(b.Int64()) + _, ok = b.SetString(data[offsetValues:offsetValues+64], 16) + if !ok || !b.IsInt64() { + return nil, errors.New("ERC1155 TransferBatch, not a number") + } + countValues := int(b.Int64()) + if countIds != countValues { + return nil, errors.New("ERC1155 TransferBatch, count values and ids does not match") + } + idValues := make([]bchain.TokenTransferIdValue, countValues) + for i := 0; i < countValues; i++ { + var id, value big.Int + o := offsetIds + 64 + 64*i + _, ok := id.SetString(data[o:o+64], 16) + if !ok { + return nil, errors.New("ERC1155 log Data id is not a number") + } + o = offsetValues + 64 + 64*i + _, ok = value.SetString(data[o:o+64], 16) + if !ok { + return nil, errors.New("ERC1155 log Data value is not a number") + } + idValues[i] = bchain.TokenTransferIdValue{Id: id, Value: value} + } + return &bchain.TokenTransfer{ + Type: bchain.ERC1155, + Contract: EIP55AddressFromAddress(l.Address), + From: EIP55AddressFromAddress(from), + To: EIP55AddressFromAddress(to), + IdValues: idValues, + }, nil +} +func contractGetTransfersFromLog(logs []*bchain.RpcLog) (bchain.TokenTransfers, error) { + var r bchain.TokenTransfers + var tt *bchain.TokenTransfer + var err error + for _, l := range logs { + tl := len(l.Topics) + if tl > 0 { + signature := l.Topics[0] + if signature == tokenTransferEventSignature { + tt, err = processTransferEvent(l) + } else if signature == tokenERC1155TransferSingleEventSignature && tl == 4 { + tt, err = processERC1155TransferSingleEvent(l) + } else if signature == tokenERC1155TransferBatchEventSignature { + tt, err = processERC1155TransferBatchEvent(l) + } else { + continue + } + if err != nil { + return nil, err + } + if tt != nil { + r = append(r, tt) + } + } + } + return r, nil +} + +func contractGetTransfersFromTx(tx *bchain.RpcTransaction) (bchain.TokenTransfers, error) { + var r bchain.TokenTransfers + if len(tx.Payload) == 10+128 && strings.HasPrefix(tx.Payload, erc20TransferMethodSignature) { + to, err := addressFromPaddedHex(tx.Payload[10 : 10+64]) + if err != nil { + return nil, err + } + var t big.Int + _, ok := t.SetString(tx.Payload[10+64:], 16) + if !ok { + return nil, errors.New("Data is not a number") + } + r = append(r, &bchain.TokenTransfer{ + Type: bchain.ERC20, + Contract: EIP55AddressFromAddress(tx.To), + From: EIP55AddressFromAddress(tx.From), + To: EIP55AddressFromAddress(to), + Value: t, + }) + } else if len(tx.Payload) >= 10+192 && + (strings.HasPrefix(tx.Payload, erc721TransferFromMethodSignature) || + strings.HasPrefix(tx.Payload, erc721SafeTransferFromMethodSignature) || + strings.HasPrefix(tx.Payload, erc721SafeTransferFromWithDataMethodSignature)) { + from, err := addressFromPaddedHex(tx.Payload[10 : 10+64]) + if err != nil { + return nil, err + } + to, err := addressFromPaddedHex(tx.Payload[10+64 : 10+128]) + if err != nil { + return nil, err + } + var t big.Int + _, ok := t.SetString(tx.Payload[10+128:10+192], 16) + if !ok { + return nil, errors.New("Data is not a number") + } + r = append(r, &bchain.TokenTransfer{ + Type: bchain.ERC721, + Contract: EIP55AddressFromAddress(tx.To), + From: EIP55AddressFromAddress(from), + To: EIP55AddressFromAddress(to), + Value: t, + }) + } + return r, nil +} + +func (b *EthereumRPC) ethCall(data, to string) (string, error) { + ctx, cancel := context.WithTimeout(context.Background(), b.timeout) + defer cancel() + var r string + err := b.rpc.CallContext(ctx, &r, "eth_call", map[string]interface{}{ + "data": data, + "to": to, + }, "latest") + if err != nil { + return "", err + } + 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() + } + return contract, nil +} + +// EthereumTypeGetErc20ContractBalance returns balance of ERC20 contract for given address +func (b *EthereumRPC) EthereumTypeGetErc20ContractBalance(addrDesc, contractDesc bchain.AddressDescriptor) (*big.Int, error) { + addr := EIP55Address(addrDesc) + contract := EIP55Address(contractDesc) + req := contractBalanceOf + "0000000000000000000000000000000000000000000000000000000000000000"[len(addr)-2:] + addr[2:] + data, err := b.ethCall(req, contract) + if err != nil { + return nil, err + } + r := parseSimpleNumericProperty(data) + if r == nil { + return nil, errors.New("Invalid balance") + } + return r, nil +} diff --git a/bchain/coins/eth/contract_test.go b/bchain/coins/eth/contract_test.go new file mode 100644 index 00000000..2efa1eae --- /dev/null +++ b/bchain/coins/eth/contract_test.go @@ -0,0 +1,291 @@ +//go:build unittest + +package eth + +import ( + "fmt" + "math/big" + "strings" + "testing" + + "github.com/trezor/blockbook/bchain" + "github.com/trezor/blockbook/tests/dbtestdata" +) + +func Test_contractGetTransfersFromLog(t *testing.T) { + tests := []struct { + name string + args []*bchain.RpcLog + want bchain.TokenTransfers + wantErr bool + }{ + { + name: "ERC20 transfer 1", + args: []*bchain.RpcLog{ + { + Address: "0x76a45e8976499ab9ae223cc584019341d5a84e96", + Topics: []string{ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x0000000000000000000000002aacf811ac1a60081ea39f7783c0d26c500871a8", + "0x000000000000000000000000e9a5216ff992cfa01594d43501a56e12769eb9d2", + }, + Data: "0x0000000000000000000000000000000000000000000000000000000000000123", + }, + }, + want: bchain.TokenTransfers{ + { + Contract: "0x76a45e8976499ab9ae223cc584019341d5a84e96", + From: "0x2aacf811ac1a60081ea39f7783c0d26c500871a8", + To: "0xe9a5216ff992cfa01594d43501a56e12769eb9d2", + Value: *big.NewInt(0x123), + }, + }, + }, + { + name: "ERC20 transfer 2", + args: []*bchain.RpcLog{ + { // Transfer + Address: "0x0d0f936ee4c93e25944694d6c121de94d9760f11", + Topics: []string{ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x0000000000000000000000006f44cceb49b4a5812d54b6f494fc2febf25511ed", + "0x0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d", + }, + Data: "0x0000000000000000000000000000000000000000000000006a8313d60b1f606b", + }, + { // Transfer + Address: "0xc778417e063141139fce010982780140aa0cd5ab", + Topics: []string{ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d", + "0x0000000000000000000000006f44cceb49b4a5812d54b6f494fc2febf25511ed", + }, + Data: "0x000000000000000000000000000000000000000000000000000308fd0e798ac0", + }, + { // not Transfer + Address: "0x479cc461fecd078f766ecc58533d6f69580cf3ac", + Topics: []string{ + "0x0d0b9391970d9a25552f37d436d2aae2925e2bfe1b2a923754bada030c498cb3", + "0x0000000000000000000000006f44cceb49b4a5812d54b6f494fc2febf25511ed", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x5af266c0a89a07c1917deaa024414577e6c3c31c8907d079e13eb448c082594f", + }, + Data: "0x0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d0000000000000", + }, + { // not Transfer + Address: "0x0d0f936ee4c93e25944694d6c121de94d9760f11", + Topics: []string{ + "0x0d0b9391970d9a25552f37d436d2aae2925e2bfe1b2a923754bada030c498cb3", + "0x0000000000000000000000007b62eb7fe80350dc7ec945c0b73242cb9877fb1b", + "0xb0b69dad58df6032c3b266e19b1045b19c87acd2c06fb0c598090f44b8e263aa", + }, + Data: "0x0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d000000000000000000000000c778417e063141139fce010982780140aa0cd5ab0000000000000000000000000d0f936ee4c93e25944694d6c121de94d9760f1100000000000000000000000000000000000000000000000000031855667df7a80000000000000000000000000000000000000000000000006a8313d60b1f800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + }, + }, + want: bchain.TokenTransfers{ + { + Contract: "0x0d0f936ee4c93e25944694d6c121de94d9760f11", + From: "0x6f44cceb49b4a5812d54b6f494fc2febf25511ed", + To: "0x4bda106325c335df99eab7fe363cac8a0ba2a24d", + Value: *big.NewInt(0x6a8313d60b1f606b), + }, + { + Contract: "0xc778417e063141139fce010982780140aa0cd5ab", + From: "0x4bda106325c335df99eab7fe363cac8a0ba2a24d", + To: "0x6f44cceb49b4a5812d54b6f494fc2febf25511ed", + Value: *big.NewInt(0x308fd0e798ac0), + }, + }, + }, + { + name: "ERC721 transfer 1", + args: []*bchain.RpcLog{ + { // Approval + Address: "0x5689b918D34C038901870105A6C7fc24744D31eB", + Topics: []string{ + "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925", + "0x0000000000000000000000000a206d4d5ff79cb5069def7fe3598421cff09391", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000001396", + }, + Data: "0x", + }, + { // Transfer + Address: "0x5689b918D34C038901870105A6C7fc24744D31eB", + Topics: []string{ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x0000000000000000000000000a206d4d5ff79cb5069def7fe3598421cff09391", + "0x0000000000000000000000006a016d7eec560549ffa0fbdb7f15c2b27302087f", + "0x0000000000000000000000000000000000000000000000000000000000001396", + }, + Data: "0x", + }, + { // OrdersMatched + Address: "0x7Be8076f4EA4A4AD08075C2508e481d6C946D12b", + Topics: []string{ + "0xc4109843e0b7d514e4c093114b863f8e7d8d9a458c372cd51bfe526b588006c9", + "0x0000000000000000000000000a206d4d5ff79cb5069def7fe3598421cff09391", + "0x0000000000000000000000006a016d7eec560549ffa0fbdb7f15c2b27302087f", + "0x0000000000000000000000000000000000000000000000000000000000000000", + }, + Data: "0x000000000000000000000000000000000000000000000000000000000000000069d3f0cc25f121f2aa96215f51ec4b4f1966f2d2ffbd3d8d8a45ad27b1c90323000000000000000000000000000000000000000000000000008e1bc9bf040000", + }, + }, + want: bchain.TokenTransfers{ + { + Type: bchain.ERC721, + Contract: "0x5689b918D34C038901870105A6C7fc24744D31eB", + From: "0x0a206d4d5ff79cb5069def7fe3598421cff09391", + To: "0x6a016d7eec560549ffa0fbdb7f15c2b27302087f", + Value: *big.NewInt(0x1396), + }, + }, + }, + { + name: "ERC1155 TransferSingle", + args: []*bchain.RpcLog{ + { // Transfer + Address: "0x6Fd712E3A5B556654044608F9129040A4839E36c", + Topics: []string{ + "0x5f9832c7244497a64c11c4a4f7597934bdf02b0361c54ad8e90091c2ce1f9e3c", + }, + Data: "0x000000000000000000000000a3950b823cb063dd9afc0d27f35008b805b3ed530000000000000000000000004392faf3bb96b5694ecc6ef64726f61cdd4bb0ec000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000009600000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001", + }, + { // TransferSingle + Address: "0x6Fd712E3A5B556654044608F9129040A4839E36c", + Topics: []string{ + "0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62", + "0x0000000000000000000000009248a6048a58db9f0212dc7cd85ee8741128be72", + "0x000000000000000000000000a3950b823cb063dd9afc0d27f35008b805b3ed53", + "0x0000000000000000000000004392faf3bb96b5694ecc6ef64726f61cdd4bb0ec", + }, + Data: "0x00000000000000000000000000000000000000000000000000000000000000960000000000000000000000000000000000000000000000000000000000000011", + }, + { // unknown + Address: "0x9248A6048a58db9f0212dC7CD85eE8741128be72", + Topics: []string{ + "0x0b7bef9468bee71526deef3cbbded0ec1a0aa3d5a3e81eaffb0e758552b33199", + }, + Data: "0x0000000000000000000000000000000000000000000000000000000000000060000000000000000000000000a3950b823cb063dd9afc0d27f35008b805b3ed530000000000000000000000004392faf3bb96b5694ecc6ef64726f61cdd4bb0ec0000000000000000000000000000000000000000000000000000000000000001", + }, + }, + want: bchain.TokenTransfers{ + { + Type: bchain.ERC1155, + Contract: "0x6Fd712E3A5B556654044608F9129040A4839E36c", + From: "0xa3950b823cb063dd9afc0d27f35008b805b3ed53", + To: "0x4392faf3bb96b5694ecc6ef64726f61cdd4bb0ec", + IdValues: []bchain.TokenTransferIdValue{{Id: *big.NewInt(150), Value: *big.NewInt(0x11)}}, + }, + }, + }, + { + name: "ERC1155 TransferBatch", + args: []*bchain.RpcLog{ + { // TransferBatch + Address: "0x6c42C26a081c2F509F8bb68fb7Ac3062311cCfB7", + Topics: []string{ + "0x4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb", + "0x0000000000000000000000005dc6288b35e0807a3d6feb89b3a2ff4ab773168e", + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000005dc6288b35e0807a3d6feb89b3a2ff4ab773168e", + }, + Data: "0x000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000006f0000000000000000000000000000000000000000000000000000000000000076a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000a", + }, + }, + want: bchain.TokenTransfers{ + { + Type: bchain.ERC1155, + Contract: "0x6c42c26a081c2f509f8bb68fb7ac3062311ccfb7", + From: "0x0000000000000000000000000000000000000000", + To: "0x5dc6288b35e0807a3d6feb89b3a2ff4ab773168e", + IdValues: []bchain.TokenTransferIdValue{ + {Id: *big.NewInt(1776), Value: *big.NewInt(1)}, + {Id: *big.NewInt(1898), Value: *big.NewInt(10)}, + }, + }, + }, + }} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := contractGetTransfersFromLog(tt.args) + if (err != nil) != tt.wantErr { + t.Errorf("contractGetTransfersFromLog error = %v, wantErr %v", err, tt.wantErr) + return + } + if len(got) != len(tt.want) { + t.Errorf("contractGetTransfersFromLog len not same, %+v, want %+v", got, tt.want) + } + for i := range got { + // the addresses could have different case + if strings.ToLower(fmt.Sprint(got[i])) != strings.ToLower(fmt.Sprint(tt.want[i])) { + t.Errorf("contractGetTransfersFromLog %d = %+v, want %+v", i, got[i], tt.want[i]) + } + + } + }) + } +} + +func Test_contractGetTransfersFromTx(t *testing.T) { + p := NewEthereumParser(1) + b1 := dbtestdata.GetTestEthereumTypeBlock1(p) + b2 := dbtestdata.GetTestEthereumTypeBlock2(p) + bn, _ := new(big.Int).SetString("21e19e0c9bab2400000", 16) + tests := []struct { + name string + args *bchain.RpcTransaction + want bchain.TokenTransfers + }{ + { + name: "no contract transfer", + args: (b1.Txs[0].CoinSpecificData.(bchain.EthereumSpecificData)).Tx, + want: bchain.TokenTransfers{}, + }, + { + name: "ERC20 transfer", + args: (b1.Txs[1].CoinSpecificData.(bchain.EthereumSpecificData)).Tx, + want: bchain.TokenTransfers{ + { + Type: bchain.ERC20, + Contract: "0x4af4114f73d1c1c903ac9e0361b379d1291808a2", + From: "0x20cd153de35d469ba46127a0c8f18626b59a256a", + To: "0x555ee11fbddc0e49a9bab358a8941ad95ffdb48f", + Value: *bn, + }, + }, + }, + { + name: "ERC721 transferFrom", + args: (b2.Txs[2].CoinSpecificData.(bchain.EthereumSpecificData)).Tx, + want: bchain.TokenTransfers{ + { + Type: bchain.ERC721, + Contract: "0xcda9fc258358ecaa88845f19af595e908bb7efe9", + From: "0x837e3f699d85a4b0b99894567e9233dfb1dcb081", + To: "0x7b62eb7fe80350dc7ec945c0b73242cb9877fb1b", + Value: *big.NewInt(1), + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := contractGetTransfersFromTx(tt.args) + if err != nil { + t.Errorf("contractGetTransfersFromTx error = %v", err) + return + } + if len(got) != len(tt.want) { + t.Errorf("contractGetTransfersFromTx len not same, %+v, want %+v", got, tt.want) + } + for i := range got { + // the addresses could have different case + if strings.ToLower(fmt.Sprint(got[i])) != strings.ToLower(fmt.Sprint(tt.want[i])) { + t.Errorf("contractGetTransfersFromTx %d = %+v, want %+v", i, got[i], tt.want[i]) + } + + } + }) + } +} diff --git a/bchain/coins/eth/dataparser.go b/bchain/coins/eth/dataparser.go new file mode 100644 index 00000000..399ef2d1 --- /dev/null +++ b/bchain/coins/eth/dataparser.go @@ -0,0 +1,58 @@ +package eth + +import ( + "bytes" + "encoding/hex" + "math/big" + "unicode/utf8" +) + +func parseSimpleNumericProperty(data string) *big.Int { + if has0xPrefix(data) { + data = data[2:] + } + if len(data) > 64 { + data = data[:64] + } + if len(data) == 64 { + var n big.Int + _, ok := n.SetString(data, 16) + if ok { + return &n + } + } + return nil +} + +func parseSimpleStringProperty(data string) string { + if has0xPrefix(data) { + data = data[2:] + } + if len(data) > 128 { + n := parseSimpleNumericProperty(data[64:128]) + if n != nil { + l := n.Uint64() + if l > 0 && 2*int(l) <= len(data)-128 { + b, err := hex.DecodeString(data[128 : 128+2*l]) + if err == nil { + return string(b) + } + } + } + } + // allow string properties as UTF-8 data + b, err := hex.DecodeString(data) + if err == nil { + i := bytes.Index(b, []byte{0}) + if i > 32 { + i = 32 + } + if i > 0 { + b = b[:i] + } + if utf8.Valid(b) { + return string(b) + } + } + return "" +} diff --git a/bchain/coins/eth/dataparser_test.go b/bchain/coins/eth/dataparser_test.go new file mode 100644 index 00000000..9af84c1f --- /dev/null +++ b/bchain/coins/eth/dataparser_test.go @@ -0,0 +1,53 @@ +//go:build unittest + +package eth + +import "testing" + +func Test_parseSimpleStringProperty(t *testing.T) { + tests := []struct { + name string + args string + want string + }{ + { + name: "1", + args: "0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000758504c4f44444500000000000000000000000000000000000000000000000000", + want: "XPLODDE", + }, + { + name: "2", + args: "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000022426974436c617665202d20436f6e73756d657220416374697669747920546f6b656e00000000000000", + want: "BitClave - Consumer Activity Token", + }, + { + name: "short", + args: "0x44616920537461626c65636f696e2076312e3000000000000000000000000000", + want: "Dai Stablecoin v1.0", + }, + { + name: "short2", + args: "0x44616920537461626c65636f696e2076312e3020444444444444444444444444", + want: "Dai Stablecoin v1.0 DDDDDDDDDDDD", + }, + { + name: "long", + args: "0x556e6973776170205631000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + want: "Uniswap V1", + }, + { + name: "garbage", + args: "0x2234880850896048596206002535425366538144616734015984380565810000", + want: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := parseSimpleStringProperty(tt.args) + // the addresses could have different case + if got != tt.want { + t.Errorf("parseSimpleStringProperty = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/bchain/coins/eth/erc20.go b/bchain/coins/eth/erc20.go deleted file mode 100644 index 6971f707..00000000 --- a/bchain/coins/eth/erc20.go +++ /dev/null @@ -1,245 +0,0 @@ -package eth - -import ( - "bytes" - "context" - "encoding/hex" - "math/big" - "strings" - "sync" - "unicode/utf8" - - ethcommon "github.com/ethereum/go-ethereum/common" - "github.com/golang/glog" - "github.com/juju/errors" - "github.com/trezor/blockbook/bchain" -) - -var erc20abi = `[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function","signature":"0x06fdde03"}, -{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function","signature":"0x95d89b41"}, -{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"type":"function","signature":"0x313ce567"}, -{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function","signature":"0x18160ddd"}, -{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"type":"function","signature":"0x70a08231"}, -{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function","signature":"0xa9059cbb"}, -{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function","signature":"0x23b872dd"}, -{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"}],"name":"approve","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function","signature":"0x095ea7b3"}, -{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"remaining","type":"uint256"}],"payable":false,"type":"function","signature":"0xdd62ed3e"}, -{"anonymous":false,"inputs":[{"indexed":true,"name":"_from","type":"address"},{"indexed":true,"name":"_to","type":"address"},{"indexed":false,"name":"_value","type":"uint256"}],"name":"Transfer","type":"event","signature":"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"}, -{"anonymous":false,"inputs":[{"indexed":true,"name":"_owner","type":"address"},{"indexed":true,"name":"_spender","type":"address"},{"indexed":false,"name":"_value","type":"uint256"}],"name":"Approval","type":"event","signature":"0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925"}, -{"inputs":[{"name":"_initialAmount","type":"uint256"},{"name":"_tokenName","type":"string"},{"name":"_decimalUnits","type":"uint8"},{"name":"_tokenSymbol","type":"string"}],"payable":false,"type":"constructor"}, -{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"},{"name":"_extraData","type":"bytes"}],"name":"approveAndCall","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function","signature":"0xcae9ca51"}, -{"constant":true,"inputs":[],"name":"version","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function","signature":"0x54fd4d50"}]` - -// doing the parsing/processing without using go-ethereum/accounts/abi library, it is simple to get data from Transfer event -const erc20TransferMethodSignature = "0xa9059cbb" -const erc20TransferEventSignature = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" -const erc20NameSignature = "0x06fdde03" -const erc20SymbolSignature = "0x95d89b41" -const erc20DecimalsSignature = "0x313ce567" -const erc20BalanceOf = "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 - if has0xPrefix(s) { - _, ok = t.SetString(s[2:], 16) - } else { - _, ok = t.SetString(s, 16) - } - if !ok { - return "", errors.New("Data is not a number") - } - a := ethcommon.BigToAddress(&t) - return a.String(), nil -} - -func erc20GetTransfersFromLog(logs []*bchain.RpcLog) ([]bchain.Erc20Transfer, error) { - var r []bchain.Erc20Transfer - for _, l := range logs { - if len(l.Topics) == 3 && l.Topics[0] == erc20TransferEventSignature { - var t big.Int - _, ok := t.SetString(l.Data, 0) - if !ok { - return nil, errors.New("Data is not a number") - } - from, err := addressFromPaddedHex(l.Topics[1]) - if err != nil { - return nil, err - } - to, err := addressFromPaddedHex(l.Topics[2]) - if err != nil { - return nil, err - } - r = append(r, bchain.Erc20Transfer{ - Contract: EIP55AddressFromAddress(l.Address), - From: EIP55AddressFromAddress(from), - To: EIP55AddressFromAddress(to), - Tokens: t, - }) - } - } - return r, nil -} - -func erc20GetTransfersFromTx(tx *bchain.RpcTransaction) ([]bchain.Erc20Transfer, error) { - var r []bchain.Erc20Transfer - if len(tx.Payload) == 128+len(erc20TransferMethodSignature) && strings.HasPrefix(tx.Payload, erc20TransferMethodSignature) { - to, err := addressFromPaddedHex(tx.Payload[len(erc20TransferMethodSignature) : 64+len(erc20TransferMethodSignature)]) - if err != nil { - return nil, err - } - var t big.Int - _, ok := t.SetString(tx.Payload[len(erc20TransferMethodSignature)+64:], 16) - if !ok { - return nil, errors.New("Data is not a number") - } - r = append(r, bchain.Erc20Transfer{ - Contract: EIP55AddressFromAddress(tx.To), - From: EIP55AddressFromAddress(tx.From), - To: EIP55AddressFromAddress(to), - Tokens: t, - }) - } - return r, nil -} - -func (b *EthereumRPC) ethCall(data, to string) (string, error) { - ctx, cancel := context.WithTimeout(context.Background(), b.timeout) - defer cancel() - var r string - err := b.rpc.CallContext(ctx, &r, "eth_call", map[string]interface{}{ - "data": data, - "to": to, - }, "latest") - if err != nil { - return "", err - } - return r, nil -} - -func parseErc20NumericProperty(contractDesc bchain.AddressDescriptor, data string) *big.Int { - if has0xPrefix(data) { - data = data[2:] - } - if len(data) > 64 { - data = data[:64] - } - if len(data) == 64 { - var n big.Int - _, ok := n.SetString(data, 16) - if ok { - return &n - } - } - if glog.V(1) { - glog.Warning("Cannot parse '", data, "' for contract ", contractDesc) - } - return nil -} - -func parseErc20StringProperty(contractDesc bchain.AddressDescriptor, data string) string { - if has0xPrefix(data) { - data = data[2:] - } - if len(data) > 128 { - n := parseErc20NumericProperty(contractDesc, data[64:128]) - if n != nil { - l := n.Uint64() - if l > 0 && 2*int(l) <= len(data)-128 { - b, err := hex.DecodeString(data[128 : 128+2*l]) - if err == nil { - return string(b) - } - } - } - } - // allow string properties as UTF-8 data - b, err := hex.DecodeString(data) - if err == nil { - i := bytes.Index(b, []byte{0}) - if i > 32 { - i = 32 - } - if i > 0 { - b = b[:i] - } - if utf8.Valid(b) { - return string(b) - } - } - if glog.V(1) { - glog.Warning("Cannot parse '", data, "' for contract ", contractDesc) - } - return "" -} - -// 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(erc20NameSignature, 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 := parseErc20StringProperty(contractDesc, data) - if name != "" { - data, err = b.ethCall(erc20SymbolSignature, address) - if err != nil { - glog.Warning(errors.Annotatef(err, "erc20SymbolSignature %v", address)) - return nil, nil - // return nil, errors.Annotatef(err, "erc20SymbolSignature %v", address) - } - symbol := parseErc20StringProperty(contractDesc, data) - data, err = b.ethCall(erc20DecimalsSignature, 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 := parseErc20NumericProperty(contractDesc, data) - if d != nil { - contract.Decimals = int(uint8(d.Uint64())) - } else { - contract.Decimals = EtherAmountDecimalPoint - } - } else { - contract = nil - } - cachedContractsMux.Lock() - cachedContracts[cds] = contract - cachedContractsMux.Unlock() - } - return contract, nil -} - -// EthereumTypeGetErc20ContractBalance returns balance of ERC20 contract for given address -func (b *EthereumRPC) EthereumTypeGetErc20ContractBalance(addrDesc, contractDesc bchain.AddressDescriptor) (*big.Int, error) { - addr := EIP55Address(addrDesc) - contract := EIP55Address(contractDesc) - req := erc20BalanceOf + "0000000000000000000000000000000000000000000000000000000000000000"[len(addr)-2:] + addr[2:] - data, err := b.ethCall(req, contract) - if err != nil { - return nil, err - } - r := parseErc20NumericProperty(contractDesc, data) - if r == nil { - return nil, errors.New("Invalid balance") - } - return r, nil -} diff --git a/bchain/coins/eth/erc20_test.go b/bchain/coins/eth/erc20_test.go deleted file mode 100644 index 574144d4..00000000 --- a/bchain/coins/eth/erc20_test.go +++ /dev/null @@ -1,204 +0,0 @@ -//go:build unittest - -package eth - -import ( - "fmt" - "math/big" - "strings" - "testing" - - "github.com/trezor/blockbook/bchain" - "github.com/trezor/blockbook/tests/dbtestdata" -) - -func TestErc20_erc20GetTransfersFromLog(t *testing.T) { - tests := []struct { - name string - args []*bchain.RpcLog - want []bchain.Erc20Transfer - wantErr bool - }{ - { - name: "1", - args: []*bchain.RpcLog{ - { - Address: "0x76a45e8976499ab9ae223cc584019341d5a84e96", - Topics: []string{ - "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", - "0x0000000000000000000000002aacf811ac1a60081ea39f7783c0d26c500871a8", - "0x000000000000000000000000e9a5216ff992cfa01594d43501a56e12769eb9d2", - }, - Data: "0x0000000000000000000000000000000000000000000000000000000000000123", - }, - }, - want: []bchain.Erc20Transfer{ - { - Contract: "0x76a45e8976499ab9ae223cc584019341d5a84e96", - From: "0x2aacf811ac1a60081ea39f7783c0d26c500871a8", - To: "0xe9a5216ff992cfa01594d43501a56e12769eb9d2", - Tokens: *big.NewInt(0x123), - }, - }, - }, - { - name: "2", - args: []*bchain.RpcLog{ - { // Transfer - Address: "0x0d0f936ee4c93e25944694d6c121de94d9760f11", - Topics: []string{ - "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", - "0x0000000000000000000000006f44cceb49b4a5812d54b6f494fc2febf25511ed", - "0x0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d", - }, - Data: "0x0000000000000000000000000000000000000000000000006a8313d60b1f606b", - }, - { // Transfer - Address: "0xc778417e063141139fce010982780140aa0cd5ab", - Topics: []string{ - "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", - "0x0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d", - "0x0000000000000000000000006f44cceb49b4a5812d54b6f494fc2febf25511ed", - }, - Data: "0x000000000000000000000000000000000000000000000000000308fd0e798ac0", - }, - { // not Transfer - Address: "0x479cc461fecd078f766ecc58533d6f69580cf3ac", - Topics: []string{ - "0x0d0b9391970d9a25552f37d436d2aae2925e2bfe1b2a923754bada030c498cb3", - "0x0000000000000000000000006f44cceb49b4a5812d54b6f494fc2febf25511ed", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x5af266c0a89a07c1917deaa024414577e6c3c31c8907d079e13eb448c082594f", - }, - Data: "0x0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d0000000000000", - }, - { // not Transfer - Address: "0x0d0f936ee4c93e25944694d6c121de94d9760f11", - Topics: []string{ - "0x0d0b9391970d9a25552f37d436d2aae2925e2bfe1b2a923754bada030c498cb3", - "0x0000000000000000000000007b62eb7fe80350dc7ec945c0b73242cb9877fb1b", - "0xb0b69dad58df6032c3b266e19b1045b19c87acd2c06fb0c598090f44b8e263aa", - }, - Data: "0x0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d000000000000000000000000c778417e063141139fce010982780140aa0cd5ab0000000000000000000000000d0f936ee4c93e25944694d6c121de94d9760f1100000000000000000000000000000000000000000000000000031855667df7a80000000000000000000000000000000000000000000000006a8313d60b1f800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - }, - }, - want: []bchain.Erc20Transfer{ - { - Contract: "0x0d0f936ee4c93e25944694d6c121de94d9760f11", - From: "0x6f44cceb49b4a5812d54b6f494fc2febf25511ed", - To: "0x4bda106325c335df99eab7fe363cac8a0ba2a24d", - Tokens: *big.NewInt(0x6a8313d60b1f606b), - }, - { - Contract: "0xc778417e063141139fce010982780140aa0cd5ab", - From: "0x4bda106325c335df99eab7fe363cac8a0ba2a24d", - To: "0x6f44cceb49b4a5812d54b6f494fc2febf25511ed", - Tokens: *big.NewInt(0x308fd0e798ac0), - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := erc20GetTransfersFromLog(tt.args) - if (err != nil) != tt.wantErr { - t.Errorf("erc20GetTransfersFromLog error = %v, wantErr %v", err, tt.wantErr) - return - } - // the addresses could have different case - if strings.ToLower(fmt.Sprint(got)) != strings.ToLower(fmt.Sprint(tt.want)) { - t.Errorf("erc20GetTransfersFromLog = %+v, want %+v", got, tt.want) - } - }) - } -} - -func TestErc20_parseErc20StringProperty(t *testing.T) { - tests := []struct { - name string - args string - want string - }{ - { - name: "1", - args: "0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000758504c4f44444500000000000000000000000000000000000000000000000000", - want: "XPLODDE", - }, - { - name: "2", - args: "0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000022426974436c617665202d20436f6e73756d657220416374697669747920546f6b656e00000000000000", - want: "BitClave - Consumer Activity Token", - }, - { - name: "short", - args: "0x44616920537461626c65636f696e2076312e3000000000000000000000000000", - want: "Dai Stablecoin v1.0", - }, - { - name: "short2", - args: "0x44616920537461626c65636f696e2076312e3020444444444444444444444444", - want: "Dai Stablecoin v1.0 DDDDDDDDDDDD", - }, - { - name: "long", - args: "0x556e6973776170205631000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - want: "Uniswap V1", - }, - { - name: "garbage", - args: "0x2234880850896048596206002535425366538144616734015984380565810000", - want: "", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := parseErc20StringProperty(nil, tt.args) - // the addresses could have different case - if got != tt.want { - t.Errorf("parseErc20StringProperty = %v, want %v", got, tt.want) - } - }) - } -} - -func TestErc20_erc20GetTransfersFromTx(t *testing.T) { - p := NewEthereumParser(1) - b := dbtestdata.GetTestEthereumTypeBlock1(p) - bn, _ := new(big.Int).SetString("21e19e0c9bab2400000", 16) - tests := []struct { - name string - args *bchain.RpcTransaction - want []bchain.Erc20Transfer - }{ - { - name: "0", - args: (b.Txs[0].CoinSpecificData.(bchain.EthereumSpecificData)).Tx, - want: []bchain.Erc20Transfer{}, - }, - { - name: "1", - args: (b.Txs[1].CoinSpecificData.(bchain.EthereumSpecificData)).Tx, - want: []bchain.Erc20Transfer{ - { - Contract: "0x4af4114f73d1c1c903ac9e0361b379d1291808a2", - From: "0x20cd153de35d469ba46127a0c8f18626b59a256a", - To: "0x555ee11fbddc0e49a9bab358a8941ad95ffdb48f", - Tokens: *bn, - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := erc20GetTransfersFromTx(tt.args) - if err != nil { - t.Errorf("erc20GetTransfersFromTx error = %v", err) - return - } - // the addresses could have different case - if strings.ToLower(fmt.Sprint(got)) != strings.ToLower(fmt.Sprint(tt.want)) { - t.Errorf("erc20GetTransfersFromTx = %+v, want %+v", got, tt.want) - } - }) - } -} diff --git a/bchain/coins/eth/ethparser.go b/bchain/coins/eth/ethparser.go index 2c975fac..92ed0054 100644 --- a/bchain/coins/eth/ethparser.go +++ b/bchain/coins/eth/ethparser.go @@ -13,9 +13,12 @@ import ( "golang.org/x/crypto/sha3" ) -// EthereumTypeAddressDescriptorLen - in case of EthereumType, the AddressDescriptor has fixed length +// EthereumTypeAddressDescriptorLen - the AddressDescriptor of EthereumType has fixed length const EthereumTypeAddressDescriptorLen = 20 +// EthereumTypeTxidLen - the length of Txid +const EthereumTypeTxidLen = 32 + // EtherAmountDecimalPoint defines number of decimal points in Ether amounts const EtherAmountDecimalPoint = 18 @@ -388,7 +391,7 @@ func (p *EthereumParser) UnpackTx(buf []byte) (*bchain.Tx, uint32, error) { // PackedTxidLen returns length in bytes of packed txid func (p *EthereumParser) PackedTxidLen() int { - return 32 + return EthereumTypeTxidLen } // PackTxid packs txid to byte array @@ -437,16 +440,16 @@ func GetHeightFromTx(tx *bchain.Tx) (uint32, error) { return uint32(n), nil } -// EthereumTypeGetErc20FromTx returns Erc20 data from bchain.Tx -func (p *EthereumParser) EthereumTypeGetErc20FromTx(tx *bchain.Tx) ([]bchain.Erc20Transfer, error) { - var r []bchain.Erc20Transfer +// EthereumTypeGetTokenTransfersFromTx returns contract transfers from bchain.Tx +func (p *EthereumParser) EthereumTypeGetTokenTransfersFromTx(tx *bchain.Tx) (bchain.TokenTransfers, error) { + var r bchain.TokenTransfers var err error csd, ok := tx.CoinSpecificData.(bchain.EthereumSpecificData) if ok { if csd.Receipt != nil { - r, err = erc20GetTransfersFromLog(csd.Receipt.Logs) + r, err = contractGetTransfersFromLog(csd.Receipt.Logs) } else { - r, err = erc20GetTransfersFromTx(csd.Tx) + r, err = contractGetTransfersFromTx(csd.Tx) } if err != nil { return nil, err @@ -518,7 +521,7 @@ func ParseErrorFromOutput(output string) string { if len(output) < 8+64+64+64 || output[:8] != errorOutputSignature { return "" } - return parseErc20StringProperty(nil, output[8:]) + return parseSimpleStringProperty(output[8:]) } // PackInternalTransactionError packs common error messages to single byte to save DB space diff --git a/bchain/coins/eth/ethrpc.go b/bchain/coins/eth/ethrpc.go index ac3ad3d9..1b6f2803 100644 --- a/bchain/coins/eth/ethrpc.go +++ b/bchain/coins/eth/ethrpc.go @@ -491,14 +491,14 @@ func (b *EthereumRPC) getBlockRaw(hash string, height uint32, fullTxs bool) (jso return raw, nil } -func (b *EthereumRPC) getERC20EventsForBlock(blockNumber string) (map[string][]*bchain.RpcLog, error) { +func (b *EthereumRPC) getTokenTransferEventsForBlock(blockNumber string) (map[string][]*bchain.RpcLog, error) { ctx, cancel := context.WithTimeout(context.Background(), b.timeout) defer cancel() var logs []rpcLogWithTxHash err := b.rpc.CallContext(ctx, &logs, "eth_getLogs", map[string]interface{}{ "fromBlock": blockNumber, "toBlock": blockNumber, - "topics": []string{erc20TransferEventSignature}, + "topics": []string{tokenTransferEventSignature, tokenERC1155TransferSingleEventSignature, tokenERC1155TransferBatchEventSignature}, }) if err != nil { return nil, errors.Annotatef(err, "blockNumber %v", blockNumber) @@ -630,8 +630,8 @@ func (b *EthereumRPC) GetBlock(hash string, height uint32) (*bchain.Block, error if err != nil { return nil, errors.Annotatef(err, "hash %v, height %v", hash, height) } - // get ERC20 events - logs, err := b.getERC20EventsForBlock(head.Number) + // get contract transfers events + logs, err := b.getTokenTransferEventsForBlock(head.Number) if err != nil { return nil, err } diff --git a/bchain/mempool_ethereum_type.go b/bchain/mempool_ethereum_type.go index 91fdeeac..23d333fd 100644 --- a/bchain/mempool_ethereum_type.go +++ b/bchain/mempool_ethereum_type.go @@ -74,11 +74,11 @@ func (m *MempoolEthereumType) createTxEntry(txid string, txTime uint32) (txEntry addrIndexes, input.AddrDesc = appendAddress(addrIndexes, ^int32(i), a, parser) } } - t, err := parser.EthereumTypeGetErc20FromTx(tx) + t, err := parser.EthereumTypeGetTokenTransfersFromTx(tx) if err != nil { - glog.Error("GetErc20FromTx for tx ", txid, ", ", err) + glog.Error("GetGetTokenTransfersFromTx for tx ", txid, ", ", err) } else { - mtx.Erc20 = t + mtx.TokenTransfers = t for i := range t { addrIndexes, _ = appendAddress(addrIndexes, ^int32(i+1), t[i].From, parser) addrIndexes, _ = appendAddress(addrIndexes, int32(i+1), t[i].To, parser) diff --git a/bchain/types.go b/bchain/types.go index ae590abc..d2fe44c4 100644 --- a/bchain/types.go +++ b/bchain/types.go @@ -102,15 +102,24 @@ type MempoolVin struct { // MempoolTx is blockchain transaction in mempool // optimized for onNewTx notification type MempoolTx struct { - Hex string `json:"hex"` - Txid string `json:"txid"` - Version int32 `json:"version"` - LockTime uint32 `json:"locktime"` - Vin []MempoolVin `json:"vin"` - Vout []Vout `json:"vout"` - Blocktime int64 `json:"blocktime,omitempty"` - Erc20 []Erc20Transfer `json:"-"` - CoinSpecificData interface{} `json:"-"` + Hex string `json:"hex"` + Txid string `json:"txid"` + Version int32 `json:"version"` + LockTime uint32 `json:"locktime"` + Vin []MempoolVin `json:"vin"` + Vout []Vout `json:"vout"` + Blocktime int64 `json:"blocktime,omitempty"` + TokenTransfers TokenTransfers `json:"-"` + CoinSpecificData interface{} `json:"-"` +} + +// TokenTransfers is array of TokenTransfer +type TokenTransfers []*TokenTransfer + +func (a TokenTransfers) Len() int { return len(a) } +func (a TokenTransfers) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a TokenTransfers) Less(i, j int) bool { + return a[i].Type < a[j].Type } // Block is block header and list of transactions @@ -328,7 +337,7 @@ type BlockChainParser interface { DeriveAddressDescriptors(descriptor *XpubDescriptor, change uint32, indexes []uint32) ([]AddressDescriptor, error) DeriveAddressDescriptorsFromTo(descriptor *XpubDescriptor, change uint32, fromIndex uint32, toIndex uint32) ([]AddressDescriptor, error) // EthereumType specific - EthereumTypeGetErc20FromTx(tx *Tx) ([]Erc20Transfer, error) + EthereumTypeGetTokenTransfersFromTx(tx *Tx) (TokenTransfers, error) } // Mempool defines common interface to mempool diff --git a/bchain/types_ethereum_type.go b/bchain/types_ethereum_type.go index f4ca166b..4061eea7 100644 --- a/bchain/types_ethereum_type.go +++ b/bchain/types_ethereum_type.go @@ -22,6 +22,16 @@ 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"` @@ -38,12 +48,19 @@ type Erc20Contract struct { Decimals int `json:"decimals"` } -// Erc20Transfer contains a single ERC20 token transfer -type Erc20Transfer struct { +type TokenTransferIdValue struct { + Id big.Int + Value big.Int +} + +// TokenTransfer contains a single ERC20/ERC721/ERC1155 token transfer +type TokenTransfer struct { + Type TokenTransferType Contract string From string To string - Tokens big.Int + Value big.Int + IdValues []TokenTransferIdValue } // RpcTransaction is returned by eth_getTransactionByHash diff --git a/db/rocksdb_ethereumtype.go b/db/rocksdb_ethereumtype.go index 4abc0bd4..03bbcf49 100644 --- a/db/rocksdb_ethereumtype.go +++ b/db/rocksdb_ethereumtype.go @@ -5,7 +5,6 @@ import ( "encoding/hex" "math/big" - vlq "github.com/bsm/go-vlq" "github.com/flier/gorocksdb" "github.com/golang/glog" "github.com/juju/errors" @@ -18,8 +17,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 } // AddrContracts contains number of transactions and contracts for an address @@ -30,26 +33,109 @@ type AddrContracts struct { Contracts []AddrContract } +// packAddrContract packs AddrContracts into a byte buffer +func packAddrContracts(acs *AddrContracts) []byte { + buf := make([]byte, 0, 128) + varBuf := make([]byte, maxPackedBigintBytes) + l := packVaruint(acs.TotalTxs, varBuf) + buf = append(buf, varBuf[:l]...) + l = packVaruint(acs.NonContractTxs, varBuf) + buf = append(buf, varBuf[:l]...) + l = packVaruint(acs.InternalTxs, varBuf) + buf = append(buf, varBuf[:l]...) + for _, ac := range acs.Contracts { + buf = append(buf, ac.Contract...) + l = packVaruint(uint(ac.Type)+ac.Txs<<2, varBuf) + buf = append(buf, varBuf[:l]...) + if ac.Type == bchain.ERC20 { + l = packBigint(&ac.Value, varBuf) + buf = append(buf, varBuf[:l]...) + } else if ac.Type == bchain.ERC721 { + l = packVaruint(uint(len(ac.Ids)), varBuf) + buf = append(buf, varBuf[:l]...) + for i := range ac.Ids { + l = packBigint(&ac.Ids[i], varBuf) + buf = append(buf, varBuf[:l]...) + } + } else { // bchain.ERC1155 + l = packVaruint(uint(len(ac.IdValues)), varBuf) + buf = append(buf, varBuf[:l]...) + for i := range ac.IdValues { + l = packBigint(&ac.IdValues[i].Id, varBuf) + buf = append(buf, varBuf[:l]...) + l = packBigint(&ac.IdValues[i].Value, varBuf) + buf = append(buf, varBuf[:l]...) + } + } + } + return buf +} + +func unpackAddrContracts(buf []byte, addrDesc bchain.AddressDescriptor) (*AddrContracts, error) { + tt, l := unpackVaruint(buf) + buf = buf[l:] + nct, l := unpackVaruint(buf) + buf = buf[l:] + ict, l := unpackVaruint(buf) + buf = buf[l:] + c := make([]AddrContract, 0, 4) + for len(buf) > 0 { + if len(buf) < eth.EthereumTypeAddressDescriptorLen { + return nil, errors.New("Invalid data stored in cfAddressContracts for AddrDesc " + addrDesc.String()) + } + contract := append(bchain.AddressDescriptor(nil), buf[:eth.EthereumTypeAddressDescriptorLen]...) + txs, l := unpackVaruint(buf[eth.EthereumTypeAddressDescriptorLen:]) + buf = buf[eth.EthereumTypeAddressDescriptorLen+l:] + ttt := bchain.TokenTransferType(txs & 3) + txs >>= 2 + ac := AddrContract{ + Type: ttt, + Contract: contract, + Txs: txs, + } + if ttt == bchain.ERC20 { + b, ll := unpackBigint(buf) + buf = buf[ll:] + ac.Value = b + } else { + len, ll := unpackVaruint(buf) + buf = buf[ll:] + if ttt == bchain.ERC721 { + ac.Ids = make([]big.Int, len) + for i := uint(0); i < len; i++ { + b, ll := unpackBigint(buf) + buf = buf[ll:] + ac.Ids[i] = b + } + } else { + ac.IdValues = make([]bchain.TokenTransferIdValue, len) + for i := uint(0); i < len; i++ { + b, ll := unpackBigint(buf) + buf = buf[ll:] + ac.IdValues[i].Id = b + b, ll = unpackBigint(buf) + buf = buf[ll:] + ac.IdValues[i].Value = b + } + } + } + c = append(c, ac) + } + return &AddrContracts{ + TotalTxs: tt, + NonContractTxs: nct, + InternalTxs: ict, + Contracts: c, + }, nil +} + func (d *RocksDB) storeAddressContracts(wb *gorocksdb.WriteBatch, acm map[string]*AddrContracts) error { - buf := make([]byte, 64) - varBuf := make([]byte, vlq.MaxLen64) for addrDesc, acs := range acm { // address with 0 contracts is removed from db - happens on disconnect if acs == nil || (acs.NonContractTxs == 0 && acs.InternalTxs == 0 && len(acs.Contracts) == 0) { wb.DeleteCF(d.cfh[cfAddressContracts], bchain.AddressDescriptor(addrDesc)) } else { - buf = buf[:0] - l := packVaruint(acs.TotalTxs, varBuf) - buf = append(buf, varBuf[:l]...) - l = packVaruint(acs.NonContractTxs, varBuf) - buf = append(buf, varBuf[:l]...) - l = packVaruint(acs.InternalTxs, varBuf) - buf = append(buf, varBuf[:l]...) - for _, ac := range acs.Contracts { - buf = append(buf, ac.Contract...) - l = packVaruint(ac.Txs, varBuf) - buf = append(buf, varBuf[:l]...) - } + buf := packAddrContracts(acs) wb.PutCF(d.cfh[cfAddressContracts], bchain.AddressDescriptor(addrDesc), buf) } } @@ -67,31 +153,7 @@ func (d *RocksDB) GetAddrDescContracts(addrDesc bchain.AddressDescriptor) (*Addr if len(buf) == 0 { return nil, nil } - tt, l := unpackVaruint(buf) - buf = buf[l:] - nct, l := unpackVaruint(buf) - buf = buf[l:] - ict, l := unpackVaruint(buf) - buf = buf[l:] - c := make([]AddrContract, 0, 4) - for len(buf) > 0 { - if len(buf) < eth.EthereumTypeAddressDescriptorLen { - return nil, errors.New("Invalid data stored in cfAddressContracts for AddrDesc " + addrDesc.String()) - } - txs, l := unpackVaruint(buf[eth.EthereumTypeAddressDescriptorLen:]) - contract := append(bchain.AddressDescriptor(nil), buf[:eth.EthereumTypeAddressDescriptorLen]...) - c = append(c, AddrContract{ - Contract: contract, - Txs: txs, - }) - buf = buf[eth.EthereumTypeAddressDescriptorLen+l:] - } - return &AddrContracts{ - TotalTxs: tt, - NonContractTxs: nct, - InternalTxs: ict, - Contracts: c, - }, nil + return unpackAddrContracts(buf, addrDesc) } func findContractInAddressContracts(contract bchain.AddressDescriptor, contracts []AddrContract) (int, bool) { @@ -117,7 +179,94 @@ const transferFrom = ^int32(0) const internalTransferTo = int32(1) const internalTransferFrom = ^int32(1) -func (d *RocksDB) addToAddressesAndContractsEthereumType(addrDesc bchain.AddressDescriptor, btxID []byte, index int32, contract bchain.AddressDescriptor, addresses addressesMap, addressContracts map[string]*AddrContracts, addTxCount bool) error { +// addToAddressesMapEthereumType maintains mapping between addresses and transactions in one block +// it ensures that each index is there only once, there can be for example multiple internal transactions of the same address +// the return value is true if the tx was processed before, to not to count the tx multiple times +func addToAddressesMapEthereumType(addresses addressesMap, strAddrDesc string, btxID []byte, index int32) bool { + // check that the address was already processed in this block + // if not found, it has certainly not been counted + at, found := addresses[strAddrDesc] + if found { + // if the tx is already in the slice, append the index to the array of indexes + for i, t := range at { + if bytes.Equal(btxID, t.btxID) { + for _, existing := range t.indexes { + if existing == index { + return true + } + } + at[i].indexes = append(t.indexes, index) + return true + } + } + } + addresses[strAddrDesc] = append(at, txIndexes{ + btxID: btxID, + indexes: []int32{index}, + }) + return false +} + +func addToContract(c *AddrContract, contractIndex int, index int32, contract bchain.AddressDescriptor, transfer *bchain.TokenTransfer, addTxCount bool) int32 { + var aggregate func(*big.Int, *big.Int) + // index 0 is for ETH transfers, index 1 (InternalTxIndexOffset) is for internal transfers, contract indexes start with 2 (ContractIndexOffset) + if index < 0 { + index = ^int32(contractIndex + ContractIndexOffset) + aggregate = func(s, v *big.Int) { + s.Sub(s, v) + if s.Sign() < 0 { + glog.Warningf("rocksdb: addToContracts: contract %s, from %s, negative aggregate", transfer.Contract, transfer.From) + s.SetInt64(0) + } + } + } else { + index = int32(contractIndex + ContractIndexOffset) + aggregate = func(s, v *big.Int) { + s.Add(s, v) + } + } + if transfer.Type == bchain.ERC20 { + aggregate(&c.Value, &transfer.Value) + } else if transfer.Type == bchain.ERC721 { + if index < 0 { + // remove token from the list + for i := range c.Ids { + if c.Ids[i].Cmp(&transfer.Value) == 0 { + c.Ids = append(c.Ids[:i], c.Ids[i+1:]...) + break + } + } + } else { + // add token to the list + c.Ids = append(c.Ids, transfer.Value) + } + } else { // bchain.ERC1155 + for _, t := range transfer.IdValues { + for i := range c.IdValues { + // find the token in the list + if c.IdValues[i].Id.Cmp(&t.Id) == 0 { + aggregate(&c.IdValues[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:]...) + } + goto nextTransfer + } + } + // if not found and transfer to, add to the list + if index >= 0 { + c.IdValues = append(c.IdValues, t) + } + nextTransfer: + } + } + if addTxCount { + c.Txs++ + } + return index +} + +func (d *RocksDB) addToAddressesAndContractsEthereumType(addrDesc bchain.AddressDescriptor, btxID []byte, index int32, contract bchain.AddressDescriptor, transfer *bchain.TokenTransfer, addTxCount bool, addresses addressesMap, addressContracts map[string]*AddrContracts) error { var err error strAddrDesc := string(addrDesc) ac, e := addressContracts[strAddrDesc] @@ -146,20 +295,16 @@ func (d *RocksDB) addToAddressesAndContractsEthereumType(addrDesc bchain.Address // do not store contracts for 0x0000000000000000000000000000000000000000 address if !isZeroAddress(addrDesc) { // locate the contract and set i to the index in the array of contracts - i, found := findContractInAddressContracts(contract, ac.Contracts) + contractIndex, found := findContractInAddressContracts(contract, ac.Contracts) if !found { - i = len(ac.Contracts) - ac.Contracts = append(ac.Contracts, AddrContract{Contract: contract}) - } - // index 0 is for ETH transfers, index 1 (InternalTxIndexOffset) is for internal transfers, contract indexes start with 2 (ContractIndexOffset) - if index < 0 { - index = ^int32(i + ContractIndexOffset) - } else { - index = int32(i + ContractIndexOffset) - } - if addTxCount { - ac.Contracts[i].Txs++ + contractIndex = len(ac.Contracts) + ac.Contracts = append(ac.Contracts, AddrContract{ + Contract: contract, + Type: transfer.Type, + }) } + c := &ac.Contracts[contractIndex] + index = addToContract(c, contractIndex, index, contract, transfer, addTxCount) } else { if index < 0 { index = transferFrom @@ -168,7 +313,7 @@ func (d *RocksDB) addToAddressesAndContractsEthereumType(addrDesc bchain.Address } } } - counted := addToAddressesMap(addresses, strAddrDesc, btxID, index) + counted := addToAddressesMapEthereumType(addresses, strAddrDesc, btxID, index) if !counted { ac.TotalTxs++ } @@ -176,7 +321,10 @@ func (d *RocksDB) addToAddressesAndContractsEthereumType(addrDesc bchain.Address } type ethBlockTxContract struct { - addr, contract bchain.AddressDescriptor + from, to, contract bchain.AddressDescriptor + transferType bchain.TokenTransferType + value big.Int + idValues []bchain.TokenTransferIdValue } type ethInternalTransfer struct { @@ -199,171 +347,190 @@ type ethBlockTx struct { internalData *ethInternalData } +func (d *RocksDB) processBaseTxData(blockTx *ethBlockTx, tx *bchain.Tx, addresses addressesMap, addressContracts map[string]*AddrContracts) error { + var from, to bchain.AddressDescriptor + var err error + // there is only one output address in EthereumType transaction, store it in format txid 0 + if len(tx.Vout) == 1 && len(tx.Vout[0].ScriptPubKey.Addresses) == 1 { + to, err = d.chainParser.GetAddrDescFromAddress(tx.Vout[0].ScriptPubKey.Addresses[0]) + if err != nil { + // do not log ErrAddressMissing, transactions can be without to address (for example eth contracts) + if err != bchain.ErrAddressMissing { + glog.Warningf("rocksdb: processBaseTxData: %v, tx %v, output", err, tx.Txid) + } + } else { + if err = d.addToAddressesAndContractsEthereumType(to, blockTx.btxID, transferTo, nil, nil, true, addresses, addressContracts); err != nil { + return err + } + blockTx.to = to + } + } + // there is only one input address in EthereumType transaction, store it in format txid ^0 + if len(tx.Vin) == 1 && len(tx.Vin[0].Addresses) == 1 { + from, err = d.chainParser.GetAddrDescFromAddress(tx.Vin[0].Addresses[0]) + if err != nil { + if err != bchain.ErrAddressMissing { + glog.Warningf("rocksdb: processBaseTxData: %v, tx %v, input", err, tx.Txid) + } + } else { + if err = d.addToAddressesAndContractsEthereumType(from, blockTx.btxID, transferFrom, nil, nil, !bytes.Equal(from, to), addresses, addressContracts); err != nil { + return err + } + blockTx.from = from + } + } + return nil +} + +func (d *RocksDB) processInternalData(blockTx *ethBlockTx, tx *bchain.Tx, id *bchain.EthereumInternalData, addresses addressesMap, addressContracts map[string]*AddrContracts) error { + blockTx.internalData = ðInternalData{ + internalType: id.Type, + errorMsg: id.Error, + } + // index contract creation + if id.Type == bchain.CREATE { + to, err := d.chainParser.GetAddrDescFromAddress(id.Contract) + if err != nil { + if err != bchain.ErrAddressMissing { + glog.Warningf("rocksdb: processInternalData: %v, tx %v, create contract", err, tx.Txid) + } + // set the internalType to CALL if incorrect contract so that it is not breaking the packing of data to DB + blockTx.internalData.internalType = bchain.CALL + } else { + blockTx.internalData.contract = to + if err = d.addToAddressesAndContractsEthereumType(to, blockTx.btxID, internalTransferTo, nil, nil, true, addresses, addressContracts); err != nil { + return err + } + } + } + // index internal transfers + if len(id.Transfers) > 0 { + blockTx.internalData.transfers = make([]ethInternalTransfer, len(id.Transfers)) + for i := range id.Transfers { + iti := &id.Transfers[i] + ito := &blockTx.internalData.transfers[i] + to, err := d.chainParser.GetAddrDescFromAddress(iti.To) + if err != nil { + // do not log ErrAddressMissing, transactions can be without to address (for example eth contracts) + if err != bchain.ErrAddressMissing { + glog.Warningf("rocksdb: processInternalData: %v, tx %v, internal transfer %d to", err, tx.Txid, i) + } + } else { + if err = d.addToAddressesAndContractsEthereumType(to, blockTx.btxID, internalTransferTo, nil, nil, true, addresses, addressContracts); err != nil { + return err + } + ito.to = to + } + from, err := d.chainParser.GetAddrDescFromAddress(iti.From) + if err != nil { + if err != bchain.ErrAddressMissing { + glog.Warningf("rocksdb: processInternalData: %v, tx %v, internal transfer %d from", err, tx.Txid, i) + } + } else { + if err = d.addToAddressesAndContractsEthereumType(from, blockTx.btxID, internalTransferFrom, nil, nil, !bytes.Equal(from, to), addresses, addressContracts); err != nil { + return err + } + ito.from = from + } + ito.internalType = iti.Type + ito.value = iti.Value + } + } + return nil +} + +func (d *RocksDB) processContractTransfers(blockTx *ethBlockTx, tx *bchain.Tx, addresses addressesMap, addressContracts map[string]*AddrContracts) error { + tokenTransfers, err := d.chainParser.EthereumTypeGetTokenTransfersFromTx(tx) + if err != nil { + glog.Warningf("rocksdb: processContractTransfers %v, tx %v", err, tx.Txid) + } + blockTx.contracts = make([]ethBlockTxContract, len(tokenTransfers)) + for i, t := range tokenTransfers { + var contract, from, to bchain.AddressDescriptor + contract, err = d.chainParser.GetAddrDescFromAddress(t.Contract) + if err == nil { + from, err = d.chainParser.GetAddrDescFromAddress(t.From) + if err == nil { + to, err = d.chainParser.GetAddrDescFromAddress(t.To) + } + } + if err != nil { + glog.Warningf("rocksdb: processContractTransfers %v, tx %v, transfer %v", err, tx.Txid, t) + continue + } + if err = d.addToAddressesAndContractsEthereumType(to, blockTx.btxID, int32(i), contract, t, true, addresses, addressContracts); err != nil { + return err + } + eq := bytes.Equal(from, to) + if err = d.addToAddressesAndContractsEthereumType(from, blockTx.btxID, ^int32(i), contract, t, !eq, addresses, addressContracts); err != nil { + return err + } + bc := &blockTx.contracts[i] + bc.transferType = t.Type + bc.from = from + bc.to = to + bc.contract = contract + bc.value = t.Value + bc.idValues = t.IdValues + } + return nil +} + func (d *RocksDB) processAddressesEthereumType(block *bchain.Block, addresses addressesMap, addressContracts map[string]*AddrContracts) ([]ethBlockTx, error) { blockTxs := make([]ethBlockTx, len(block.Txs)) - for txi, tx := range block.Txs { + for txi := range block.Txs { + tx := &block.Txs[txi] btxID, err := d.chainParser.PackTxid(tx.Txid) if err != nil { return nil, err } blockTx := &blockTxs[txi] blockTx.btxID = btxID - var from, to bchain.AddressDescriptor - // there is only one output address in EthereumType transaction, store it in format txid 0 - if len(tx.Vout) == 1 && len(tx.Vout[0].ScriptPubKey.Addresses) == 1 { - to, err = d.chainParser.GetAddrDescFromAddress(tx.Vout[0].ScriptPubKey.Addresses[0]) - if err != nil { - // do not log ErrAddressMissing, transactions can be without to address (for example eth contracts) - if err != bchain.ErrAddressMissing { - glog.Warningf("rocksdb: addrDesc: %v - height %d, tx %v, output", err, block.Height, tx.Txid) - } - } else { - if err = d.addToAddressesAndContractsEthereumType(to, btxID, transferTo, nil, addresses, addressContracts, true); err != nil { - return nil, err - } - blockTx.to = to - } - } - // there is only one input address in EthereumType transaction, store it in format txid ^0 - if len(tx.Vin) == 1 && len(tx.Vin[0].Addresses) == 1 { - from, err = d.chainParser.GetAddrDescFromAddress(tx.Vin[0].Addresses[0]) - if err != nil { - if err != bchain.ErrAddressMissing { - glog.Warningf("rocksdb: addrDesc: %v - height %d, tx %v, input", err, block.Height, tx.Txid) - } - } else { - if err = d.addToAddressesAndContractsEthereumType(from, btxID, transferFrom, nil, addresses, addressContracts, !bytes.Equal(from, to)); err != nil { - return nil, err - } - blockTx.from = from - } + if err = d.processBaseTxData(blockTx, tx, addresses, addressContracts); err != nil { + return nil, err } // process internal data eid, _ := tx.CoinSpecificData.(bchain.EthereumSpecificData) if eid.InternalData != nil { - blockTx.internalData = ðInternalData{ - internalType: eid.InternalData.Type, - errorMsg: eid.InternalData.Error, - } - // index contract creation - if eid.InternalData.Type == bchain.CREATE { - to, err = d.chainParser.GetAddrDescFromAddress(eid.InternalData.Contract) - if err != nil { - if err != bchain.ErrAddressMissing { - glog.Warningf("rocksdb: addrDesc: %v - height %d, tx %v, create contract", err, block.Height, tx.Txid) - } - // set the internalType to CALL if incorrect contract so that it is not breaking the packing of data to DB - blockTx.internalData.internalType = bchain.CALL - } else { - blockTx.internalData.contract = to - if err = d.addToAddressesAndContractsEthereumType(to, btxID, internalTransferTo, nil, addresses, addressContracts, true); err != nil { - return nil, err - } - } - } - // index internal transfers - if len(eid.InternalData.Transfers) > 0 { - blockTx.internalData.transfers = make([]ethInternalTransfer, len(eid.InternalData.Transfers)) - for i := range eid.InternalData.Transfers { - iti := &eid.InternalData.Transfers[i] - ito := &blockTx.internalData.transfers[i] - to, err = d.chainParser.GetAddrDescFromAddress(iti.To) - if err != nil { - // do not log ErrAddressMissing, transactions can be without to address (for example eth contracts) - if err != bchain.ErrAddressMissing { - glog.Warningf("rocksdb: addrDesc: %v - height %d, tx %v, internal transfer %d to", err, block.Height, tx.Txid, i) - } - } else { - if err = d.addToAddressesAndContractsEthereumType(to, btxID, internalTransferTo, nil, addresses, addressContracts, true); err != nil { - return nil, err - } - ito.to = to - } - from, err = d.chainParser.GetAddrDescFromAddress(iti.From) - if err != nil { - if err != bchain.ErrAddressMissing { - glog.Warningf("rocksdb: addrDesc: %v - height %d, tx %v, internal transfer %d from", err, block.Height, tx.Txid, i) - } - } else { - if err = d.addToAddressesAndContractsEthereumType(from, btxID, internalTransferFrom, nil, addresses, addressContracts, !bytes.Equal(from, to)); err != nil { - return nil, err - } - ito.from = from - } - ito.internalType = iti.Type - ito.value = iti.Value - } - } - } - // store erc20 transfers - erc20, err := d.chainParser.EthereumTypeGetErc20FromTx(&tx) - if err != nil { - glog.Warningf("rocksdb: GetErc20FromTx %v - height %d, tx %v", err, block.Height, tx.Txid) - } - blockTx.contracts = make([]ethBlockTxContract, len(erc20)*2) - j := 0 - for i, t := range erc20 { - var contract, from, to bchain.AddressDescriptor - contract, err = d.chainParser.GetAddrDescFromAddress(t.Contract) - if err == nil { - from, err = d.chainParser.GetAddrDescFromAddress(t.From) - if err == nil { - to, err = d.chainParser.GetAddrDescFromAddress(t.To) - } - } - if err != nil { - glog.Warningf("rocksdb: GetErc20FromTx %v - height %d, tx %v, transfer %v", err, block.Height, tx.Txid, t) - continue - } - if err = d.addToAddressesAndContractsEthereumType(to, btxID, int32(i), contract, addresses, addressContracts, true); err != nil { + if err = d.processInternalData(blockTx, tx, eid.InternalData, addresses, addressContracts); err != nil { return nil, err } - eq := bytes.Equal(from, to) - bc := &blockTx.contracts[j] - j++ - bc.addr = from - bc.contract = contract - if err = d.addToAddressesAndContractsEthereumType(from, btxID, ^int32(i), contract, addresses, addressContracts, !eq); err != nil { - return nil, err - } - // add to address to blockTx.contracts only if it is different from from address - if !eq { - bc = &blockTx.contracts[j] - j++ - bc.addr = to - bc.contract = contract - } } - blockTx.contracts = blockTx.contracts[:j] + // store contract transfers + if err = d.processContractTransfers(blockTx, tx, addresses, addressContracts); err != nil { + return nil, err + } } return blockTxs, nil } var ethZeroAddress []byte = make([]byte, eth.EthereumTypeAddressDescriptorLen) +func appendAddress(buf []byte, a bchain.AddressDescriptor) []byte { + if len(a) != eth.EthereumTypeAddressDescriptorLen { + buf = append(buf, ethZeroAddress...) + } else { + buf = append(buf, a...) + } + return buf +} + func packEthInternalData(data *ethInternalData) []byte { // allocate enough for type+contract+all transfers with bigint value buf := make([]byte, 0, (2*len(data.transfers)+1)*(eth.EthereumTypeAddressDescriptorLen+16)) - appendAddress := func(a bchain.AddressDescriptor) { - if len(a) != eth.EthereumTypeAddressDescriptorLen { - buf = append(buf, ethZeroAddress...) - } else { - buf = append(buf, a...) - } - } varBuf := make([]byte, maxPackedBigintBytes) // internalType is one bit (CALL|CREATE), it is joined with count of internal transfers*2 l := packVaruint(uint(data.internalType)&1+uint(len(data.transfers))<<1, varBuf) buf = append(buf, varBuf[:l]...) if data.internalType == bchain.CREATE { - appendAddress(data.contract) + buf = appendAddress(buf, data.contract) } for i := range data.transfers { t := &data.transfers[i] buf = append(buf, byte(t.internalType)) - appendAddress(t.from) - appendAddress(t.to) + buf = appendAddress(buf, t.from) + buf = appendAddress(buf, t.to) l = packBigint(&t.value, varBuf) buf = append(buf, varBuf[:l]...) } @@ -412,7 +579,10 @@ func (d *RocksDB) GetEthereumInternalData(txid string) (*bchain.EthereumInternal if err != nil { return nil, err } + return d.getEthereumInternalData(btxID) +} +func (d *RocksDB) getEthereumInternalData(btxID []byte) (*bchain.EthereumInternalData, error) { val, err := d.db.GetCF(d.ro, d.cfh[cfInternalData], btxID) if err != nil { return nil, err @@ -435,50 +605,44 @@ func (d *RocksDB) storeInternalDataEthereumType(wb *gorocksdb.WriteBatch, blockT return nil } +func packBlockTx(buf []byte, blockTx *ethBlockTx) []byte { + varBuf := make([]byte, maxPackedBigintBytes) + buf = append(buf, blockTx.btxID...) + buf = appendAddress(buf, blockTx.from) + buf = appendAddress(buf, blockTx.to) + // internal data are not stored in blockTx, they are fetched on disconnect directly from the cfInternalData column + // contracts - store the number of address pairs + l := packVaruint(uint(len(blockTx.contracts)), varBuf) + buf = append(buf, varBuf[:l]...) + for j := range blockTx.contracts { + c := &blockTx.contracts[j] + buf = appendAddress(buf, c.from) + buf = appendAddress(buf, c.to) + buf = appendAddress(buf, c.contract) + l = packVaruint(uint(c.transferType), varBuf) + buf = append(buf, varBuf[:l]...) + if c.transferType == bchain.ERC1155 { + l = packVaruint(uint(len(c.idValues)), varBuf) + buf = append(buf, varBuf[:l]...) + for i := range c.idValues { + l = packBigint(&c.idValues[i].Id, varBuf) + buf = append(buf, varBuf[:l]...) + l = packBigint(&c.idValues[i].Value, varBuf) + buf = append(buf, varBuf[:l]...) + } + } else { // ERC20, ERC721 + l = packBigint(&c.value, varBuf) + buf = append(buf, varBuf[:l]...) + } + } + return buf +} + func (d *RocksDB) storeAndCleanupBlockTxsEthereumType(wb *gorocksdb.WriteBatch, block *bchain.Block, blockTxs []ethBlockTx) error { pl := d.chainParser.PackedTxidLen() buf := make([]byte, 0, (pl+2*eth.EthereumTypeAddressDescriptorLen)*len(blockTxs)) - varBuf := make([]byte, vlq.MaxLen64) - appendAddress := func(a bchain.AddressDescriptor) { - if len(a) != eth.EthereumTypeAddressDescriptorLen { - buf = append(buf, ethZeroAddress...) - } else { - buf = append(buf, a...) - } - } for i := range blockTxs { - blockTx := &blockTxs[i] - buf = append(buf, blockTx.btxID...) - appendAddress(blockTx.from) - appendAddress(blockTx.to) - // internal data - store the number of addresses, with odd number the CREATE tx type - var internalDataTransfers uint - if blockTx.internalData != nil { - internalDataTransfers = uint(len(blockTx.internalData.transfers)) * 2 - if blockTx.internalData.internalType == bchain.CREATE { - internalDataTransfers++ - } - } - l := packVaruint(internalDataTransfers, varBuf) - buf = append(buf, varBuf[:l]...) - if internalDataTransfers > 0 { - if blockTx.internalData.internalType == bchain.CREATE { - appendAddress(blockTx.internalData.contract) - } - for j := range blockTx.internalData.transfers { - c := &blockTx.internalData.transfers[j] - appendAddress(c.from) - appendAddress(c.to) - } - } - // contracts - store the number of address pairs - l = packVaruint(uint(len(blockTx.contracts)), varBuf) - buf = append(buf, varBuf[:l]...) - for j := range blockTx.contracts { - c := &blockTx.contracts[j] - appendAddress(c.addr) - appendAddress(c.contract) - } + buf = packBlockTx(buf, &blockTxs[i]) } key := packUint(block.Height) wb.PutCF(d.cfh[cfBlockTxs], key, buf) @@ -501,8 +665,78 @@ func (d *RocksDB) storeBlockInternalDataErrorEthereumType(wb *gorocksdb.WriteBat return nil } +// unpackBlockTx unpacks ethBlockTx from buf, starting at position pos +// the position is updated as the data is unpacked and returned to the caller +func unpackBlockTx(buf []byte, pos int) (*ethBlockTx, int, error) { + getAddress := func(i int) (bchain.AddressDescriptor, int, error) { + if len(buf)-i < eth.EthereumTypeAddressDescriptorLen { + glog.Error("rocksdb: Inconsistent data in blockTxs ", hex.EncodeToString(buf)) + return nil, 0, errors.New("Inconsistent data in blockTxs") + } + a := append(bchain.AddressDescriptor(nil), buf[i:i+eth.EthereumTypeAddressDescriptorLen]...) + return a, i + eth.EthereumTypeAddressDescriptorLen, nil + } + var from, to bchain.AddressDescriptor + var err error + if len(buf)-pos < eth.EthereumTypeTxidLen { + glog.Error("rocksdb: Inconsistent data in blockTxs ", hex.EncodeToString(buf)) + return nil, 0, errors.New("Inconsistent data in blockTxs") + } + txid := append([]byte(nil), buf[pos:pos+eth.EthereumTypeTxidLen]...) + pos += eth.EthereumTypeTxidLen + from, pos, err = getAddress(pos) + if err != nil { + return nil, 0, err + } + to, pos, err = getAddress(pos) + if err != nil { + return nil, 0, err + } + // contracts + cc, l := unpackVaruint(buf[pos:]) + pos += l + contracts := make([]ethBlockTxContract, cc) + for j := range contracts { + c := &contracts[j] + c.from, pos, err = getAddress(pos) + if err != nil { + return nil, 0, err + } + c.to, pos, err = getAddress(pos) + if err != nil { + return nil, 0, err + } + c.contract, pos, err = getAddress(pos) + if err != nil { + return nil, 0, err + } + cc, l = unpackVaruint(buf[pos:]) + c.transferType = bchain.TokenTransferType(cc) + pos += l + if c.transferType == bchain.ERC1155 { + cc, l = unpackVaruint(buf[pos:]) + pos += l + c.idValues = make([]bchain.TokenTransferIdValue, cc) + for i := range c.idValues { + c.idValues[i].Id, l = unpackBigint(buf[pos:]) + pos += l + c.idValues[i].Value, l = unpackBigint(buf[pos:]) + pos += l + } + } else { // ERC20, ERC721 + c.value, l = unpackBigint(buf[pos:]) + pos += l + } + } + return ðBlockTx{ + btxID: txid, + from: from, + to: to, + contracts: contracts, + }, pos, nil +} + func (d *RocksDB) getBlockTxsEthereumType(height uint32) ([]ethBlockTx, error) { - pl := d.chainParser.PackedTxidLen() val, err := d.db.GetCF(d.ro, d.cfh[cfBlockTxs], packUint(height)) if err != nil { return nil, err @@ -514,187 +748,170 @@ func (d *RocksDB) getBlockTxsEthereumType(height uint32) ([]ethBlockTx, error) { return nil, nil } // buf can be empty slice, this means the block did not contain any transactions - bt := make([]ethBlockTx, 0, 8) - getAddress := func(i int) (bchain.AddressDescriptor, int, error) { - if len(buf)-i < eth.EthereumTypeAddressDescriptorLen { - glog.Error("rocksdb: Inconsistent data in blockTxs ", hex.EncodeToString(buf)) - return nil, 0, errors.New("Inconsistent data in blockTxs") - } - a := append(bchain.AddressDescriptor(nil), buf[i:i+eth.EthereumTypeAddressDescriptorLen]...) - // return null addresses as nil - for _, b := range a { - if b != 0 { - return a, i + eth.EthereumTypeAddressDescriptorLen, nil - } - } - return nil, i + eth.EthereumTypeAddressDescriptorLen, nil - } - var from, to bchain.AddressDescriptor + bt := make([]ethBlockTx, 0, 16) + var btx *ethBlockTx for i := 0; i < len(buf); { - if len(buf)-i < pl { - glog.Error("rocksdb: Inconsistent data in blockTxs ", hex.EncodeToString(buf)) - return nil, errors.New("Inconsistent data in blockTxs") - } - txid := append([]byte(nil), buf[i:i+pl]...) - i += pl - from, i, err = getAddress(i) + btx, i, err = unpackBlockTx(buf, i) if err != nil { return nil, err } - to, i, err = getAddress(i) - if err != nil { - return nil, err - } - // internal data - var internalData *ethInternalData - cc, l := unpackVaruint(buf[i:]) - i += l - if cc > 0 { - internalData = ðInternalData{} - // odd count of internal transfers means it is CREATE transaction with the contract added to the list - if cc&1 == 1 { - internalData.internalType = bchain.CREATE - internalData.contract, i, err = getAddress(i) - if err != nil { - return nil, err - } - } - internalData.transfers = make([]ethInternalTransfer, cc/2) - for j := range internalData.transfers { - t := &internalData.transfers[j] - t.from, i, err = getAddress(i) - t.to, i, err = getAddress(i) - if err != nil { - return nil, err - } - } - } - // contracts - cc, l = unpackVaruint(buf[i:]) - i += l - contracts := make([]ethBlockTxContract, cc) - for j := range contracts { - contracts[j].addr, i, err = getAddress(i) - if err != nil { - return nil, err - } - contracts[j].contract, i, err = getAddress(i) - if err != nil { - return nil, err - } - } - bt = append(bt, ethBlockTx{ - btxID: txid, - from: from, - to: to, - internalData: internalData, - contracts: contracts, - }) + bt = append(bt, *btx) } return bt, nil } +func (d *RocksDB) disconnectAddress(btxID []byte, internal bool, addrDesc bchain.AddressDescriptor, btxContract *ethBlockTxContract, addresses map[string]map[string]struct{}, contracts map[string]*AddrContracts) error { + var err error + // do not process empty address + if len(addrDesc) == 0 { + return nil + } + s := string(addrDesc) + txid := string(btxID) + // find if tx for this address was already encountered + mtx, ftx := addresses[s] + if !ftx { + mtx = make(map[string]struct{}) + mtx[txid] = struct{}{} + addresses[s] = mtx + } else { + _, ftx = mtx[txid] + if !ftx { + mtx[txid] = struct{}{} + } + } + addrContracts, fc := contracts[s] + if !fc { + addrContracts, err = d.GetAddrDescContracts(addrDesc) + if err != nil { + return err + } + if addrContracts != nil { + contracts[s] = addrContracts + } + } + if addrContracts != nil { + if !ftx { + addrContracts.TotalTxs-- + } + if btxContract == nil { + if internal { + if addrContracts.InternalTxs > 0 { + addrContracts.InternalTxs-- + } else { + glog.Warning("AddressContracts ", addrDesc, ", InternalTxs would be negative, tx ", hex.EncodeToString(btxID)) + } + } else { + if addrContracts.NonContractTxs > 0 { + addrContracts.NonContractTxs-- + } else { + glog.Warning("AddressContracts ", addrDesc, ", EthTxs would be negative, tx ", hex.EncodeToString(btxID)) + } + } + } else { + contractIndex, found := findContractInAddressContracts(btxContract.contract, addrContracts.Contracts) + if found { + addrContract := &addrContracts.Contracts[contractIndex] + if addrContract.Txs > 0 { + addrContract.Txs-- + if addrContract.Txs == 0 { + // no transactions, remove the contract + addrContracts.Contracts = append(addrContracts.Contracts[:contractIndex], addrContracts.Contracts[contractIndex+1:]...) + } else { + // update the values of the contract, reverse the direction + var index int32 + if bytes.Equal(addrDesc, btxContract.to) { + index = transferFrom + } else { + index = transferTo + } + addToContract(addrContract, contractIndex, index, btxContract.contract, &bchain.TokenTransfer{ + Type: btxContract.transferType, + Value: btxContract.value, + IdValues: btxContract.idValues, + }, false) + } + } else { + glog.Warning("AddressContracts ", addrDesc, ", contract ", contractIndex, " Txs would be negative, tx ", hex.EncodeToString(btxID)) + } + } else { + glog.Warning("AddressContracts ", addrDesc, ", contract ", btxContract.contract, " not found, tx ", hex.EncodeToString(btxID)) + } + } + } else { + if !isZeroAddress(addrDesc) { + glog.Warning("AddressContracts ", addrDesc, " not found, tx ", hex.EncodeToString(btxID)) + } + } + return nil +} + +func (d *RocksDB) disconnectInternalData(btxID []byte, addresses map[string]map[string]struct{}, contracts map[string]*AddrContracts) error { + internalData, err := d.getEthereumInternalData(btxID) + if err != nil { + return err + } + if internalData != nil { + if internalData.Type == bchain.CREATE { + contract, err := d.chainParser.GetAddrDescFromAddress(internalData.Contract) + if err != nil { + return err + } + if err := d.disconnectAddress(btxID, true, contract, nil, addresses, contracts); err != nil { + return err + } + } + for j := range internalData.Transfers { + t := &internalData.Transfers[j] + var from, to bchain.AddressDescriptor + from, err = d.chainParser.GetAddrDescFromAddress(t.From) + if err == nil { + to, err = d.chainParser.GetAddrDescFromAddress(t.To) + } + if err != nil { + return err + } + if err := d.disconnectAddress(btxID, true, from, nil, addresses, contracts); err != nil { + return err + } + // if from==to, tx is counted only once and does not have to be disconnected again + if !bytes.Equal(from, to) { + if err := d.disconnectAddress(btxID, true, to, nil, addresses, contracts); err != nil { + return err + } + } + } + } + return nil +} + func (d *RocksDB) disconnectBlockTxsEthereumType(wb *gorocksdb.WriteBatch, height uint32, blockTxs []ethBlockTx, contracts map[string]*AddrContracts) error { glog.Info("Disconnecting block ", height, " containing ", len(blockTxs), " transactions") addresses := make(map[string]map[string]struct{}) - disconnectAddress := func(btxID []byte, internal bool, addrDesc, contract bchain.AddressDescriptor) error { - var err error - // do not process empty address - if len(addrDesc) == 0 { - return nil - } - s := string(addrDesc) - txid := string(btxID) - // find if tx for this address was already encountered - mtx, ftx := addresses[s] - if !ftx { - mtx = make(map[string]struct{}) - mtx[txid] = struct{}{} - addresses[s] = mtx - } else { - _, ftx = mtx[txid] - if !ftx { - mtx[txid] = struct{}{} - } - } - c, fc := contracts[s] - if !fc { - c, err = d.GetAddrDescContracts(addrDesc) - if err != nil { - return err - } - contracts[s] = c - } - if c != nil { - if !ftx { - c.TotalTxs-- - } - if contract == nil { - if internal { - if c.InternalTxs > 0 { - c.InternalTxs-- - } else { - glog.Warning("AddressContracts ", addrDesc, ", InternalTxs would be negative, tx ", hex.EncodeToString(btxID)) - } - } else { - if c.NonContractTxs > 0 { - c.NonContractTxs-- - } else { - glog.Warning("AddressContracts ", addrDesc, ", EthTxs would be negative, tx ", hex.EncodeToString(btxID)) - } - } - } else { - i, found := findContractInAddressContracts(contract, c.Contracts) - if found { - if c.Contracts[i].Txs > 0 { - c.Contracts[i].Txs-- - if c.Contracts[i].Txs == 0 { - c.Contracts = append(c.Contracts[:i], c.Contracts[i+1:]...) - } - } else { - glog.Warning("AddressContracts ", addrDesc, ", contract ", i, " Txs would be negative, tx ", hex.EncodeToString(btxID)) - } - } else { - glog.Warning("AddressContracts ", addrDesc, ", contract ", contract, " not found, tx ", hex.EncodeToString(btxID)) - } - } - } else { - glog.Warning("AddressContracts ", addrDesc, " not found, tx ", hex.EncodeToString(btxID)) - } - return nil - } for i := range blockTxs { blockTx := &blockTxs[i] - if err := disconnectAddress(blockTx.btxID, false, blockTx.from, nil); err != nil { + if err := d.disconnectAddress(blockTx.btxID, false, blockTx.from, nil, addresses, contracts); err != nil { return err } // if from==to, tx is counted only once and does not have to be disconnected again if !bytes.Equal(blockTx.from, blockTx.to) { - if err := disconnectAddress(blockTx.btxID, false, blockTx.to, nil); err != nil { + if err := d.disconnectAddress(blockTx.btxID, false, blockTx.to, nil, addresses, contracts); err != nil { return err } } - if blockTx.internalData != nil { - if blockTx.internalData.internalType == bchain.CREATE { - if err := disconnectAddress(blockTx.btxID, true, blockTx.internalData.contract, nil); err != nil { - return err - } - } - for j := range blockTx.internalData.transfers { - t := &blockTx.internalData.transfers[j] - if err := disconnectAddress(blockTx.btxID, true, t.from, nil); err != nil { - return err - } - // if from==to, tx is counted only once and does not have to be disconnected again - if !bytes.Equal(t.from, t.to) { - if err := disconnectAddress(blockTx.btxID, true, t.to, nil); err != nil { - return err - } - } - } + // internal data + err := d.disconnectInternalData(blockTx.btxID, addresses, contracts) + if err != nil { + return err + } - for _, c := range blockTx.contracts { - if err := disconnectAddress(blockTx.btxID, false, c.addr, c.contract); err != nil { + // contracts + for j := range blockTx.contracts { + c := &blockTx.contracts[j] + if err := d.disconnectAddress(blockTx.btxID, false, c.from, c, addresses, contracts); err != nil { + return err + } + if err := d.disconnectAddress(blockTx.btxID, false, c.to, c, addresses, contracts); err != nil { return err } } diff --git a/db/rocksdb_ethereumtype_test.go b/db/rocksdb_ethereumtype_test.go index fb5d70eb..6f9ed711 100644 --- a/db/rocksdb_ethereumtype_test.go +++ b/db/rocksdb_ethereumtype_test.go @@ -4,6 +4,7 @@ package db import ( "encoding/hex" + "math/big" "reflect" "testing" @@ -22,6 +23,12 @@ func ethereumTestnetParser() *eth.EthereumParser { return eth.NewEthereumParser(1) } +func bigintFromStringToHex(s string) string { + var b big.Int + b.SetString(s, 0) + return bigintToHex(&b) +} + func verifyAfterEthereumTypeBlock1(t *testing.T, d *RocksDB, afterDisconnect bool) { if err := checkColumn(d, cfHeight, []keyPair{ { @@ -35,7 +42,7 @@ func verifyAfterEthereumTypeBlock1(t *testing.T, d *RocksDB, afterDisconnect boo } } if err := checkColumn(d, cfAddresses, []keyPair{ - {addressKeyHex(dbtestdata.EthAddr3e, 4321000, d), txIndexesHex(dbtestdata.EthTxidB1T2, []int32{^1, 1, ^1}) + txIndexesHex(dbtestdata.EthTxidB1T1, []int32{^0}), nil}, + {addressKeyHex(dbtestdata.EthAddr3e, 4321000, d), txIndexesHex(dbtestdata.EthTxidB1T2, []int32{^1, 1}) + txIndexesHex(dbtestdata.EthTxidB1T1, []int32{^0}), nil}, {addressKeyHex(dbtestdata.EthAddr55, 4321000, d), txIndexesHex(dbtestdata.EthTxidB1T2, []int32{2}) + txIndexesHex(dbtestdata.EthTxidB1T1, []int32{0}), nil}, {addressKeyHex(dbtestdata.EthAddr20, 4321000, d), txIndexesHex(dbtestdata.EthTxidB1T2, []int32{^0, ^2}), nil}, {addressKeyHex(dbtestdata.EthAddr9f, 4321000, d), txIndexesHex(dbtestdata.EthTxidB1T2, []int32{^1, 1}), nil}, @@ -48,8 +55,14 @@ func verifyAfterEthereumTypeBlock1(t *testing.T, d *RocksDB, afterDisconnect boo if err := checkColumn(d, cfAddressContracts, []keyPair{ {dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr3e, d.chainParser), "020102", nil}, - {dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser), "020100" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + "01", nil}, - {dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr20, d.chainParser), "010100" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + "01", nil}, + { + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser), + "020100" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + varuintToHex(1<<2+uint(bchain.ERC20)) + 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, + }, {dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr9f, d.chainParser), "010002", nil}, {dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser), "010101", nil}, }); err != nil { @@ -81,16 +94,10 @@ func verifyAfterEthereumTypeBlock1(t *testing.T, d *RocksDB, afterDisconnect boo { "0041eee8", dbtestdata.EthTxidB1T1 + - dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr3e, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser) + "00" + "00" + + 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) + - "06" + - dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr9f, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + - dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr3e, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr9f, d.chainParser) + - dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr3e, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr3e, d.chainParser) + - "02" + - dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr20, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + - dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, 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"), nil, }, } @@ -111,7 +118,7 @@ func verifyAfterEthereumTypeBlock2(t *testing.T, d *RocksDB, wantBlockInternalDa }, { "0041eee9", - "2b57e15e93a0ed197417a34c2498b7187df79099572c04a6b6e6ff418f74e6ee" + uintToHex(1534859988) + varuintToHex(2) + varuintToHex(2345678), + "2b57e15e93a0ed197417a34c2498b7187df79099572c04a6b6e6ff418f74e6ee" + uintToHex(1534859988) + varuintToHex(6) + varuintToHex(2345678), nil, }, }); err != nil { @@ -120,18 +127,27 @@ func verifyAfterEthereumTypeBlock2(t *testing.T, d *RocksDB, wantBlockInternalDa } } if err := checkColumn(d, cfAddresses, []keyPair{ - {addressKeyHex(dbtestdata.EthAddr3e, 4321000, d), txIndexesHex(dbtestdata.EthTxidB1T2, []int32{^1, 1, ^1}) + txIndexesHex(dbtestdata.EthTxidB1T1, []int32{^0}), nil}, + {addressKeyHex(dbtestdata.EthAddr3e, 4321000, d), txIndexesHex(dbtestdata.EthTxidB1T2, []int32{^1, 1}) + txIndexesHex(dbtestdata.EthTxidB1T1, []int32{^0}), nil}, {addressKeyHex(dbtestdata.EthAddr55, 4321000, d), txIndexesHex(dbtestdata.EthTxidB1T2, []int32{2}) + txIndexesHex(dbtestdata.EthTxidB1T1, []int32{0}), nil}, {addressKeyHex(dbtestdata.EthAddr20, 4321000, d), txIndexesHex(dbtestdata.EthTxidB1T2, []int32{^0, ^2}), nil}, {addressKeyHex(dbtestdata.EthAddr9f, 4321000, d), txIndexesHex(dbtestdata.EthTxidB1T2, []int32{^1, 1}), nil}, {addressKeyHex(dbtestdata.EthAddrContract4a, 4321000, d), txIndexesHex(dbtestdata.EthTxidB1T2, []int32{0, 1}), nil}, - {addressKeyHex(dbtestdata.EthAddr55, 4321001, d), txIndexesHex(dbtestdata.EthTxidB2T2, []int32{^3, 2}) + txIndexesHex(dbtestdata.EthTxidB2T1, []int32{^0}), nil}, - {addressKeyHex(dbtestdata.EthAddr9f, 4321001, d), txIndexesHex(dbtestdata.EthTxidB2T2, []int32{1, 1}) + txIndexesHex(dbtestdata.EthTxidB2T1, []int32{0}), nil}, + + {addressKeyHex(dbtestdata.EthAddrZero, 4321001, d), txIndexesHex(dbtestdata.EthTxidB2T5, []int32{transferFrom}), nil}, + {addressKeyHex(dbtestdata.EthAddr3e, 4321001, d), txIndexesHex(dbtestdata.EthTxidB2T4, []int32{^0, 2}), nil}, {addressKeyHex(dbtestdata.EthAddr4b, 4321001, d), txIndexesHex(dbtestdata.EthTxidB2T2, []int32{^0, ^1, 2, ^3, 3, ^2}), nil}, - {addressKeyHex(dbtestdata.EthAddr7b, 4321001, d), txIndexesHex(dbtestdata.EthTxidB2T2, []int32{^2, 3}), nil}, + {addressKeyHex(dbtestdata.EthAddr55, 4321001, d), txIndexesHex(dbtestdata.EthTxidB2T6, []int32{0, ^0, 4, ^4}) + txIndexesHex(dbtestdata.EthTxidB2T2, []int32{^3, 2}) + txIndexesHex(dbtestdata.EthTxidB2T1, []int32{^0}), nil}, + {addressKeyHex(dbtestdata.EthAddr5d, 4321001, d), txIndexesHex(dbtestdata.EthTxidB2T5, []int32{^0, 2}), nil}, + {addressKeyHex(dbtestdata.EthAddr7b, 4321001, d), txIndexesHex(dbtestdata.EthTxidB2T3, []int32{4}) + txIndexesHex(dbtestdata.EthTxidB2T2, []int32{^2, 3}), nil}, + {addressKeyHex(dbtestdata.EthAddr83, 4321001, d), txIndexesHex(dbtestdata.EthTxidB2T3, []int32{^0, ^2}), nil}, + {addressKeyHex(dbtestdata.EthAddr92, 4321001, d), txIndexesHex(dbtestdata.EthTxidB2T4, []int32{0}), nil}, + {addressKeyHex(dbtestdata.EthAddr9f, 4321001, d), txIndexesHex(dbtestdata.EthTxidB2T2, []int32{1}) + txIndexesHex(dbtestdata.EthTxidB2T1, []int32{0}), nil}, + {addressKeyHex(dbtestdata.EthAddrA3, 4321001, d), txIndexesHex(dbtestdata.EthTxidB2T4, []int32{^2}), nil}, {addressKeyHex(dbtestdata.EthAddrContract0d, 4321001, d), txIndexesHex(dbtestdata.EthTxidB2T2, []int32{1}), nil}, {addressKeyHex(dbtestdata.EthAddrContract47, 4321001, d), txIndexesHex(dbtestdata.EthTxidB2T2, []int32{0}), nil}, {addressKeyHex(dbtestdata.EthAddrContract4a, 4321001, d), txIndexesHex(dbtestdata.EthTxidB2T2, []int32{^1}), nil}, + {addressKeyHex(dbtestdata.EthAddrContract6f, 4321001, d), txIndexesHex(dbtestdata.EthTxidB2T5, []int32{0}), nil}, + {addressKeyHex(dbtestdata.EthAddrContractCd, 4321001, d), txIndexesHex(dbtestdata.EthTxidB2T3, []int32{0}), nil}, }); err != nil { { t.Fatal(err) @@ -139,15 +155,53 @@ func verifyAfterEthereumTypeBlock2(t *testing.T, d *RocksDB, wantBlockInternalDa } if err := checkColumn(d, cfAddressContracts, []keyPair{ - {dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr3e, d.chainParser), "020102", nil}, - {dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser), "040200" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + "02" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract0d, d.chainParser) + "01", nil}, - {dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr20, d.chainParser), "010100" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + "01", nil}, - {dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser), "020102", 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, + }, + { + 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, + }, + { + 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.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.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, + }, + { + 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.EthAddr83, d.chainParser), + "010100" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContractCd, d.chainParser) + varuintToHex(1<<2+uint(bchain.ERC721)) + 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, + }, + {dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr92, d.chainParser), "010100", nil}, {dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr9f, d.chainParser), "030104", nil}, - {dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr4b, d.chainParser), "010101" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract0d, d.chainParser) + "02" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + "02", nil}, - {dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr7b, d.chainParser), "010000" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + "01" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract0d, d.chainParser) + "01", nil}, {dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract0d, d.chainParser), "010001", nil}, {dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract47, d.chainParser), "010100", nil}, + {dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser), "020102", nil}, + {dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract6f, d.chainParser), "010100", nil}, + {dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContractCd, d.chainParser), "010100", nil}, }); err != nil { { t.Fatal(err) @@ -185,22 +239,25 @@ func verifyAfterEthereumTypeBlock2(t *testing.T, d *RocksDB, wantBlockInternalDa { "0041eee9", dbtestdata.EthTxidB2T1 + - dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr9f, d.chainParser) + "00" + "00" + + 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) + - "05" + - dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract0d, d.chainParser) + - dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr4b, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr9f, d.chainParser) + - dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr9f, d.chainParser) + - "08" + - dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract0d, d.chainParser) + - dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr4b, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract0d, d.chainParser) + - dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr4b, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + - dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + - dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr7b, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + - dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr4b, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + - dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr4b, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract0d, d.chainParser) + - dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr7b, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract0d, 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") + + 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") + + 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") + + 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") + + 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"), nil, }, }); err != nil { @@ -285,6 +342,10 @@ func TestRocksDB_Index_EthereumType(t *testing.T) { // get transactions for various addresses / low-high ranges verifyGetTransactions(t, d, "0x"+dbtestdata.EthAddr55, 0, 10000000, []txidIndex{ + {"0x" + dbtestdata.EthTxidB2T6, 0}, + {"0x" + dbtestdata.EthTxidB2T6, ^0}, + {"0x" + dbtestdata.EthTxidB2T6, 4}, + {"0x" + dbtestdata.EthTxidB2T6, ^4}, {"0x" + dbtestdata.EthTxidB2T2, ^3}, {"0x" + dbtestdata.EthTxidB2T2, 2}, {"0x" + dbtestdata.EthTxidB2T1, ^0}, @@ -347,7 +408,7 @@ func TestRocksDB_Index_EthereumType(t *testing.T) { } iw := &BlockInfo{ Hash: "0x2b57e15e93a0ed197417a34c2498b7187df79099572c04a6b6e6ff418f74e6ee", - Txs: 2, + Txs: 6, Size: 2345678, Time: 1534859988, Height: 4321001, @@ -468,3 +529,607 @@ func Test_BulkConnect_EthereumType(t *testing.T) { t.Fatal("Expecting is.BlockTimes 4321002, got ", len(d.is.BlockTimes)) } } + +func Test_packUnpackEthInternalData(t *testing.T) { + parser := ethereumTestnetParser() + db := &RocksDB{chainParser: parser} + tests := []struct { + name string + data ethInternalData + want *bchain.EthereumInternalData + }{ + { + name: "CALL 1", + data: ethInternalData{ + internalType: bchain.CALL, + transfers: []ethInternalTransfer{ + { + internalType: bchain.CALL, + from: addressToAddrDesc(dbtestdata.EthAddr3e, parser), + to: addressToAddrDesc(dbtestdata.EthAddr20, parser), + value: *big.NewInt(412342134), + }, + }, + }, + want: &bchain.EthereumInternalData{ + Type: bchain.CALL, + Transfers: []bchain.EthereumInternalTransfer{ + { + Type: bchain.CALL, + From: eth.EIP55AddressFromAddress(dbtestdata.EthAddr3e), + To: eth.EIP55AddressFromAddress(dbtestdata.EthAddr20), + Value: *big.NewInt(412342134), + }, + }, + }, + }, + { + name: "CALL 2", + data: ethInternalData{ + internalType: bchain.CALL, + errorMsg: "error error error", + transfers: []ethInternalTransfer{ + { + internalType: bchain.CALL, + from: addressToAddrDesc(dbtestdata.EthAddr3e, parser), + to: addressToAddrDesc(dbtestdata.EthAddr20, parser), + value: *big.NewInt(4123421341), + }, + { + internalType: bchain.CREATE, + from: addressToAddrDesc(dbtestdata.EthAddr4b, parser), + to: addressToAddrDesc(dbtestdata.EthAddr55, parser), + value: *big.NewInt(123), + }, + { + internalType: bchain.SELFDESTRUCT, + from: addressToAddrDesc(dbtestdata.EthAddr7b, parser), + to: addressToAddrDesc(dbtestdata.EthAddr83, parser), + value: *big.NewInt(67890), + }, + }, + }, + want: &bchain.EthereumInternalData{ + Type: bchain.CALL, + Error: "error error error", + Transfers: []bchain.EthereumInternalTransfer{ + { + Type: bchain.CALL, + From: eth.EIP55AddressFromAddress(dbtestdata.EthAddr3e), + To: eth.EIP55AddressFromAddress(dbtestdata.EthAddr20), + Value: *big.NewInt(4123421341), + }, + { + Type: bchain.CREATE, + From: eth.EIP55AddressFromAddress(dbtestdata.EthAddr4b), + To: eth.EIP55AddressFromAddress(dbtestdata.EthAddr55), + Value: *big.NewInt(123), + }, + { + Type: bchain.SELFDESTRUCT, + From: eth.EIP55AddressFromAddress(dbtestdata.EthAddr7b), + To: eth.EIP55AddressFromAddress(dbtestdata.EthAddr83), + Value: *big.NewInt(67890), + }, + }, + }, + }, + { + name: "CREATE", + data: ethInternalData{ + internalType: bchain.CREATE, + contract: addressToAddrDesc(dbtestdata.EthAddrContract0d, parser), + }, + want: &bchain.EthereumInternalData{ + Type: bchain.CREATE, + Contract: eth.EIP55AddressFromAddress(dbtestdata.EthAddrContract0d), + Transfers: []bchain.EthereumInternalTransfer{}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + packed := packEthInternalData(&tt.data) + got, err := db.unpackEthInternalData(packed) + if err != nil { + t.Errorf("unpackEthInternalData() error = %v", err) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("packEthInternalData/unpackEthInternalData = %+v, want %+v", got, tt.want) + } + }) + } +} + +func Test_packUnpackAddrContracts(t *testing.T) { + parser := ethereumTestnetParser() + type args struct { + buf []byte + addrDesc bchain.AddressDescriptor + } + tests := []struct { + name string + data AddrContracts + }{ + { + name: "1", + data: AddrContracts{ + TotalTxs: 30, + NonContractTxs: 20, + InternalTxs: 10, + Contracts: []AddrContract{}, + }, + }, + { + name: "2", + data: AddrContracts{ + TotalTxs: 12345, + NonContractTxs: 444, + InternalTxs: 8873, + Contracts: []AddrContract{ + { + Type: bchain.ERC20, + Contract: addressToAddrDesc(dbtestdata.EthAddrContract0d, parser), + Txs: 8, + Value: *big.NewInt(793201132), + }, + { + Type: bchain.ERC721, + Contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser), + Txs: 41235, + Ids: []big.Int{ + *big.NewInt(1), + *big.NewInt(2), + *big.NewInt(3), + *big.NewInt(3144223412344123), + *big.NewInt(5), + }, + }, + { + Type: bchain.ERC1155, + Contract: addressToAddrDesc(dbtestdata.EthAddrContract4a, parser), + Txs: 64, + IdValues: []bchain.TokenTransferIdValue{ + { + Id: *big.NewInt(1), + Value: *big.NewInt(1412341234), + }, + { + Id: *big.NewInt(123412341234), + Value: *big.NewInt(3), + }, + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + packed := packAddrContracts(&tt.data) + got, err := unpackAddrContracts(packed, nil) + if err != nil { + t.Errorf("unpackAddrContracts() error = %v", err) + return + } + if !reflect.DeepEqual(got, &tt.data) { + t.Errorf("unpackAddrContracts() = %v, want %v", got, tt.data) + } + }) + } +} + +func Test_addToContracts(t *testing.T) { + // the test builds addToContracts that keeps contracts of an address + // the test adds and removes values from addToContracts, therefore the order of tests is important + addrContracts := &AddrContracts{} + parser := ethereumTestnetParser() + type args struct { + index int32 + contract bchain.AddressDescriptor + transfer *bchain.TokenTransfer + addTxCount bool + } + tests := []struct { + name string + args args + wantIndex int32 + wantAddrContracts *AddrContracts + }{ + { + name: "ERC20 to", + args: args{ + index: 1, + contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser), + transfer: &bchain.TokenTransfer{ + Type: bchain.ERC20, + Value: *big.NewInt(123456), + }, + addTxCount: true, + }, + wantIndex: 0 + ContractIndexOffset, // the first contract of the address + wantAddrContracts: &AddrContracts{ + Contracts: []AddrContract{ + { + Type: bchain.ERC20, + Contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser), + Txs: 1, + Value: *big.NewInt(123456), + }, + }, + }, + }, + { + name: "ERC20 from", + args: args{ + index: ^1, + contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser), + transfer: &bchain.TokenTransfer{ + Type: bchain.ERC20, + Value: *big.NewInt(23456), + }, + addTxCount: true, + }, + wantIndex: ^(0 + ContractIndexOffset), // the first contract of the address + wantAddrContracts: &AddrContracts{ + Contracts: []AddrContract{ + { + Type: bchain.ERC20, + Contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser), + Value: *big.NewInt(100000), + Txs: 2, + }, + }, + }, + }, + { + name: "ERC721 to id 1", + args: args{ + index: 1, + contract: addressToAddrDesc(dbtestdata.EthAddrContract6f, parser), + transfer: &bchain.TokenTransfer{ + Type: bchain.ERC721, + Value: *big.NewInt(1), + }, + addTxCount: true, + }, + wantIndex: 1 + ContractIndexOffset, // the 2nd contract of the address + wantAddrContracts: &AddrContracts{ + Contracts: []AddrContract{ + { + Type: bchain.ERC20, + Contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser), + Value: *big.NewInt(100000), + Txs: 2, + }, + { + Type: bchain.ERC721, + Contract: addressToAddrDesc(dbtestdata.EthAddrContract6f, parser), + Txs: 1, + Ids: []big.Int{*big.NewInt(1)}, + }, + }, + }, + }, + { + name: "ERC721 to id 2", + args: args{ + index: 1, + contract: addressToAddrDesc(dbtestdata.EthAddrContract6f, parser), + transfer: &bchain.TokenTransfer{ + Type: bchain.ERC721, + Value: *big.NewInt(2), + }, + addTxCount: true, + }, + wantIndex: 1 + ContractIndexOffset, // the 2nd contract of the address + wantAddrContracts: &AddrContracts{ + Contracts: []AddrContract{ + { + Type: bchain.ERC20, + Contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser), + Value: *big.NewInt(100000), + Txs: 2, + }, + { + Type: bchain.ERC721, + Contract: addressToAddrDesc(dbtestdata.EthAddrContract6f, parser), + Txs: 2, + Ids: []big.Int{*big.NewInt(1), *big.NewInt(2)}, + }, + }, + }, + }, + { + name: "ERC721 from id 1, addTxCount=false", + args: args{ + index: ^1, + contract: addressToAddrDesc(dbtestdata.EthAddrContract6f, parser), + transfer: &bchain.TokenTransfer{ + Type: bchain.ERC721, + Value: *big.NewInt(1), + }, + addTxCount: false, + }, + wantIndex: ^(1 + ContractIndexOffset), // the 2nd contract of the address + wantAddrContracts: &AddrContracts{ + Contracts: []AddrContract{ + { + Type: bchain.ERC20, + Contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser), + Value: *big.NewInt(100000), + Txs: 2, + }, + { + Type: bchain.ERC721, + Contract: addressToAddrDesc(dbtestdata.EthAddrContract6f, parser), + Txs: 2, + Ids: []big.Int{*big.NewInt(2)}, + }, + }, + }, + }, + { + name: "ERC1155 to id 11, value 56789", + args: args{ + index: 1, + contract: addressToAddrDesc(dbtestdata.EthAddrContractCd, parser), + transfer: &bchain.TokenTransfer{ + Type: bchain.ERC1155, + IdValues: []bchain.TokenTransferIdValue{ + { + Id: *big.NewInt(11), + Value: *big.NewInt(56789), + }, + }, + }, + addTxCount: true, + }, + wantIndex: 2 + ContractIndexOffset, // the 3nd contract of the address + wantAddrContracts: &AddrContracts{ + Contracts: []AddrContract{ + { + Type: bchain.ERC20, + Contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser), + Value: *big.NewInt(100000), + Txs: 2, + }, + { + Type: bchain.ERC721, + Contract: addressToAddrDesc(dbtestdata.EthAddrContract6f, parser), + Txs: 2, + Ids: []big.Int{*big.NewInt(2)}, + }, + { + Type: bchain.ERC1155, + Contract: addressToAddrDesc(dbtestdata.EthAddrContractCd, parser), + Txs: 1, + IdValues: []bchain.TokenTransferIdValue{ + { + Id: *big.NewInt(11), + Value: *big.NewInt(56789), + }, + }, + }, + }, + }, + }, + { + name: "ERC1155 to id 11, value 111 and id 22, value 222", + args: args{ + index: 1, + contract: addressToAddrDesc(dbtestdata.EthAddrContractCd, parser), + transfer: &bchain.TokenTransfer{ + Type: bchain.ERC1155, + IdValues: []bchain.TokenTransferIdValue{ + { + Id: *big.NewInt(11), + Value: *big.NewInt(111), + }, + { + Id: *big.NewInt(22), + Value: *big.NewInt(222), + }, + }, + }, + addTxCount: true, + }, + wantIndex: 2 + ContractIndexOffset, // the 3nd contract of the address + wantAddrContracts: &AddrContracts{ + Contracts: []AddrContract{ + { + Type: bchain.ERC20, + Contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser), + Value: *big.NewInt(100000), + Txs: 2, + }, + { + Type: bchain.ERC721, + Contract: addressToAddrDesc(dbtestdata.EthAddrContract6f, parser), + Txs: 2, + Ids: []big.Int{*big.NewInt(2)}, + }, + { + Type: bchain.ERC1155, + Contract: addressToAddrDesc(dbtestdata.EthAddrContractCd, parser), + Txs: 2, + IdValues: []bchain.TokenTransferIdValue{ + { + Id: *big.NewInt(11), + Value: *big.NewInt(56900), + }, + { + Id: *big.NewInt(22), + Value: *big.NewInt(222), + }, + }, + }, + }, + }, + }, + { + name: "ERC1155 from id 11, value 112 and id 22, value 222", + args: args{ + index: ^1, + contract: addressToAddrDesc(dbtestdata.EthAddrContractCd, parser), + transfer: &bchain.TokenTransfer{ + Type: bchain.ERC1155, + IdValues: []bchain.TokenTransferIdValue{ + { + Id: *big.NewInt(11), + Value: *big.NewInt(112), + }, + { + Id: *big.NewInt(22), + Value: *big.NewInt(222), + }, + }, + }, + addTxCount: true, + }, + wantIndex: ^(2 + ContractIndexOffset), // the 3nd contract of the address + wantAddrContracts: &AddrContracts{ + Contracts: []AddrContract{ + { + Type: bchain.ERC20, + Contract: addressToAddrDesc(dbtestdata.EthAddrContract47, parser), + Value: *big.NewInt(100000), + Txs: 2, + }, + { + Type: bchain.ERC721, + Contract: addressToAddrDesc(dbtestdata.EthAddrContract6f, parser), + Txs: 2, + Ids: []big.Int{*big.NewInt(2)}, + }, + { + Type: bchain.ERC1155, + Contract: addressToAddrDesc(dbtestdata.EthAddrContractCd, parser), + Txs: 3, + IdValues: []bchain.TokenTransferIdValue{ + { + Id: *big.NewInt(11), + Value: *big.NewInt(56788), + }, + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + contractIndex, found := findContractInAddressContracts(tt.args.contract, addrContracts.Contracts) + if !found { + contractIndex = len(addrContracts.Contracts) + addrContracts.Contracts = append(addrContracts.Contracts, AddrContract{ + Contract: tt.args.contract, + Type: tt.args.transfer.Type, + }) + } + if got := addToContract(&addrContracts.Contracts[contractIndex], contractIndex, tt.args.index, tt.args.contract, tt.args.transfer, tt.args.addTxCount); got != tt.wantIndex { + t.Errorf("addToContracts() = %v, want %v", got, tt.wantIndex) + } + if !reflect.DeepEqual(addrContracts, tt.wantAddrContracts) { + t.Errorf("addToContracts() = %+v, want %+v", addrContracts, tt.wantAddrContracts) + } + }) + } +} + +func Test_packUnpackBlockTx(t *testing.T) { + parser := ethereumTestnetParser() + tests := []struct { + name string + blockTx ethBlockTx + pos int + }{ + { + name: "no contract", + blockTx: ethBlockTx{ + btxID: hexToBytes(dbtestdata.EthTxidB1T1), + from: addressToAddrDesc(dbtestdata.EthAddr3e, parser), + to: addressToAddrDesc(dbtestdata.EthAddr55, parser), + contracts: []ethBlockTxContract{}, + }, + pos: 73, + }, + { + name: "ERC20", + blockTx: ethBlockTx{ + btxID: hexToBytes(dbtestdata.EthTxidB1T1), + from: addressToAddrDesc(dbtestdata.EthAddr3e, parser), + to: addressToAddrDesc(dbtestdata.EthAddr55, parser), + contracts: []ethBlockTxContract{ + { + from: addressToAddrDesc(dbtestdata.EthAddr20, parser), + to: addressToAddrDesc(dbtestdata.EthAddr5d, parser), + contract: addressToAddrDesc(dbtestdata.EthAddrContract4a, parser), + transferType: bchain.ERC20, + value: *big.NewInt(10000), + }, + }, + }, + pos: 137, + }, + { + name: "multiple contracts", + blockTx: ethBlockTx{ + btxID: hexToBytes(dbtestdata.EthTxidB1T1), + from: addressToAddrDesc(dbtestdata.EthAddr3e, parser), + to: addressToAddrDesc(dbtestdata.EthAddr55, parser), + contracts: []ethBlockTxContract{ + { + from: addressToAddrDesc(dbtestdata.EthAddr20, parser), + to: addressToAddrDesc(dbtestdata.EthAddr3e, parser), + contract: addressToAddrDesc(dbtestdata.EthAddrContract4a, parser), + transferType: bchain.ERC20, + value: *big.NewInt(987654321), + }, + { + from: addressToAddrDesc(dbtestdata.EthAddr4b, parser), + to: addressToAddrDesc(dbtestdata.EthAddr55, parser), + contract: addressToAddrDesc(dbtestdata.EthAddrContract6f, parser), + transferType: bchain.ERC721, + 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{ + { + Id: *big.NewInt(1234), + Value: *big.NewInt(98765), + }, + { + Id: *big.NewInt(5566), + Value: *big.NewInt(12341234421), + }, + }, + }, + }, + }, + pos: 280, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + buf := make([]byte, 0) + packed := packBlockTx(buf, &tt.blockTx) + got, pos, err := unpackBlockTx(packed, 0) + if err != nil { + t.Errorf("unpackBlockTx() error = %v", err) + return + } + if !reflect.DeepEqual(*got, tt.blockTx) { + t.Errorf("unpackBlockTx() got = %v, want %v", *got, tt.blockTx) + } + if pos != tt.pos { + t.Errorf("unpackBlockTx() pos = %v, want %v", pos, tt.pos) + } + }) + } +} diff --git a/db/rocksdb_test.go b/db/rocksdb_test.go index f5118501..22912104 100644 --- a/db/rocksdb_test.go +++ b/db/rocksdb_test.go @@ -152,10 +152,10 @@ func checkColumn(d *RocksDB, col int, kp []keyPair) error { defer it.Close() i := 0 for it.SeekToFirst(); it.Valid(); it.Next() { - if i >= len(kp) { - return errors.Errorf("Expected less rows in column %v", cfNames[col]) - } key := hex.EncodeToString(it.Key().Data()) + if i >= len(kp) { + return errors.Errorf("Expected less rows in column %v, superfluous key %v", cfNames[col], key) + } if key != kp[i].Key { return errors.Errorf("Incorrect key %v found in column %v row %v, expecting %v", key, cfNames[col], i, kp[i].Key) } diff --git a/docs/rocksdb.md b/docs/rocksdb.md index 3860a70b..755049d2 100644 --- a/docs/rocksdb.md +++ b/docs/rocksdb.md @@ -84,9 +84,21 @@ Column families used only by **Ethereum type** coins: - **addressContracts** (used only by Ethereum type coins) - Maps *addrDesc* to *total number of transactions*, *number of non contract transactions*, *number of internal transactions* and array of *contracts* with *number of transfers* of given address. + Maps *addrDesc* to *total number of transactions*, *number of non contract transactions*, *number of internal transactions* + and array of *contracts* with *number of transfers* of given address. ``` - (addrDesc []byte) -> (total_txs vuint)+(non-contract_txs vuint)+(internal_txs vuint)+[]((contractAddrDesc []byte)+(nr_transfers vuint)) + (addrDesc []byte) -> (total_txs vuint)+(non-contract_txs vuint)+(internal_txs vuint)+ + []((contractAddrDesc []byte)+(type+4*nr_transfers vuint))+ + <(value bigInt) if ERC20> or <(nr_values vuint)+[](id bigInt) if ERC721> or <(nr_values vuint)+[]((id bigInt)+(value bigInt)) if ERC1155> + ``` + +- **internalData** (used only by Ethereum type coins) + + Maps *txid* to *type (CALL 0 | CREATE 1)*, *addrDesc of created contract for CREATE type*, array of *type (CALL 0 | CREATE 1 | SELFDESTRUCT 2)*, *from addrDesc*, *to addrDesc*, *value bigInt* and possible *error*. + ``` + (txid []byte) -> (type+2*nr_transfers vuint)+<(addrDesc []byte) if CREATE>+ + []((type byte)+(fromAddrDesc []byte)+(toAddrDesc []byte)+(value bigInt))+ + (error []byte) ``` - **blockTxs** @@ -104,9 +116,14 @@ Column families used only by **Ethereum type** coins: - Ethereum type The value is an array of transaction data. For each transaction is stored *txid*, - *from* and *to* address descriptors and array of *contract address descriptors* with *transfer address descriptors*. + *from* and *to* address descriptors and array of contract transfer infos consisting of + *from*, *to* and *contract* address descriptors, *type (ERC20 0 | ERC721 1 | ERC1155 2)* and value (or list of id+value for ERC1155) ``` - (height uint32) -> []((txid [32]byte)+(from addrDesc)+(to addrDesc)+(nr_contracts vuint)+[]((contract addrDesc)+(addr addrDesc))) + (height uint32) -> []( + (txid [32]byte)+(from addrDesc)+(to addrDesc)+(nr_contracts vuint)+ + []((from addrDesc)+(to addrDesc)+(contract addrDesc)+(type byte)+ + <(value bigInt) if ERC20 or ERC721> or <(nr_values vuint)+[]((id bigInt)+(value bigInt)) if ERC1155>) + ) ``` - **transactions** diff --git a/server/websocket.go b/server/websocket.go index 1942fdab..27a73827 100644 --- a/server/websocket.go +++ b/server/websocket.go @@ -919,8 +919,8 @@ func (s *WebsocketServer) getNewTxSubscriptions(tx *bchain.MempoolTx) map[string } } } - for i := range tx.Erc20 { - addrDesc, err := s.chainParser.GetAddrDescFromAddress(tx.Erc20[i].From) + for i := range tx.TokenTransfers { + addrDesc, err := s.chainParser.GetAddrDescFromAddress(tx.TokenTransfers[i].From) if err == nil && len(addrDesc) > 0 { sad := string(addrDesc) as, ok := s.addressSubscriptions[sad] @@ -928,7 +928,7 @@ func (s *WebsocketServer) getNewTxSubscriptions(tx *bchain.MempoolTx) map[string subscribed[sad] = struct{}{} } } - addrDesc, err = s.chainParser.GetAddrDescFromAddress(tx.Erc20[i].To) + addrDesc, err = s.chainParser.GetAddrDescFromAddress(tx.TokenTransfers[i].To) if err == nil && len(addrDesc) > 0 { sad := string(addrDesc) as, ok := s.addressSubscriptions[sad] diff --git a/tests/dbtestdata/dbtestdata_ethereumtype.go b/tests/dbtestdata/dbtestdata_ethereumtype.go index 65683ec5..03e72f56 100644 --- a/tests/dbtestdata/dbtestdata_ethereumtype.go +++ b/tests/dbtestdata/dbtestdata_ethereumtype.go @@ -9,26 +9,73 @@ import ( // Addresses const ( + EthAddrZero = "0000000000000000000000000000000000000000" EthAddr3e = "3e3a3d69dc66ba10737f531ed088954a9ec89d97" EthAddr55 = "555ee11fbddc0e49a9bab358a8941ad95ffdb48f" EthAddr20 = "20cd153de35d469ba46127a0c8f18626b59a256a" EthAddr9f = "9f4981531fda132e83c44680787dfa7ee31e4f8d" EthAddr4b = "4bda106325c335df99eab7fe363cac8a0ba2a24d" EthAddr7b = "7b62eb7fe80350dc7ec945c0b73242cb9877fb1b" - EthAddrContract4a = "4af4114f73d1c1c903ac9e0361b379d1291808a2" // ERC-20 (VTY) - EthAddrContract0d = "0d0f936ee4c93e25944694d6c121de94d9760f11" // ERC-20 (MTT) + EthAddr83 = "837e3f699d85a4b0b99894567e9233dfb1dcb081" + EthAddrA3 = "a3950b823cb063dd9afc0d27f35008b805b3ed53" + EthAddr5d = "5dc6288b35e0807a3d6feb89b3a2ff4ab773168e" + EthAddr92 = "9248A6048a58db9f0212dC7CD85eE8741128be72" + EthAddrContract4a = "4af4114f73d1c1c903ac9e0361b379d1291808a2" // ERC20 (VTY) + EthAddrContract0d = "0d0f936ee4c93e25944694d6c121de94d9760f11" // ERC20 (MTT) EthAddrContract47 = "479cc461fecd078f766ecc58533d6f69580cf3ac" // non ERC20 + EthAddrContractCd = "cda9fc258358ecaa88845f19af595e908bb7efe9" // ERC721 + EthAddrContract6f = "6fd712e3a5b556654044608f9129040a4839e36c" // ERC1155 + // non contract + // EthAddr3e -> EthAddr55, value 1999622000000000000 EthTxidB1T1 = "cd647151552b5132b2aef7c9be00dc6f73afc5901dde157aab131335baaa853b" EthTx1Packed = "08e8dd870210a6a6f0db051a6908ece40212050430e234001888a40122081bc0159d530e60003220cd647151552b5132b2aef7c9be00dc6f73afc5901dde157aab131335baaa853b3a14555ee11fbddc0e49a9bab358a8941ad95ffdb48f42143e3a3d69dc66ba10737f531ed088954a9ec89d97480a22070a025208120101" EthTx1FailedPacked = "08e8dd870210a6a6f0db051a6908ece40212050430e234001888a40122081bc0159d530e60003220cd647151552b5132b2aef7c9be00dc6f73afc5901dde157aab131335baaa853b3a14555ee11fbddc0e49a9bab358a8941ad95ffdb48f42143e3a3d69dc66ba10737f531ed088954a9ec89d97480a22040a025208" EthTx1NoStatusPacked = "08e8dd870210a6a6f0db051a6908ece40212050430e234001888a40122081bc0159d530e60003220cd647151552b5132b2aef7c9be00dc6f73afc5901dde157aab131335baaa853b3a14555ee11fbddc0e49a9bab358a8941ad95ffdb48f42143e3a3d69dc66ba10737f531ed088954a9ec89d97480a22070a025208120155" - EthTxidB1T2 = "a9cd088aba2131000da6f38a33c20169baee476218deea6b78720700b895b101" - EthTx2Packed = "08e8dd870210a6a6f0db051aa20108d001120509502f900018d5e1042a44a9059cbb000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f00000000000000000000000000000000000000000000021e19e0c9bab24000003220a9cd088aba2131000da6f38a33c20169baee476218deea6b78720700b895b1013a144af4114f73d1c1c903ac9e0361b379d1291808a2421420cd153de35d469ba46127a0c8f18626b59a256a22a8010a02cb391201011a9e010a144af4114f73d1c1c903ac9e0361b379d1291808a2122000000000000000000000000000000000000000000000021e19e0c9bab24000001a20ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef1a2000000000000000000000000020cd153de35d469ba46127a0c8f18626b59a256a1a20000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f" - EthTxidB2T1 = "c2c3dd1ecb00e8a6d81f793d24387cf2947a313e94ab03b1fb22cd63320f6c91" - EthTx3Packed = "08e9dd870210d4b5f0db051a6708c20112050218711a001888a401220710bc3578bd37d83220c2c3dd1ecb00e8a6d81f793d24387cf2947a313e94ab03b1fb22cd63320f6c913a149f4981531fda132e83c44680787dfa7ee31e4f8d4214555ee11fbddc0e49a9bab358a8941ad95ffdb48f480722070a025208120101" - EthTxidB2T2 = "c92919ad24ffd58f760b18df7949f06e1190cf54a50a0e3745a385608ed3cbf2" - EthTx4Packed = "08e9dd870210d4b5f0db051aa50b08f6be0712043b9aca001890a10f2ac40a4f15078700000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000003c00000000000000000000000000000000000000000000000000000000000000420000000000000000000000000000000000000000000000000000000000000048000000000000000000000000000000000000000000000000000000000000004e00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d0000000000000000000000000d0f936ee4c93e25944694d6c121de94d9760f110000000000000000000000004af4114f73d1c1c903ac9e0361b379d1291808a200000000000000000000000000000000000000000000000000000000000000000000000000000000000000007b62eb7fe80350dc7ec945c0b73242cb9877fb1b0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d0000000000000000000000004af4114f73d1c1c903ac9e0361b379d1291808a20000000000000000000000000d0f936ee4c93e25944694d6c121de94d9760f110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000a5ef5a7656bfb0000000000000000000000000000000000000000000000000000004ba78398d5c5000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000166cfe0b9579b4ecf7a2801880f644009a324671a79754ea57c3a103c6e70d3dbef6ba69a08000000000000000000000000000000000000000000000000004f937d86afb90000000000000000000000000000000000000000000000000ab280fd8037d500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000166cfb784b7c1f3fbe8b75484603ab8adc58aaee3a46245a6579fac7077b5570018b4e0d4eb0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000308fd0e798ac00000000000000000000000000000000000000000000000006a8313d60b1f80000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001b000000000000000000000000000000000000000000000000000000000000001b00000000000000000000000000000000000000000000000000000000000000029de0ccec59e8948e3d905b40e5542335ebc1eb4674db517d2f6392ec7fdeb3d45f3449d313ee2589819c6c79eb1c1b047adae68565c1608e3a1d1d70823febb0000000000000000000000000000000000000000000000000000000000000000234d06fe17f1202e8b07177a30eb64d14adc08cdb3fa1b3e3e0bea0f9672c02175b77c01c51d3c7e460723b27ecbc7801fd6482559a8c9999593f9a4d149c73843220c92919ad24ffd58f760b18df7949f06e1190cf54a50a0e3745a385608ed3cbf23a14479cc461fecd078f766ecc58533d6f69580cf3ac42144bda106325c335df99eab7fe363cac8a0ba2a24d482422d40b0a03034d301201011a9e010a140d0f936ee4c93e25944694d6c121de94d9760f1112200000000000000000000000000000000000000000000000006a8313d60b1f606b1a20ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef1a20000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f1a200000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d1a9e010a144af4114f73d1c1c903ac9e0361b379d1291808a21220000000000000000000000000000000000000000000000000000308fd0e798ac01a20ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef1a200000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d1a20000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f1aa1030a14479cc461fecd078f766ecc58533d6f69580cf3ac1280020000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d0000000000000000000000000d0f936ee4c93e25944694d6c121de94d9760f110000000000000000000000004af4114f73d1c1c903ac9e0361b379d1291808a20000000000000000000000000000000000000000000000006a8313d60b1f606b000000000000000000000000000000000000000000000000000308fd0e798ac0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005e083a16f4b092c5729a49f9c3ed3cc171bb3d3d0c22e20b1de6063c32f399ac1a200d0b9391970d9a25552f37d436d2aae2925e2bfe1b2a923754bada030c498cb31a20000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f1a2000000000000000000000000000000000000000000000000000000000000000001a205af266c0a89a07c1917deaa024414577e6c3c31c8907d079e13eb448c082594f1a9e010a144af4114f73d1c1c903ac9e0361b379d1291808a2122000000000000000000000000000000000000000000000000000031855667df7a81a20ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef1a200000000000000000000000007b62eb7fe80350dc7ec945c0b73242cb9877fb1b1a200000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d1a9e010a140d0f936ee4c93e25944694d6c121de94d9760f1112200000000000000000000000000000000000000000000000006a8313d60b1f80001a20ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef1a200000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d1a200000000000000000000000007b62eb7fe80350dc7ec945c0b73242cb9877fb1b1aa1030a14479cc461fecd078f766ecc58533d6f69580cf3ac1280020000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d0000000000000000000000004af4114f73d1c1c903ac9e0361b379d1291808a20000000000000000000000000d0f936ee4c93e25944694d6c121de94d9760f1100000000000000000000000000000000000000000000000000031855667df7a80000000000000000000000000000000000000000000000006a8313d60b1f800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f2b0d62c44ed08f2a5adef40c875d20310a42a9d4f488bd26323256fe01c7f481a200d0b9391970d9a25552f37d436d2aae2925e2bfe1b2a923754bada030c498cb31a200000000000000000000000007b62eb7fe80350dc7ec945c0b73242cb9877fb1b1a2000000000000000000000000000000000000000000000000000000000000000001a20b0b69dad58df6032c3b266e19b1045b19c87acd2c06fb0c598090f44b8e263aa" + + // ERC20 + // EthAddr20 -> EthAddrContract4a, value 0 + // ERC20 EthAddrContract4a: EthAddr20 -> EthAddr55, value 10000000000000000000000 + EthTxidB1T2 = "a9cd088aba2131000da6f38a33c20169baee476218deea6b78720700b895b101" + EthTx2Packed = "08e8dd870210a6a6f0db051aa20108d001120509502f900018d5e1042a44a9059cbb000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f00000000000000000000000000000000000000000000021e19e0c9bab24000003220a9cd088aba2131000da6f38a33c20169baee476218deea6b78720700b895b1013a144af4114f73d1c1c903ac9e0361b379d1291808a2421420cd153de35d469ba46127a0c8f18626b59a256a22a8010a02cb391201011a9e010a144af4114f73d1c1c903ac9e0361b379d1291808a2122000000000000000000000000000000000000000000000021e19e0c9bab24000001a20ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef1a2000000000000000000000000020cd153de35d469ba46127a0c8f18626b59a256a1a20000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f" + + // non contract + // EthAddr55 -> EthAddr9f, value 4710537472325592 + EthTxidB2T1 = "c2c3dd1ecb00e8a6d81f793d24387cf2947a313e94ab03b1fb22cd63320f6c91" + EthTx3Packed = "08e9dd870210d4b5f0db051a6708c20112050218711a001888a401220710bc3578bd37d83220c2c3dd1ecb00e8a6d81f793d24387cf2947a313e94ab03b1fb22cd63320f6c913a149f4981531fda132e83c44680787dfa7ee31e4f8d4214555ee11fbddc0e49a9bab358a8941ad95ffdb48f480722070a025208120101" + + // ERC20 + // EthAddr4b -> EthAddrContract47, value 0 + // ERC20 EthAddrContract0d: EthAddr55 -> EthAddr4b, value 7675000000000000001 + // ERC20 EthAddrContract4a: EthAddr4b -> EthAddr55, value 854307892726464 + // ERC20 EthAddrContract4a: EthAddr7b -> EthAddr4b, value 871180000950184 + // ERC20 EthAddrContract0d: EthAddr4b -> EthAddr7b, value 7674999999999991915 + EthTxidB2T2 = "c92919ad24ffd58f760b18df7949f06e1190cf54a50a0e3745a385608ed3cbf2" + EthTx4Packed = "08e9dd870210d4b5f0db051aa50b08f6be0712043b9aca001890a10f2ac40a4f15078700000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000003c00000000000000000000000000000000000000000000000000000000000000420000000000000000000000000000000000000000000000000000000000000048000000000000000000000000000000000000000000000000000000000000004e00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d0000000000000000000000000d0f936ee4c93e25944694d6c121de94d9760f110000000000000000000000004af4114f73d1c1c903ac9e0361b379d1291808a200000000000000000000000000000000000000000000000000000000000000000000000000000000000000007b62eb7fe80350dc7ec945c0b73242cb9877fb1b0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d0000000000000000000000004af4114f73d1c1c903ac9e0361b379d1291808a20000000000000000000000000d0f936ee4c93e25944694d6c121de94d9760f110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000a5ef5a7656bfb0000000000000000000000000000000000000000000000000000004ba78398d5c5000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000166cfe0b9579b4ecf7a2801880f644009a324671a79754ea57c3a103c6e70d3dbef6ba69a08000000000000000000000000000000000000000000000000004f937d86afb90000000000000000000000000000000000000000000000000ab280fd8037d500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000166cfb784b7c1f3fbe8b75484603ab8adc58aaee3a46245a6579fac7077b5570018b4e0d4eb0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000308fd0e798ac00000000000000000000000000000000000000000000000006a8313d60b1f606b0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001b000000000000000000000000000000000000000000000000000000000000001b00000000000000000000000000000000000000000000000000000000000000029de0ccec59e8948e3d905b40e5542335ebc1eb4674db517d2f6392ec7fdeb3d45f3449d313ee2589819c6c79eb1c1b047adae68565c1608e3a1d1d70823febb0000000000000000000000000000000000000000000000000000000000000000234d06fe17f1202e8b07177a30eb64d14adc08cdb3fa1b3e3e0bea0f9672c02175b77c01c51d3c7e460723b27ecbc7801fd6482559a8c9999593f9a4d149c73843220c92919ad24ffd58f760b18df7949f06e1190cf54a50a0e3745a385608ed3cbf23a14479cc461fecd078f766ecc58533d6f69580cf3ac42144bda106325c335df99eab7fe363cac8a0ba2a24d482422d40b0a03034d301201011a9e010a140d0f936ee4c93e25944694d6c121de94d9760f1112200000000000000000000000000000000000000000000000006a8313d60b1f80011a20ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef1a20000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f1a200000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d1a9e010a144af4114f73d1c1c903ac9e0361b379d1291808a21220000000000000000000000000000000000000000000000000000308fd0e798ac01a20ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef1a200000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d1a20000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f1aa1030a14479cc461fecd078f766ecc58533d6f69580cf3ac1280020000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d0000000000000000000000000d0f936ee4c93e25944694d6c121de94d9760f110000000000000000000000004af4114f73d1c1c903ac9e0361b379d1291808a20000000000000000000000000000000000000000000000006a8313d60b1f8001000000000000000000000000000000000000000000000000000308fd0e798ac0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005e083a16f4b092c5729a49f9c3ed3cc171bb3d3d0c22e20b1de6063c32f399ac1a200d0b9391970d9a25552f37d436d2aae2925e2bfe1b2a923754bada030c498cb31a20000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f1a2000000000000000000000000000000000000000000000000000000000000000001a205af266c0a89a07c1917deaa024414577e6c3c31c8907d079e13eb448c082594f1a9e010a144af4114f73d1c1c903ac9e0361b379d1291808a2122000000000000000000000000000000000000000000000000000031855667df7a81a20ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef1a200000000000000000000000007b62eb7fe80350dc7ec945c0b73242cb9877fb1b1a200000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d1a9e010a140d0f936ee4c93e25944694d6c121de94d9760f1112200000000000000000000000000000000000000000000000006a8313d60b1f606b1a20ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef1a200000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d1a200000000000000000000000007b62eb7fe80350dc7ec945c0b73242cb9877fb1b1aa1030a14479cc461fecd078f766ecc58533d6f69580cf3ac1280020000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d0000000000000000000000004af4114f73d1c1c903ac9e0361b379d1291808a20000000000000000000000000d0f936ee4c93e25944694d6c121de94d9760f1100000000000000000000000000000000000000000000000000031855667df7a80000000000000000000000000000000000000000000000006a8313d60b1f606b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f2b0d62c44ed08f2a5adef40c875d20310a42a9d4f488bd26323256fe01c7f481a200d0b9391970d9a25552f37d436d2aae2925e2bfe1b2a923754bada030c498cb31a200000000000000000000000007b62eb7fe80350dc7ec945c0b73242cb9877fb1b1a2000000000000000000000000000000000000000000000000000000000000000001a20b0b69dad58df6032c3b266e19b1045b19c87acd2c06fb0c598090f44b8e263aa" + + // ERC721 + // EthAddr83 -> EthAddrContractCd, value 0 + // ERC721 EthAddrContractCd: EthAddr83 -> EthAddr7b, value 1 + EthTxidB2T3 = "ca7628be5c80cda77163729ec63d218ee868a399d827a4682a478c6f48a6e22a" + EthTx5Packed = "089ff7cc05109eaecd8e061ac2010802120459682f0718a9e7052a6423b872dd000000000000000000000000837e3f699d85a4b0b99894567e9233dfb1dcb0810000000000000000000000007b62eb7fe80350dc7ec945c0b73242cb9877fb1b00000000000000000000000000000000000000000000000000000000000000013220ca7628be5c80cda77163729ec63d218ee868a399d827a4682a478c6f48a6e22a3a14cda9fc258358ecaa88845f19af595e908bb7efe94214837e3f699d85a4b0b99894567e9233dfb1dcb081480122c9020a02e5061201011a9e010a14cda9fc258358ecaa88845f19af595e908bb7efe91a208c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9251a20000000000000000000000000837e3f699d85a4b0b99894567e9233dfb1dcb0811a2000000000000000000000000000000000000000000000000000000000000000001a2000000000000000000000000000000000000000000000000000000000000000011a9e010a14cda9fc258358ecaa88845f19af595e908bb7efe91a20ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef1a20000000000000000000000000837e3f699d85a4b0b99894567e9233dfb1dcb0811a200000000000000000000000007b62eb7fe80350dc7ec945c0b73242cb9877fb1b1a200000000000000000000000000000000000000000000000000000000000000001" + + // ERC1155 TransferSingle + // EthAddr3e -> EthAddr92, value 100000000000000000 + // ERC1155 EthAddrContract6f: EthAddrA3 -> EthAddr3e, values [(150,1)] + EthTxidB2T4 = "463a2a3f6303f88aec60fe7859081f80e8845b39495969a819c6bae9283aa12a" + EthTx6Packed = "08d2a6c80510ccfe8c8e061aad0108c1021204595faa4318f2dd0f2208016345785d8a00002a44d9bdda70000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000013220463a2a3f6303f88aec60fe7859081f80e8845b39495969a819c6bae9283aa12a3a149248a6048a58db9f0212dc7cd85ee8741128be7242143e3a3d69dc66ba10737f531ed088954a9ec89d97480822e7050a0302120d1201011abb020a146fd712e3a5b556654044608f9129040a4839e36c128002000000000000000000000000a3950b823cb063dd9afc0d27f35008b805b3ed530000000000000000000000003e3a3d69dc66ba10737f531ed088954a9ec89d97000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000096000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000011a205f9832c7244497a64c11c4a4f7597934bdf02b0361c54ad8e90091c2ce1f9e3c1ae0010a146fd712e3a5b556654044608f9129040a4839e36c1240000000000000000000000000000000000000000000000000000000000000009600000000000000000000000000000000000000000000000000000000000000011a20c3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f621a200000000000000000000000009248a6048a58db9f0212dc7cd85ee8741128be721a20000000000000000000000000a3950b823cb063dd9afc0d27f35008b805b3ed531a200000000000000000000000003e3a3d69dc66ba10737f531ed088954a9ec89d971abb010a149248a6048a58db9f0212dc7cd85ee8741128be721280010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000a3950b823cb063dd9afc0d27f35008b805b3ed530000000000000000000000003e3a3d69dc66ba10737f531ed088954a9ec89d9700000000000000000000000000000000000000000000000000000000000000011a200b7bef9468bee71526deef3cbbded0ec1a0aa3d5a3e81eaffb0e758552b33199" + + // ERC1155 TransferBatch + // EthAddr5d -> EthAddrContract6f, value 0 + // ERC1155 EthAddrContract6f: EthAddrZero -> EthAddr5d, values [(1776,1),(1898,10)] + EthTxidB2T5 = "6942c79c04ae981a2d194deb0ae5ae5e9d5d7a90fd9f52246b162fa645155e3a" + EthTx7Packed = "08a6c7d504108bb88f82061ae103085612044235839b18fbbf042a8403786279190000000000000000000000005dc6288b35e0807a3d6feb89b3a2ff4ab773168e000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000006f0000000000000000000000000000000000000000000000000000000000000076a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000001010000000000000000000000000000000000000000000000000000000000000032206942c79c04ae981a2d194deb0ae5ae5e9d5d7a90fd9f52246b162fa645155e3a3a146fd712e3a5b556654044608f9129040a4839e36c42145dc6288b35e0807a3d6feb89b3a2ff4ab773168e22ac030a03011ffb1201011aa1030a146fd712e3a5b556654044608f9129040a4839e36c128002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000006f0000000000000000000000000000000000000000000000000000000000000076a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000a1a204a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb1a200000000000000000000000005dc6288b35e0807a3d6feb89b3a2ff4ab773168e1a2000000000000000000000000000000000000000000000000000000000000000001a200000000000000000000000005dc6288b35e0807a3d6feb89b3a2ff4ab773168e" + + // ERC20 - special (not realistic) tx, all transfers from the same address to the same address + // EthAddr55 -> EthAddr55, value 0 + // ERC20 EthAddr55: EthAddr55 -> EthAddr55, value 10000000000000000000000 + EthTxidB2T6 = "e71e0d1dc1ac58b7a0c9fb14d0693af0764df07a72d882fffc020e464c91b63c" + EthTx8Packed = "08e8dd870210a6a6f0db051aa20108d001120509502f900018d5e1042a44a9059cbb000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f00000000000000000000000000000000000000000000021e19e0c9bab24000003220e71e0d1dc1ac58b7a0c9fb14d0693af0764df07a72d882fffc020e464c91b63c3a14555ee11fbddc0e49a9bab358a8941ad95ffdb48f4214555ee11fbddc0e49a9bab358a8941ad95ffdb48f22a8010a02cb391201011a9e010a14555ee11fbddc0e49a9bab358a8941ad95ffdb48f122000000000000000000000000000000000000000000000021e19e0c9bab24000001a20ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef1a20000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f1a20000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f" ) var EthTx2InternalData = &bchain.EthereumInternalData{ @@ -138,6 +185,14 @@ func GetTestEthereumTypeBlock2(parser bchain.BlockChainParser) *bchain.Block { }, { packed: EthTx4Packed, internal: EthTx4InternalData, + }, { + packed: EthTx5Packed, + }, { + packed: EthTx6Packed, + }, { + packed: EthTx7Packed, + }, { + packed: EthTx8Packed, }}, parser), } }