diff --git a/api/worker.go b/api/worker.go index 74b086ea..ccd54a43 100644 --- a/api/worker.go +++ b/api/worker.go @@ -701,9 +701,9 @@ func (w *Worker) getEthereumTypeAddressBalances(addrDesc bchain.AddressDescripto continue } // filter only transactions of this contract - filter.Vout = i + 1 + filter.Vout = i + db.ContractIndexOffset } - t, err := w.getEthereumToken(i+1, addrDesc, c.Contract, details, int(c.Txs)) + t, err := w.getEthereumToken(i+db.ContractIndexOffset, addrDesc, c.Contract, details, int(c.Txs)) if err != nil { return nil, nil, nil, 0, 0, 0, err } diff --git a/bchain/coins/blockchain.go b/bchain/coins/blockchain.go index 1fe7baae..c4008e8f 100644 --- a/bchain/coins/blockchain.go +++ b/bchain/coins/blockchain.go @@ -70,6 +70,7 @@ func init() { BlockChainFactories["Ethereum"] = eth.NewEthereumRPC BlockChainFactories["Ethereum Classic"] = eth.NewEthereumRPC BlockChainFactories["Ethereum Testnet Ropsten"] = eth.NewEthereumRPC + BlockChainFactories["Ethereum Testnet Ropsten Archive"] = eth.NewEthereumRPC BlockChainFactories["Ethereum Testnet Goerli"] = eth.NewEthereumRPC BlockChainFactories["Bcash"] = bch.NewBCashRPC BlockChainFactories["Bcash Testnet"] = bch.NewBCashRPC diff --git a/bchain/types_ethereumtype.go b/bchain/types_ethereum_type.go similarity index 100% rename from bchain/types_ethereumtype.go rename to bchain/types_ethereum_type.go diff --git a/db/rocksdb.go b/db/rocksdb.go index 59b90c9a..62c771b0 100644 --- a/db/rocksdb.go +++ b/db/rocksdb.go @@ -110,8 +110,11 @@ const ( // BitcoinType cfAddressBalance cfTxAddresses + + __break__ + // EthereumType - cfAddressContracts = cfAddressBalance + cfAddressContracts = iota - __break__ + cfAddressBalance - 1 cfInternalData cfContracts ) diff --git a/db/rocksdb_ethereumtype.go b/db/rocksdb_ethereumtype.go index e63b144c..a46cac27 100644 --- a/db/rocksdb_ethereumtype.go +++ b/db/rocksdb_ethereumtype.go @@ -3,6 +3,7 @@ package db import ( "bytes" "encoding/hex" + "math/big" vlq "github.com/bsm/go-vlq" "github.com/flier/gorocksdb" @@ -12,6 +13,9 @@ import ( "github.com/trezor/blockbook/bchain/coins/eth" ) +const InternalTxIndexOffset = 1 +const ContractIndexOffset = 2 + // AddrContract is Contract address with number of transactions done by given address type AddrContract struct { Contract bchain.AddressDescriptor @@ -108,6 +112,11 @@ func isZeroAddress(addrDesc bchain.AddressDescriptor) bool { return true } +const transferTo = int32(0) +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 { var err error strAddrDesc := string(addrDesc) @@ -127,7 +136,11 @@ func (d *RocksDB) addToAddressesAndContractsEthereumType(addrDesc bchain.Address } if contract == nil { if addTxCount { - ac.NonContractTxs++ + if index == internalTransferFrom || index == internalTransferTo { + ac.InternalTxs++ + } else { + ac.NonContractTxs++ + } } } else { // do not store contracts for 0x0000000000000000000000000000000000000000 address @@ -138,15 +151,21 @@ func (d *RocksDB) addToAddressesAndContractsEthereumType(addrDesc bchain.Address i = len(ac.Contracts) ac.Contracts = append(ac.Contracts, AddrContract{Contract: contract}) } - // index 0 is for ETH transfers, contract indexes start with 1 + // 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 + 1) + index = ^int32(i + ContractIndexOffset) } else { - index = int32(i + 1) + index = int32(i + ContractIndexOffset) } if addTxCount { ac.Contracts[i].Txs++ } + } else { + if index < 0 { + index = transferFrom + } else { + index = transferTo + } } } counted := addToAddressesMap(addresses, strAddrDesc, btxID, index) @@ -160,10 +179,23 @@ type ethBlockTxContract struct { addr, contract bchain.AddressDescriptor } +type ethInternalTransfer struct { + internalType bchain.EthereumInternalTransactionType + from, to bchain.AddressDescriptor + value big.Int +} + +type ethInternalData struct { + internalType bchain.EthereumInternalTransactionType + contract bchain.AddressDescriptor + transfers []ethInternalTransfer +} + type ethBlockTx struct { - btxID []byte - from, to bchain.AddressDescriptor - contracts []ethBlockTxContract + btxID []byte + from, to bchain.AddressDescriptor + contracts []ethBlockTxContract + internalData *ethInternalData } func (d *RocksDB) processAddressesEthereumType(block *bchain.Block, addresses addressesMap, addressContracts map[string]*AddrContracts) ([]ethBlockTx, error) { @@ -184,12 +216,12 @@ func (d *RocksDB) processAddressesEthereumType(block *bchain.Block, addresses ad if err != bchain.ErrAddressMissing { glog.Warningf("rocksdb: addrDesc: %v - height %d, tx %v, output", err, block.Height, tx.Txid) } - continue + } else { + if err = d.addToAddressesAndContractsEthereumType(to, btxID, transferTo, nil, addresses, addressContracts, true); err != nil { + return nil, err + } + blockTx.to = to } - if err = d.addToAddressesAndContractsEthereumType(to, btxID, 0, 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 { @@ -198,12 +230,68 @@ func (d *RocksDB) processAddressesEthereumType(block *bchain.Block, addresses ad if err != bchain.ErrAddressMissing { glog.Warningf("rocksdb: addrDesc: %v - height %d, tx %v, input", err, block.Height, tx.Txid) } - continue + } 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.addToAddressesAndContractsEthereumType(from, btxID, ^int32(0), nil, addresses, addressContracts, !bytes.Equal(from, to)); err != nil { - return nil, err + } + // process internal data + eid, _ := tx.CoinSpecificData.(bchain.EthereumSpecificData) + if eid.InternalData != nil { + blockTx.internalData = ðInternalData{ + internalType: eid.InternalData.Type, + } + // 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 + } } - blockTx.from = from } // store erc20 transfers erc20, err := d.chainParser.EthereumTypeGetErc20FromTx(&tx) @@ -249,14 +337,95 @@ func (d *RocksDB) processAddressesEthereumType(block *bchain.Block, addresses ad return blockTxs, nil } +var ethZeroAddress []byte = make([]byte, eth.EthereumTypeAddressDescriptorLen) + +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) + } + for i := range data.transfers { + t := &data.transfers[i] + buf = append(buf, byte(t.internalType)) + appendAddress(t.from) + appendAddress(t.to) + l = packBigint(&t.value, varBuf) + buf = append(buf, varBuf[:l]...) + } + return buf +} + +func (d *RocksDB) unpackEthInternalData(buf []byte) (*bchain.EthereumInternalData, error) { + id := bchain.EthereumInternalData{} + v, l := unpackVaruint(buf) + id.Type = bchain.EthereumInternalTransactionType(v & 1) + id.Transfers = make([]bchain.EthereumInternalTransfer, v>>1) + if id.Type == bchain.CREATE { + addresses, _, _ := d.chainParser.GetAddressesFromAddrDesc(buf[l : l+eth.EthereumTypeAddressDescriptorLen]) + l += eth.EthereumTypeAddressDescriptorLen + if len(addresses) > 0 { + id.Contract = addresses[0] + } + } + var ll int + for i := range id.Transfers { + t := &id.Transfers[i] + t.Type = bchain.EthereumInternalTransactionType(buf[l]) + l++ + addresses, _, _ := d.chainParser.GetAddressesFromAddrDesc(buf[l : l+eth.EthereumTypeAddressDescriptorLen]) + l += eth.EthereumTypeAddressDescriptorLen + if len(addresses) > 0 { + t.From = addresses[0] + } + addresses, _, _ = d.chainParser.GetAddressesFromAddrDesc(buf[l : l+eth.EthereumTypeAddressDescriptorLen]) + l += eth.EthereumTypeAddressDescriptorLen + if len(addresses) > 0 { + t.To = addresses[0] + } + t.Value, ll = unpackBigint(buf[l:]) + l += ll + } + return &id, nil +} + +func (d *RocksDB) GetEthereumInternalData(txid string) (*bchain.EthereumInternalData, error) { + btxID, err := d.chainParser.PackTxid(txid) + if err != nil { + return nil, err + } + + val, err := d.db.GetCF(d.ro, d.cfh[cfInternalData], btxID) + if err != nil { + return nil, err + } + defer val.Free() + buf := val.Data() + if len(buf) == 0 { + return nil, nil + } + return d.unpackEthInternalData(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) - zeroAddress := make([]byte, eth.EthereumTypeAddressDescriptorLen) appendAddress := func(a bchain.AddressDescriptor) { if len(a) != eth.EthereumTypeAddressDescriptorLen { - buf = append(buf, zeroAddress...) + buf = append(buf, ethZeroAddress...) } else { buf = append(buf, a...) } @@ -266,7 +435,29 @@ func (d *RocksDB) storeAndCleanupBlockTxsEthereumType(wb *gorocksdb.WriteBatch, buf = append(buf, blockTx.btxID...) appendAddress(blockTx.from) appendAddress(blockTx.to) - l := packVaruint(uint(len(blockTx.contracts)), varBuf) + // internal data - store the number of addresses, with odd number the CREATE tx type + var internalDataTransfers uint + if blockTx.internalData != nil { + wb.PutCF(d.cfh[cfInternalData], blockTx.btxID, packEthInternalData(blockTx.internalData)) + 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] @@ -323,8 +514,33 @@ func (d *RocksDB) getBlockTxsEthereumType(height uint32) ([]ethBlockTx, error) { 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) @@ -337,10 +553,11 @@ func (d *RocksDB) getBlockTxsEthereumType(height uint32) ([]ethBlockTx, error) { } } bt = append(bt, ethBlockTx{ - btxID: txid, - from: from, - to: to, - contracts: contracts, + btxID: txid, + from: from, + to: to, + internalData: internalData, + contracts: contracts, }) } return bt, nil @@ -349,7 +566,7 @@ func (d *RocksDB) getBlockTxsEthereumType(height uint32) ([]ethBlockTx, error) { 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, addrDesc, contract bchain.AddressDescriptor) error { + disconnectAddress := func(btxID []byte, internal bool, addrDesc, contract bchain.AddressDescriptor) error { var err error // do not process empty address if len(addrDesc) == 0 { @@ -382,10 +599,18 @@ func (d *RocksDB) disconnectBlockTxsEthereumType(wb *gorocksdb.WriteBatch, heigh c.TotalTxs-- } if contract == nil { - if c.NonContractTxs > 0 { - c.NonContractTxs-- + if internal { + if c.InternalTxs > 0 { + c.InternalTxs-- + } else { + glog.Warning("AddressContracts ", addrDesc, ", InternalTxs would be negative, tx ", hex.EncodeToString(btxID)) + } } else { - glog.Warning("AddressContracts ", addrDesc, ", EthTxs would be negative, tx ", hex.EncodeToString(btxID)) + 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) @@ -409,21 +634,41 @@ func (d *RocksDB) disconnectBlockTxsEthereumType(wb *gorocksdb.WriteBatch, heigh } for i := range blockTxs { blockTx := &blockTxs[i] - if err := disconnectAddress(blockTx.btxID, blockTx.from, nil); err != nil { + if err := disconnectAddress(blockTx.btxID, false, blockTx.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(blockTx.from, blockTx.to) { - if err := disconnectAddress(blockTx.btxID, blockTx.to, nil); err != nil { + if err := disconnectAddress(blockTx.btxID, false, blockTx.to, nil); 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 + } + } + } + } for _, c := range blockTx.contracts { - if err := disconnectAddress(blockTx.btxID, c.addr, c.contract); err != nil { + if err := disconnectAddress(blockTx.btxID, false, c.addr, c.contract); err != nil { return err } } wb.DeleteCF(d.cfh[cfTransactions], blockTx.btxID) + wb.DeleteCF(d.cfh[cfInternalData], blockTx.btxID) } for a := range addresses { key := packAddressKey([]byte(a), height) diff --git a/db/rocksdb_ethereumtype_test.go b/db/rocksdb_ethereumtype_test.go index 55af30ff..20df296d 100644 --- a/db/rocksdb_ethereumtype_test.go +++ b/db/rocksdb_ethereumtype_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/juju/errors" + "github.com/trezor/blockbook/bchain" "github.com/trezor/blockbook/bchain/coins/eth" "github.com/trezor/blockbook/tests/dbtestdata" ) @@ -33,10 +34,11 @@ func verifyAfterEthereumTypeBlock1(t *testing.T, d *RocksDB, afterDisconnect boo } } if err := checkColumn(d, cfAddresses, []keyPair{ - {addressKeyHex(dbtestdata.EthAddr3e, 4321000, d), txIndexesHex(dbtestdata.EthTxidB1T1, []int32{^0}), nil}, - {addressKeyHex(dbtestdata.EthAddr55, 4321000, d), txIndexesHex(dbtestdata.EthTxidB1T2, []int32{1}) + txIndexesHex(dbtestdata.EthTxidB1T1, []int32{0}), nil}, - {addressKeyHex(dbtestdata.EthAddr20, 4321000, d), txIndexesHex(dbtestdata.EthTxidB1T2, []int32{^0, ^1}), nil}, - {addressKeyHex(dbtestdata.EthAddrContract4a, 4321000, d), txIndexesHex(dbtestdata.EthTxidB1T2, []int32{0}), nil}, + {addressKeyHex(dbtestdata.EthAddr3e, 4321000, d), txIndexesHex(dbtestdata.EthTxidB1T2, []int32{^1, 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}, }); err != nil { { t.Fatal(err) @@ -44,10 +46,26 @@ func verifyAfterEthereumTypeBlock1(t *testing.T, d *RocksDB, afterDisconnect boo } if err := checkColumn(d, cfAddressContracts, []keyPair{ - {dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr3e, d.chainParser), "010100", nil}, + {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.EthAddrContract4a, d.chainParser), "010100", nil}, + {dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr9f, d.chainParser), "010002", nil}, + {dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser), "010101", nil}, + }); err != nil { + { + t.Fatal(err) + } + } + + if err := checkColumn(d, cfInternalData, []keyPair{ + { + dbtestdata.EthTxidB1T2, + "06" + + "01" + dbtestdata.EthAddr9f + dbtestdata.EthAddrContract4a + "030f4240" + + "00" + dbtestdata.EthAddr3e + dbtestdata.EthAddr9f + "030f4241" + + "00" + dbtestdata.EthAddr3e + dbtestdata.EthAddr3e + "030f4242", + nil, + }, }); err != nil { { t.Fatal(err) @@ -62,9 +80,13 @@ 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" + + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr3e, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser) + "00" + "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), @@ -97,15 +119,18 @@ func verifyAfterEthereumTypeBlock2(t *testing.T, d *RocksDB) { } } if err := checkColumn(d, cfAddresses, []keyPair{ - {addressKeyHex(dbtestdata.EthAddr3e, 4321000, d), txIndexesHex(dbtestdata.EthTxidB1T1, []int32{^0}), nil}, - {addressKeyHex(dbtestdata.EthAddr55, 4321000, d), txIndexesHex(dbtestdata.EthTxidB1T2, []int32{1}) + txIndexesHex(dbtestdata.EthTxidB1T1, []int32{0}), nil}, - {addressKeyHex(dbtestdata.EthAddr20, 4321000, d), txIndexesHex(dbtestdata.EthTxidB1T2, []int32{^0, ^1}), nil}, - {addressKeyHex(dbtestdata.EthAddrContract4a, 4321000, d), txIndexesHex(dbtestdata.EthTxidB1T2, []int32{0}), nil}, - {addressKeyHex(dbtestdata.EthAddr55, 4321001, d), txIndexesHex(dbtestdata.EthTxidB2T2, []int32{^2, 1}) + txIndexesHex(dbtestdata.EthTxidB2T1, []int32{^0}), nil}, - {addressKeyHex(dbtestdata.EthAddr9f, 4321001, d), txIndexesHex(dbtestdata.EthTxidB2T1, []int32{0}), nil}, - {addressKeyHex(dbtestdata.EthAddr4b, 4321001, d), txIndexesHex(dbtestdata.EthTxidB2T2, []int32{^0, 1, ^2, 2, ^1}), nil}, - {addressKeyHex(dbtestdata.EthAddr7b, 4321001, d), txIndexesHex(dbtestdata.EthTxidB2T2, []int32{^1, 2}), nil}, + {addressKeyHex(dbtestdata.EthAddr3e, 4321000, d), txIndexesHex(dbtestdata.EthTxidB1T2, []int32{^1, 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.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.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}, }); err != nil { { t.Fatal(err) @@ -113,13 +138,14 @@ func verifyAfterEthereumTypeBlock2(t *testing.T, d *RocksDB) { } if err := checkColumn(d, cfAddressContracts, []keyPair{ - {dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr3e, d.chainParser), "010100", nil}, + {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), "010100", nil}, - {dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr9f, d.chainParser), "010100", nil}, - {dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr4b, d.chainParser), "010100" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract0d, d.chainParser) + "02" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + "02", nil}, + {dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser), "020102", 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}, }); err != nil { { @@ -127,13 +153,39 @@ func verifyAfterEthereumTypeBlock2(t *testing.T, d *RocksDB) { } } + if err := checkColumn(d, cfInternalData, []keyPair{ + { + dbtestdata.EthTxidB1T2, + "06" + + "01" + dbtestdata.EthAddr9f + dbtestdata.EthAddrContract4a + "030f4240" + + "00" + dbtestdata.EthAddr3e + dbtestdata.EthAddr9f + "030f4241" + + "00" + dbtestdata.EthAddr3e + dbtestdata.EthAddr3e + "030f4242", + nil, + }, + { + dbtestdata.EthTxidB2T2, + "05" + dbtestdata.EthAddrContract0d + + "00" + dbtestdata.EthAddr4b + dbtestdata.EthAddr9f + "030f424a" + + "02" + dbtestdata.EthAddrContract4a + dbtestdata.EthAddr9f + "030f424b", + nil, + }, + }); err != nil { + { + t.Fatal(err) + } + } + if err := checkColumn(d, cfBlockTxs, []keyPair{ { "0041eee9", dbtestdata.EthTxidB2T1 + - dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr9f, d.chainParser) + "00" + + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser) + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr9f, d.chainParser) + "00" + "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) + @@ -152,6 +204,19 @@ func verifyAfterEthereumTypeBlock2(t *testing.T, d *RocksDB) { } } +func formatInternalData(in *bchain.EthereumInternalData) *bchain.EthereumInternalData { + out := *in + if out.Type == bchain.CREATE { + out.Contract = eth.EIP55AddressFromAddress(out.Contract) + } + for i := range out.Transfers { + t := &out.Transfers[i] + t.From = eth.EIP55AddressFromAddress(t.From) + t.To = eth.EIP55AddressFromAddress(t.To) + } + return &out +} + // TestRocksDB_Index_EthereumType is an integration test probing the whole indexing functionality for EthereumType chains // It does the following: // 1) Connect two blocks (inputs from 2nd block are spending some outputs from the 1st block) @@ -195,14 +260,27 @@ 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.EthTxidB2T2, ^2}, - {"0x" + dbtestdata.EthTxidB2T2, 1}, + {"0x" + dbtestdata.EthTxidB2T2, ^3}, + {"0x" + dbtestdata.EthTxidB2T2, 2}, {"0x" + dbtestdata.EthTxidB2T1, ^0}, - {"0x" + dbtestdata.EthTxidB1T2, 1}, + {"0x" + dbtestdata.EthTxidB1T2, 2}, {"0x" + dbtestdata.EthTxidB1T1, 0}, }, nil) verifyGetTransactions(t, d, "mtGXQvBowMkBpnhLckhxhbwYK44Gs9eBad", 500000, 1000000, []txidIndex{}, errors.New("Address missing")) + id, err := d.GetEthereumInternalData(dbtestdata.EthTxidB1T1) + if err != nil || id != nil { + t.Errorf("GetEthereumInternalData(%s) = %+v, want %+v, err %v", dbtestdata.EthTxidB1T1, id, nil, err) + } + id, err = d.GetEthereumInternalData(dbtestdata.EthTxidB1T2) + if err != nil || !reflect.DeepEqual(id, formatInternalData(dbtestdata.EthTx2InternalData)) { + t.Errorf("GetEthereumInternalData(%s) = %+v, want %+v, err %v", dbtestdata.EthTxidB1T2, id, formatInternalData(dbtestdata.EthTx2InternalData), err) + } + id, err = d.GetEthereumInternalData(dbtestdata.EthTxidB2T2) + if err != nil || !reflect.DeepEqual(id, formatInternalData(dbtestdata.EthTx4InternalData)) { + t.Errorf("GetEthereumInternalData(%s) = %+v, want %+v, err %v", dbtestdata.EthTxidB2T2, id, formatInternalData(dbtestdata.EthTx4InternalData), err) + } + // GetBestBlock height, hash, err := d.GetBestBlock() if err != nil { diff --git a/tests/dbtestdata/dbtestdata_ethereumtype.go b/tests/dbtestdata/dbtestdata_ethereumtype.go index cebc9cb2..56e04ec5 100644 --- a/tests/dbtestdata/dbtestdata_ethereumtype.go +++ b/tests/dbtestdata/dbtestdata_ethereumtype.go @@ -2,6 +2,7 @@ package dbtestdata import ( "encoding/hex" + "math/big" "github.com/trezor/blockbook/bchain" ) @@ -30,6 +31,48 @@ const ( EthTx4Packed = "08e9dd870210d4b5f0db051aa50b08f6be0712043b9aca001890a10f2ac40a4f15078700000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000003c00000000000000000000000000000000000000000000000000000000000000420000000000000000000000000000000000000000000000000000000000000048000000000000000000000000000000000000000000000000000000000000004e00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d0000000000000000000000000d0f936ee4c93e25944694d6c121de94d9760f110000000000000000000000004af4114f73d1c1c903ac9e0361b379d1291808a200000000000000000000000000000000000000000000000000000000000000000000000000000000000000007b62eb7fe80350dc7ec945c0b73242cb9877fb1b0000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d0000000000000000000000004af4114f73d1c1c903ac9e0361b379d1291808a20000000000000000000000000d0f936ee4c93e25944694d6c121de94d9760f110000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000a5ef5a7656bfb0000000000000000000000000000000000000000000000000000004ba78398d5c5000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000166cfe0b9579b4ecf7a2801880f644009a324671a79754ea57c3a103c6e70d3dbef6ba69a08000000000000000000000000000000000000000000000000004f937d86afb90000000000000000000000000000000000000000000000000ab280fd8037d500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000166cfb784b7c1f3fbe8b75484603ab8adc58aaee3a46245a6579fac7077b5570018b4e0d4eb0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000308fd0e798ac00000000000000000000000000000000000000000000000006a8313d60b1f80000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001b000000000000000000000000000000000000000000000000000000000000001b00000000000000000000000000000000000000000000000000000000000000029de0ccec59e8948e3d905b40e5542335ebc1eb4674db517d2f6392ec7fdeb3d45f3449d313ee2589819c6c79eb1c1b047adae68565c1608e3a1d1d70823febb0000000000000000000000000000000000000000000000000000000000000000234d06fe17f1202e8b07177a30eb64d14adc08cdb3fa1b3e3e0bea0f9672c02175b77c01c51d3c7e460723b27ecbc7801fd6482559a8c9999593f9a4d149c73843220c92919ad24ffd58f760b18df7949f06e1190cf54a50a0e3745a385608ed3cbf23a14479cc461fecd078f766ecc58533d6f69580cf3ac42144bda106325c335df99eab7fe363cac8a0ba2a24d482422d40b0a03034d301201011a9e010a140d0f936ee4c93e25944694d6c121de94d9760f1112200000000000000000000000000000000000000000000000006a8313d60b1f606b1a20ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef1a20000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f1a200000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d1a9e010a144af4114f73d1c1c903ac9e0361b379d1291808a21220000000000000000000000000000000000000000000000000000308fd0e798ac01a20ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef1a200000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d1a20000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f1aa1030a14479cc461fecd078f766ecc58533d6f69580cf3ac1280020000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d0000000000000000000000000d0f936ee4c93e25944694d6c121de94d9760f110000000000000000000000004af4114f73d1c1c903ac9e0361b379d1291808a20000000000000000000000000000000000000000000000006a8313d60b1f606b000000000000000000000000000000000000000000000000000308fd0e798ac0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005e083a16f4b092c5729a49f9c3ed3cc171bb3d3d0c22e20b1de6063c32f399ac1a200d0b9391970d9a25552f37d436d2aae2925e2bfe1b2a923754bada030c498cb31a20000000000000000000000000555ee11fbddc0e49a9bab358a8941ad95ffdb48f1a2000000000000000000000000000000000000000000000000000000000000000001a205af266c0a89a07c1917deaa024414577e6c3c31c8907d079e13eb448c082594f1a9e010a144af4114f73d1c1c903ac9e0361b379d1291808a2122000000000000000000000000000000000000000000000000000031855667df7a81a20ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef1a200000000000000000000000007b62eb7fe80350dc7ec945c0b73242cb9877fb1b1a200000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d1a9e010a140d0f936ee4c93e25944694d6c121de94d9760f1112200000000000000000000000000000000000000000000000006a8313d60b1f80001a20ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef1a200000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d1a200000000000000000000000007b62eb7fe80350dc7ec945c0b73242cb9877fb1b1aa1030a14479cc461fecd078f766ecc58533d6f69580cf3ac1280020000000000000000000000004bda106325c335df99eab7fe363cac8a0ba2a24d0000000000000000000000004af4114f73d1c1c903ac9e0361b379d1291808a20000000000000000000000000d0f936ee4c93e25944694d6c121de94d9760f1100000000000000000000000000000000000000000000000000031855667df7a80000000000000000000000000000000000000000000000006a8313d60b1f800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f2b0d62c44ed08f2a5adef40c875d20310a42a9d4f488bd26323256fe01c7f481a200d0b9391970d9a25552f37d436d2aae2925e2bfe1b2a923754bada030c498cb31a200000000000000000000000007b62eb7fe80350dc7ec945c0b73242cb9877fb1b1a2000000000000000000000000000000000000000000000000000000000000000001a20b0b69dad58df6032c3b266e19b1045b19c87acd2c06fb0c598090f44b8e263aa" ) +var EthTx2InternalData = &bchain.EthereumInternalData{ + Transfers: []bchain.EthereumInternalTransfer{ + { + Type: bchain.CREATE, + From: EthAddr9f, + To: EthAddrContract4a, + Value: *big.NewInt(1000000), + }, + { + Type: bchain.CALL, + From: EthAddr3e, + To: EthAddr9f, + Value: *big.NewInt(1000001), + }, + { + Type: bchain.CALL, + From: EthAddr3e, + To: EthAddr3e, + Value: *big.NewInt(1000002), + }, + }, +} + +var EthTx4InternalData = &bchain.EthereumInternalData{ + Type: bchain.CREATE, + Contract: EthAddrContract0d, + Transfers: []bchain.EthereumInternalTransfer{ + { + Type: bchain.CALL, + From: EthAddr4b, + To: EthAddr9f, + Value: *big.NewInt(1000010), + }, + { + Type: bchain.SELFDESTRUCT, + From: EthAddrContract4a, + To: EthAddr9f, + Value: *big.NewInt(1000011), + }, + }, +} + type packedAndInternal struct { packed string internal *bchain.EthereumInternalData @@ -48,6 +91,7 @@ func unpackTxs(packed []packedAndInternal, parser bchain.BlockChainParser) []bch } c, _ := tx.CoinSpecificData.(bchain.EthereumSpecificData) c.InternalData = p.internal + tx.CoinSpecificData = c r[i] = *tx } return r @@ -66,7 +110,8 @@ func GetTestEthereumTypeBlock1(parser bchain.BlockChainParser) *bchain.Block { Txs: unpackTxs([]packedAndInternal{{ packed: EthTx1Packed, }, { - packed: EthTx2Packed, + packed: EthTx2Packed, + internal: EthTx2InternalData, }}, parser), } } @@ -84,7 +129,8 @@ func GetTestEthereumTypeBlock2(parser bchain.BlockChainParser) *bchain.Block { Txs: unpackTxs([]packedAndInternal{{ packed: EthTx3Packed, }, { - packed: EthTx4Packed, + packed: EthTx4Packed, + internal: EthTx4InternalData, }}, parser), } }