From aa9f78a0281e4cd4adfcd8307bbf4c2cf0707f04 Mon Sep 17 00:00:00 2001 From: Ian Norden Date: Wed, 1 Apr 2020 22:34:06 -0500 Subject: [PATCH] new fields in eth.receipt_cids to index contract addresses seen in logs; handle null dst/contract addrs in trxs/rcts --- ...0032_add_log_contracts_to_receipt_cids.sql | 7 + .../00026_create_eth_headers_table.sql | 0 .../maaaybe/00027_create_eth_uncles_table.sql | 0 .../00028_create_eth_transactions_table.sql | 0 .../00029_create_eth_receipts_table.sql | 0 .../maaaybe/00030_create_eth_logs_table.sql | 0 .../00031_create_eth_accounts_table.sql | 0 .../00032_create_eth_storage_leaf_table.sql | 0 .../00033_create_btc_headers_table.sql | 0 ...00034_create_btc_transactions_table.sql.go | 1 - pkg/super_node/eth/api.go | 210 +----------------- pkg/super_node/eth/api_test.go | 2 + pkg/super_node/eth/backend.go | 206 +++++++++++++++++ pkg/super_node/eth/converter.go | 35 ++- pkg/super_node/eth/filterer.go | 20 +- pkg/super_node/eth/filterer_test.go | 24 +- pkg/super_node/eth/indexer.go | 10 +- pkg/super_node/eth/indexer_test.go | 6 +- pkg/super_node/eth/mocks/test_data.go | 92 ++++++-- pkg/super_node/eth/models.go | 18 +- pkg/super_node/eth/publisher.go | 13 +- pkg/super_node/eth/publisher_test.go | 8 +- pkg/super_node/eth/retriever.go | 22 +- pkg/super_node/eth/retriever_test.go | 52 +++-- pkg/super_node/eth/subscription_config.go | 14 +- pkg/super_node/shared/functions.go | 15 +- pkg/watcher/eth/converter.go | 32 +-- 27 files changed, 441 insertions(+), 346 deletions(-) create mode 100644 db/migrations/00032_add_log_contracts_to_receipt_cids.sql delete mode 100644 db/migrations/maaaybe/00026_create_eth_headers_table.sql delete mode 100644 db/migrations/maaaybe/00027_create_eth_uncles_table.sql delete mode 100644 db/migrations/maaaybe/00028_create_eth_transactions_table.sql delete mode 100644 db/migrations/maaaybe/00029_create_eth_receipts_table.sql delete mode 100644 db/migrations/maaaybe/00030_create_eth_logs_table.sql delete mode 100644 db/migrations/maaaybe/00031_create_eth_accounts_table.sql delete mode 100644 db/migrations/maaaybe/00032_create_eth_storage_leaf_table.sql delete mode 100644 db/migrations/maaaybe/00033_create_btc_headers_table.sql delete mode 100644 db/migrations/maaaybe/00034_create_btc_transactions_table.sql.go diff --git a/db/migrations/00032_add_log_contracts_to_receipt_cids.sql b/db/migrations/00032_add_log_contracts_to_receipt_cids.sql new file mode 100644 index 00000000..7638755e --- /dev/null +++ b/db/migrations/00032_add_log_contracts_to_receipt_cids.sql @@ -0,0 +1,7 @@ +-- +goose Up +ALTER TABLE eth.receipt_cids +ADD COLUMN log_contracts VARCHAR(66)[]; + +-- +goose Down +ALTER TABLE eth.receipt_cids +DROP COLUMN log_contracts; \ No newline at end of file diff --git a/db/migrations/maaaybe/00026_create_eth_headers_table.sql b/db/migrations/maaaybe/00026_create_eth_headers_table.sql deleted file mode 100644 index e69de29b..00000000 diff --git a/db/migrations/maaaybe/00027_create_eth_uncles_table.sql b/db/migrations/maaaybe/00027_create_eth_uncles_table.sql deleted file mode 100644 index e69de29b..00000000 diff --git a/db/migrations/maaaybe/00028_create_eth_transactions_table.sql b/db/migrations/maaaybe/00028_create_eth_transactions_table.sql deleted file mode 100644 index e69de29b..00000000 diff --git a/db/migrations/maaaybe/00029_create_eth_receipts_table.sql b/db/migrations/maaaybe/00029_create_eth_receipts_table.sql deleted file mode 100644 index e69de29b..00000000 diff --git a/db/migrations/maaaybe/00030_create_eth_logs_table.sql b/db/migrations/maaaybe/00030_create_eth_logs_table.sql deleted file mode 100644 index e69de29b..00000000 diff --git a/db/migrations/maaaybe/00031_create_eth_accounts_table.sql b/db/migrations/maaaybe/00031_create_eth_accounts_table.sql deleted file mode 100644 index e69de29b..00000000 diff --git a/db/migrations/maaaybe/00032_create_eth_storage_leaf_table.sql b/db/migrations/maaaybe/00032_create_eth_storage_leaf_table.sql deleted file mode 100644 index e69de29b..00000000 diff --git a/db/migrations/maaaybe/00033_create_btc_headers_table.sql b/db/migrations/maaaybe/00033_create_btc_headers_table.sql deleted file mode 100644 index e69de29b..00000000 diff --git a/db/migrations/maaaybe/00034_create_btc_transactions_table.sql.go b/db/migrations/maaaybe/00034_create_btc_transactions_table.sql.go deleted file mode 100644 index f7436b85..00000000 --- a/db/migrations/maaaybe/00034_create_btc_transactions_table.sql.go +++ /dev/null @@ -1 +0,0 @@ -package maaaybe diff --git a/pkg/super_node/eth/api.go b/pkg/super_node/eth/api.go index cd80453c..f882bbb3 100644 --- a/pkg/super_node/eth/api.go +++ b/pkg/super_node/eth/api.go @@ -20,13 +20,10 @@ import ( "context" "math/big" - "github.com/vulcanize/vulcanizedb/pkg/ipfs" - "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" ) @@ -73,8 +70,8 @@ func (pea *PublicEthAPI) GetLogs(ctx context.Context, crit ethereum.FilterQuery) } } filter := ReceiptFilter{ - Contracts: addrStrs, - Topics: topicStrSets, + LogAddresses: addrStrs, + Topics: topicStrSets, } tx, err := pea.b.DB.Beginx() if err != nil { @@ -181,206 +178,3 @@ func (pea *PublicEthAPI) GetTransactionByHash(ctx context.Context, hash common.H // Transaction unknown, return as such return nil, nil } - -// extractLogsOfInterest returns logs from the receipt IPLD -func extractLogsOfInterest(rctIPLDs []ipfs.BlockModel, wantedTopics [][]string) ([]*types.Log, error) { - var logs []*types.Log - for _, rctIPLD := range rctIPLDs { - rctRLP := rctIPLD - var rct types.Receipt - if err := rlp.DecodeBytes(rctRLP.Data, &rct); err != nil { - return nil, err - } - for _, log := range rct.Logs { - if wanted := wantedLog(wantedTopics, log.Topics); wanted == true { - logs = append(logs, log) - } - } - } - return logs, nil -} - -// returns true if the log matches on the filter -func wantedLog(wantedTopics [][]string, actualTopics []common.Hash) bool { - // actualTopics will always have length <= 4 - // wantedTopics will always have length 4 - matches := 0 - for i, actualTopic := range actualTopics { - // If we have topics in this filter slot, count as a match if the actualTopic matches one of the ones in this filter slot - if len(wantedTopics[i]) > 0 { - matches += sliceContainsHash(wantedTopics[i], actualTopic) - } else { - // Filter slot is empty, not matching any topics at this slot => counts as a match - matches++ - } - } - if matches == len(actualTopics) { - return true - } - return false -} - -// returns 1 if the slice contains the hash, 0 if it does not -func sliceContainsHash(slice []string, hash common.Hash) int { - for _, str := range slice { - if str == hash.String() { - return 1 - } - } - return 0 -} - -// rpcMarshalHeader uses the generalized output filler, then adds the total difficulty field, which requires -// a `PublicEthAPI`. -func (pea *PublicEthAPI) rpcMarshalHeader(header *types.Header) (map[string]interface{}, error) { - fields := RPCMarshalHeader(header) - td, err := pea.b.GetTd(header.Hash()) - if err != nil { - return nil, err - } - fields["totalDifficulty"] = (*hexutil.Big)(td) - return fields, nil -} - -// RPCMarshalHeader converts the given header to the RPC output. -// This function is eth/internal so we have to make our own version here... -func RPCMarshalHeader(head *types.Header) map[string]interface{} { - return map[string]interface{}{ - "number": (*hexutil.Big)(head.Number), - "hash": head.Hash(), - "parentHash": head.ParentHash, - "nonce": head.Nonce, - "mixHash": head.MixDigest, - "sha3Uncles": head.UncleHash, - "logsBloom": head.Bloom, - "stateRoot": head.Root, - "miner": head.Coinbase, - "difficulty": (*hexutil.Big)(head.Difficulty), - "extraData": hexutil.Bytes(head.Extra), - "size": hexutil.Uint64(head.Size()), - "gasLimit": hexutil.Uint64(head.GasLimit), - "gasUsed": hexutil.Uint64(head.GasUsed), - "timestamp": hexutil.Uint64(head.Time), - "transactionsRoot": head.TxHash, - "receiptsRoot": head.ReceiptHash, - } -} - -// rpcMarshalBlock uses the generalized output filler, then adds the total difficulty field, which requires -// a `PublicBlockchainAPI`. -func (pea *PublicEthAPI) rpcMarshalBlock(b *types.Block, inclTx bool, fullTx bool) (map[string]interface{}, error) { - fields, err := RPCMarshalBlock(b, inclTx, fullTx) - if err != nil { - return nil, err - } - td, err := pea.b.GetTd(b.Hash()) - if err != nil { - return nil, err - } - fields["totalDifficulty"] = (*hexutil.Big)(td) - return fields, err -} - -// RPCMarshalBlock converts the given block to the RPC output which depends on fullTx. If inclTx is true transactions are -// returned. When fullTx is true the returned block contains full transaction details, otherwise it will only contain -// transaction hashes. -func RPCMarshalBlock(block *types.Block, inclTx bool, fullTx bool) (map[string]interface{}, error) { - fields := RPCMarshalHeader(block.Header()) - fields["size"] = hexutil.Uint64(block.Size()) - - if inclTx { - formatTx := func(tx *types.Transaction) (interface{}, error) { - return tx.Hash(), nil - } - if fullTx { - formatTx = func(tx *types.Transaction) (interface{}, error) { - return NewRPCTransactionFromBlockHash(block, tx.Hash()), nil - } - } - txs := block.Transactions() - transactions := make([]interface{}, len(txs)) - var err error - for i, tx := range txs { - if transactions[i], err = formatTx(tx); err != nil { - return nil, err - } - } - fields["transactions"] = transactions - } - uncles := block.Uncles() - uncleHashes := make([]common.Hash, len(uncles)) - for i, uncle := range uncles { - uncleHashes[i] = uncle.Hash() - } - fields["uncles"] = uncleHashes - - return fields, nil -} - -// NewRPCTransactionFromBlockHash returns a transaction that will serialize to the RPC representation. -func NewRPCTransactionFromBlockHash(b *types.Block, hash common.Hash) *RPCTransaction { - for idx, tx := range b.Transactions() { - if tx.Hash() == hash { - return newRPCTransactionFromBlockIndex(b, uint64(idx)) - } - } - return nil -} - -// newRPCTransactionFromBlockIndex returns a transaction that will serialize to the RPC representation. -func newRPCTransactionFromBlockIndex(b *types.Block, index uint64) *RPCTransaction { - txs := b.Transactions() - if index >= uint64(len(txs)) { - return nil - } - return NewRPCTransaction(txs[index], b.Hash(), b.NumberU64(), index) -} - -// RPCTransaction represents a transaction that will serialize to the RPC representation of a transaction -type RPCTransaction struct { - BlockHash *common.Hash `json:"blockHash"` - BlockNumber *hexutil.Big `json:"blockNumber"` - From common.Address `json:"from"` - Gas hexutil.Uint64 `json:"gas"` - GasPrice *hexutil.Big `json:"gasPrice"` - Hash common.Hash `json:"hash"` - Input hexutil.Bytes `json:"input"` - Nonce hexutil.Uint64 `json:"nonce"` - To *common.Address `json:"to"` - TransactionIndex *hexutil.Uint64 `json:"transactionIndex"` - Value *hexutil.Big `json:"value"` - V *hexutil.Big `json:"v"` - R *hexutil.Big `json:"r"` - S *hexutil.Big `json:"s"` -} - -// NewRPCTransaction returns a transaction that will serialize to the RPC -// representation, with the given location metadata set (if available). -func NewRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber uint64, index uint64) *RPCTransaction { - var signer types.Signer = types.FrontierSigner{} - if tx.Protected() { - signer = types.NewEIP155Signer(tx.ChainId()) - } - from, _ := types.Sender(signer, tx) - v, r, s := tx.RawSignatureValues() - - result := &RPCTransaction{ - From: from, - Gas: hexutil.Uint64(tx.Gas()), - GasPrice: (*hexutil.Big)(tx.GasPrice()), - Hash: tx.Hash(), - Input: hexutil.Bytes(tx.Data()), // somehow this is ending up `nil` - Nonce: hexutil.Uint64(tx.Nonce()), - To: tx.To(), - Value: (*hexutil.Big)(tx.Value()), - V: (*hexutil.Big)(v), - R: (*hexutil.Big)(r), - S: (*hexutil.Big)(s), - } - if blockHash != (common.Hash{}) { - result.BlockHash = &blockHash - result.BlockNumber = (*hexutil.Big)(new(big.Int).SetUint64(blockNumber)) - result.TransactionIndex = (*hexutil.Uint64)(&index) - } - return result -} diff --git a/pkg/super_node/eth/api_test.go b/pkg/super_node/eth/api_test.go index 61915b62..60106aee 100644 --- a/pkg/super_node/eth/api_test.go +++ b/pkg/super_node/eth/api_test.go @@ -101,8 +101,10 @@ var _ = Describe("API", func() { mocks.HeaderCID: mocks.HeaderIPLD, mocks.Trx1CID: mocks.Trx1IPLD, mocks.Trx2CID: mocks.Trx2IPLD, + mocks.Trx3CID: mocks.Trx3IPLD, mocks.Rct1CID: mocks.Rct1IPLD, mocks.Rct2CID: mocks.Rct2IPLD, + mocks.Rct3CID: mocks.Rct3IPLD, mocks.State1CID: mocks.State1IPLD, mocks.State2CID: mocks.State2IPLD, mocks.StorageCID: mocks.StorageIPLD, diff --git a/pkg/super_node/eth/backend.go b/pkg/super_node/eth/backend.go index 0b23a0d5..6c8d4a9b 100644 --- a/pkg/super_node/eth/backend.go +++ b/pkg/super_node/eth/backend.go @@ -22,6 +22,9 @@ import ( "fmt" "math/big" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/vulcanize/vulcanizedb/pkg/ipfs" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/rlp" @@ -309,3 +312,206 @@ func (b *Backend) GetTransaction(ctx context.Context, txHash common.Hash) (*type } return &transaction, common.HexToHash(txCIDWithHeaderInfo.BlockHash), uint64(txCIDWithHeaderInfo.BlockNumber), uint64(txCIDWithHeaderInfo.Index), nil } + +// extractLogsOfInterest returns logs from the receipt IPLD +func extractLogsOfInterest(rctIPLDs []ipfs.BlockModel, wantedTopics [][]string) ([]*types.Log, error) { + var logs []*types.Log + for _, rctIPLD := range rctIPLDs { + rctRLP := rctIPLD + var rct types.Receipt + if err := rlp.DecodeBytes(rctRLP.Data, &rct); err != nil { + return nil, err + } + for _, log := range rct.Logs { + if wanted := wantedLog(wantedTopics, log.Topics); wanted == true { + logs = append(logs, log) + } + } + } + return logs, nil +} + +// returns true if the log matches on the filter +func wantedLog(wantedTopics [][]string, actualTopics []common.Hash) bool { + // actualTopics will always have length <= 4 + // wantedTopics will always have length 4 + matches := 0 + for i, actualTopic := range actualTopics { + // If we have topics in this filter slot, count as a match if the actualTopic matches one of the ones in this filter slot + if len(wantedTopics[i]) > 0 { + matches += sliceContainsHash(wantedTopics[i], actualTopic) + } else { + // Filter slot is empty, not matching any topics at this slot => counts as a match + matches++ + } + } + if matches == len(actualTopics) { + return true + } + return false +} + +// returns 1 if the slice contains the hash, 0 if it does not +func sliceContainsHash(slice []string, hash common.Hash) int { + for _, str := range slice { + if str == hash.String() { + return 1 + } + } + return 0 +} + +// rpcMarshalHeader uses the generalized output filler, then adds the total difficulty field, which requires +// a `PublicEthAPI`. +func (pea *PublicEthAPI) rpcMarshalHeader(header *types.Header) (map[string]interface{}, error) { + fields := RPCMarshalHeader(header) + td, err := pea.b.GetTd(header.Hash()) + if err != nil { + return nil, err + } + fields["totalDifficulty"] = (*hexutil.Big)(td) + return fields, nil +} + +// RPCMarshalHeader converts the given header to the RPC output. +// This function is eth/internal so we have to make our own version here... +func RPCMarshalHeader(head *types.Header) map[string]interface{} { + return map[string]interface{}{ + "number": (*hexutil.Big)(head.Number), + "hash": head.Hash(), + "parentHash": head.ParentHash, + "nonce": head.Nonce, + "mixHash": head.MixDigest, + "sha3Uncles": head.UncleHash, + "logsBloom": head.Bloom, + "stateRoot": head.Root, + "miner": head.Coinbase, + "difficulty": (*hexutil.Big)(head.Difficulty), + "extraData": hexutil.Bytes(head.Extra), + "size": hexutil.Uint64(head.Size()), + "gasLimit": hexutil.Uint64(head.GasLimit), + "gasUsed": hexutil.Uint64(head.GasUsed), + "timestamp": hexutil.Uint64(head.Time), + "transactionsRoot": head.TxHash, + "receiptsRoot": head.ReceiptHash, + } +} + +// rpcMarshalBlock uses the generalized output filler, then adds the total difficulty field, which requires +// a `PublicBlockchainAPI`. +func (pea *PublicEthAPI) rpcMarshalBlock(b *types.Block, inclTx bool, fullTx bool) (map[string]interface{}, error) { + fields, err := RPCMarshalBlock(b, inclTx, fullTx) + if err != nil { + return nil, err + } + td, err := pea.b.GetTd(b.Hash()) + if err != nil { + return nil, err + } + fields["totalDifficulty"] = (*hexutil.Big)(td) + return fields, err +} + +// RPCMarshalBlock converts the given block to the RPC output which depends on fullTx. If inclTx is true transactions are +// returned. When fullTx is true the returned block contains full transaction details, otherwise it will only contain +// transaction hashes. +func RPCMarshalBlock(block *types.Block, inclTx bool, fullTx bool) (map[string]interface{}, error) { + fields := RPCMarshalHeader(block.Header()) + fields["size"] = hexutil.Uint64(block.Size()) + + if inclTx { + formatTx := func(tx *types.Transaction) (interface{}, error) { + return tx.Hash(), nil + } + if fullTx { + formatTx = func(tx *types.Transaction) (interface{}, error) { + return NewRPCTransactionFromBlockHash(block, tx.Hash()), nil + } + } + txs := block.Transactions() + transactions := make([]interface{}, len(txs)) + var err error + for i, tx := range txs { + if transactions[i], err = formatTx(tx); err != nil { + return nil, err + } + } + fields["transactions"] = transactions + } + uncles := block.Uncles() + uncleHashes := make([]common.Hash, len(uncles)) + for i, uncle := range uncles { + uncleHashes[i] = uncle.Hash() + } + fields["uncles"] = uncleHashes + + return fields, nil +} + +// NewRPCTransactionFromBlockHash returns a transaction that will serialize to the RPC representation. +func NewRPCTransactionFromBlockHash(b *types.Block, hash common.Hash) *RPCTransaction { + for idx, tx := range b.Transactions() { + if tx.Hash() == hash { + return newRPCTransactionFromBlockIndex(b, uint64(idx)) + } + } + return nil +} + +// newRPCTransactionFromBlockIndex returns a transaction that will serialize to the RPC representation. +func newRPCTransactionFromBlockIndex(b *types.Block, index uint64) *RPCTransaction { + txs := b.Transactions() + if index >= uint64(len(txs)) { + return nil + } + return NewRPCTransaction(txs[index], b.Hash(), b.NumberU64(), index) +} + +// RPCTransaction represents a transaction that will serialize to the RPC representation of a transaction +type RPCTransaction struct { + BlockHash *common.Hash `json:"blockHash"` + BlockNumber *hexutil.Big `json:"blockNumber"` + From common.Address `json:"from"` + Gas hexutil.Uint64 `json:"gas"` + GasPrice *hexutil.Big `json:"gasPrice"` + Hash common.Hash `json:"hash"` + Input hexutil.Bytes `json:"input"` + Nonce hexutil.Uint64 `json:"nonce"` + To *common.Address `json:"to"` + TransactionIndex *hexutil.Uint64 `json:"transactionIndex"` + Value *hexutil.Big `json:"value"` + V *hexutil.Big `json:"v"` + R *hexutil.Big `json:"r"` + S *hexutil.Big `json:"s"` +} + +// NewRPCTransaction returns a transaction that will serialize to the RPC +// representation, with the given location metadata set (if available). +func NewRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber uint64, index uint64) *RPCTransaction { + var signer types.Signer = types.FrontierSigner{} + if tx.Protected() { + signer = types.NewEIP155Signer(tx.ChainId()) + } + from, _ := types.Sender(signer, tx) + v, r, s := tx.RawSignatureValues() + + result := &RPCTransaction{ + From: from, + Gas: hexutil.Uint64(tx.Gas()), + GasPrice: (*hexutil.Big)(tx.GasPrice()), + Hash: tx.Hash(), + Input: hexutil.Bytes(tx.Data()), // somehow this is ending up `nil` + Nonce: hexutil.Uint64(tx.Nonce()), + To: tx.To(), + Value: (*hexutil.Big)(tx.Value()), + V: (*hexutil.Big)(v), + R: (*hexutil.Big)(r), + S: (*hexutil.Big)(s), + } + if blockHash != (common.Hash{}) { + result.BlockHash = &blockHash + result.BlockNumber = (*hexutil.Big)(new(big.Int).SetUint64(blockNumber)) + result.TransactionIndex = (*hexutil.Uint64)(&index) + } + return result +} diff --git a/pkg/super_node/eth/converter.go b/pkg/super_node/eth/converter.go index 61a7785b..ae5bee85 100644 --- a/pkg/super_node/eth/converter.go +++ b/pkg/super_node/eth/converter.go @@ -72,8 +72,8 @@ func (pc *PayloadConverter) Convert(payload shared.RawChainData) (shared.Convert return nil, err } txMeta := TxModel{ - Dst: shared.HandleNullAddr(trx.To()), - Src: shared.HandleNullAddr(&from), + Dst: shared.HandleNullAddrPointer(trx.To()), + Src: shared.HandleNullAddr(from), TxHash: trx.Hash().String(), Index: int64(i), } @@ -90,28 +90,27 @@ func (pc *PayloadConverter) Convert(payload shared.RawChainData) (shared.Convert if err := receipts.DeriveFields(pc.chainConfig, block.Hash(), block.NumberU64(), block.Transactions()); err != nil { return nil, err } - for i, receipt := range receipts { - // If the transaction for this receipt has a "to" address, the above DeriveFields() fails to assign it to the receipt's ContractAddress - // If it doesn't have a "to" address, it correctly derives it and assigns it to to the receipt's ContractAddress - // Weird, right? - if transactions[i].To() != nil { - receipt.ContractAddress = *transactions[i].To() - } + for _, receipt := range receipts { // Extract topic and contract data from the receipt for indexing topicSets := make([][]string, 4) + mappedContracts := make(map[string]bool) // use map to avoid duplicate addresses for _, log := range receipt.Logs { - for i := range topicSets { - if i < len(log.Topics) { - topicSets[i] = append(topicSets[i], log.Topics[i].Hex()) - } + for i, topic := range log.Topics { + topicSets[i] = append(topicSets[i], topic.Hex()) } + mappedContracts[log.Address.String()] = true + } + logContracts := make([]string, 0, len(mappedContracts)) + for addr := range mappedContracts { + logContracts = append(logContracts, addr) } rctMeta := ReceiptModel{ - Topic0s: topicSets[0], - Topic1s: topicSets[1], - Topic2s: topicSets[2], - Topic3s: topicSets[3], - Contract: receipt.ContractAddress.Hex(), + Topic0s: topicSets[0], + Topic1s: topicSets[1], + Topic2s: topicSets[2], + Topic3s: topicSets[3], + Contract: shared.HandleNullAddr(receipt.ContractAddress), + LogContracts: logContracts, } // receipt and rctMeta will have same indexes convertedPayload.Receipts = append(convertedPayload.Receipts, receipt) diff --git a/pkg/super_node/eth/filterer.go b/pkg/super_node/eth/filterer.go index eb941ae7..6ae7d691 100644 --- a/pkg/super_node/eth/filterer.go +++ b/pkg/super_node/eth/filterer.go @@ -173,7 +173,7 @@ func (s *ResponseFilterer) filerReceipts(receiptFilter ReceiptFilter, response * for i, receipt := range payload.Receipts { // topics is always length 4 topics := [][]string{payload.ReceiptMetaData[i].Topic0s, payload.ReceiptMetaData[i].Topic1s, payload.ReceiptMetaData[i].Topic2s, payload.ReceiptMetaData[i].Topic3s} - if checkReceipts(receipt, receiptFilter.Topics, topics, receiptFilter.Contracts, payload.ReceiptMetaData[i].Contract, trxHashes) { + if checkReceipts(receipt, receiptFilter.Topics, topics, receiptFilter.LogAddresses, payload.ReceiptMetaData[i].LogContracts, trxHashes) { receiptBuffer := new(bytes.Buffer) if err := receipt.EncodeRLP(receiptBuffer); err != nil { return err @@ -193,9 +193,9 @@ func (s *ResponseFilterer) filerReceipts(receiptFilter ReceiptFilter, response * return nil } -func checkReceipts(rct *types.Receipt, wantedTopics, actualTopics [][]string, wantedContracts []string, actualContract string, wantedTrxHashes []common.Hash) bool { +func checkReceipts(rct *types.Receipt, wantedTopics, actualTopics [][]string, wantedAddresses []string, actualAddresses []string, wantedTrxHashes []common.Hash) bool { // If we aren't filtering for any topics, contracts, or corresponding trxs then all receipts are a go - if len(wantedTopics) == 0 && len(wantedContracts) == 0 && len(wantedTrxHashes) == 0 { + if len(wantedTopics) == 0 && len(wantedAddresses) == 0 && len(wantedTrxHashes) == 0 { return true } // Keep receipts that are from watched txs @@ -205,18 +205,20 @@ func checkReceipts(rct *types.Receipt, wantedTopics, actualTopics [][]string, wa } } // If there are no wanted contract addresses, we keep all receipts that match the topic filter - if len(wantedContracts) == 0 { + if len(wantedAddresses) == 0 { if match := filterMatch(wantedTopics, actualTopics); match == true { return true } } // If there are wanted contract addresses to filter on - for _, wantedAddr := range wantedContracts { + for _, wantedAddr := range wantedAddresses { // and this is an address of interest - if wantedAddr == actualContract { - // we keep the receipt if it matches on the topic filter - if match := filterMatch(wantedTopics, actualTopics); match == true { - return true + for _, actualAddr := range actualAddresses { + if wantedAddr == actualAddr { + // we keep the receipt if it matches on the topic filter + if match := filterMatch(wantedTopics, actualTopics); match == true { + return true + } } } } diff --git a/pkg/super_node/eth/filterer_test.go b/pkg/super_node/eth/filterer_test.go index 568f0e2e..82e40bc0 100644 --- a/pkg/super_node/eth/filterer_test.go +++ b/pkg/super_node/eth/filterer_test.go @@ -48,12 +48,14 @@ var _ = Describe("Filterer", func() { Expect(iplds.Header).To(Equal(mocks.MockIPLDs.Header)) var expectedEmptyUncles []ipfs.BlockModel Expect(iplds.Uncles).To(Equal(expectedEmptyUncles)) - Expect(len(iplds.Transactions)).To(Equal(2)) + Expect(len(iplds.Transactions)).To(Equal(3)) Expect(shared.IPLDsContainBytes(iplds.Transactions, mocks.MockTransactions.GetRlp(0))).To(BeTrue()) Expect(shared.IPLDsContainBytes(iplds.Transactions, mocks.MockTransactions.GetRlp(1))).To(BeTrue()) - Expect(len(iplds.Receipts)).To(Equal(2)) + Expect(shared.IPLDsContainBytes(iplds.Transactions, mocks.MockTransactions.GetRlp(2))).To(BeTrue()) + Expect(len(iplds.Receipts)).To(Equal(3)) Expect(shared.IPLDsContainBytes(iplds.Receipts, mocks.MockReceipts.GetRlp(0))).To(BeTrue()) Expect(shared.IPLDsContainBytes(iplds.Receipts, mocks.MockReceipts.GetRlp(1))).To(BeTrue()) + Expect(shared.IPLDsContainBytes(iplds.Receipts, mocks.MockReceipts.GetRlp(2))).To(BeTrue()) Expect(len(iplds.StateNodes)).To(Equal(2)) for _, stateNode := range iplds.StateNodes { Expect(stateNode.Type).To(Equal(statediff.Leaf)) @@ -74,7 +76,7 @@ var _ = Describe("Filterer", func() { }) It("Applies filters from the provided config.Subscription", func() { - payload1, err := filterer.Filter(rctContractFilter, mocks.MockConvertedPayload) + payload1, err := filterer.Filter(rctAddressFilter, mocks.MockConvertedPayload) Expect(err).ToNot(HaveOccurred()) iplds1, ok := payload1.(eth.IPLDs) Expect(ok).To(BeTrue()) @@ -86,8 +88,8 @@ var _ = Describe("Filterer", func() { Expect(len(iplds1.StateNodes)).To(Equal(0)) Expect(len(iplds1.Receipts)).To(Equal(1)) Expect(iplds1.Receipts[0]).To(Equal(ipfs.BlockModel{ - Data: mocks.Rct2IPLD.RawData(), - CID: mocks.Rct2IPLD.Cid().String(), + Data: mocks.Rct1IPLD.RawData(), + CID: mocks.Rct1IPLD.Cid().String(), })) payload2, err := filterer.Filter(rctTopicsFilter, mocks.MockConvertedPayload) @@ -106,7 +108,7 @@ var _ = Describe("Filterer", func() { CID: mocks.Rct1IPLD.Cid().String(), })) - payload3, err := filterer.Filter(rctTopicsAndContractFilter, mocks.MockConvertedPayload) + payload3, err := filterer.Filter(rctTopicsAndAddressFilter, mocks.MockConvertedPayload) Expect(err).ToNot(HaveOccurred()) iplds3, ok := payload3.(eth.IPLDs) Expect(ok).To(BeTrue()) @@ -122,7 +124,7 @@ var _ = Describe("Filterer", func() { CID: mocks.Rct1IPLD.Cid().String(), })) - payload4, err := filterer.Filter(rctContractsAndTopicFilter, mocks.MockConvertedPayload) + payload4, err := filterer.Filter(rctAddressesAndTopicFilter, mocks.MockConvertedPayload) Expect(err).ToNot(HaveOccurred()) iplds4, ok := payload4.(eth.IPLDs) Expect(ok).To(BeTrue()) @@ -145,14 +147,16 @@ var _ = Describe("Filterer", func() { Expect(iplds5.BlockNumber.Int64()).To(Equal(mocks.MockIPLDs.BlockNumber.Int64())) Expect(iplds5.Header).To(Equal(ipfs.BlockModel{})) Expect(len(iplds5.Uncles)).To(Equal(0)) - Expect(len(iplds5.Transactions)).To(Equal(2)) + Expect(len(iplds5.Transactions)).To(Equal(3)) Expect(shared.IPLDsContainBytes(iplds5.Transactions, mocks.MockTransactions.GetRlp(0))).To(BeTrue()) Expect(shared.IPLDsContainBytes(iplds5.Transactions, mocks.MockTransactions.GetRlp(1))).To(BeTrue()) + Expect(shared.IPLDsContainBytes(iplds5.Transactions, mocks.MockTransactions.GetRlp(2))).To(BeTrue()) Expect(len(iplds5.StorageNodes)).To(Equal(0)) Expect(len(iplds5.StateNodes)).To(Equal(0)) - Expect(len(iplds5.Receipts)).To(Equal(2)) + Expect(len(iplds5.Receipts)).To(Equal(3)) Expect(shared.IPLDsContainBytes(iplds5.Receipts, mocks.MockReceipts.GetRlp(0))).To(BeTrue()) Expect(shared.IPLDsContainBytes(iplds5.Receipts, mocks.MockReceipts.GetRlp(1))).To(BeTrue()) + Expect(shared.IPLDsContainBytes(iplds5.Receipts, mocks.MockReceipts.GetRlp(2))).To(BeTrue()) payload6, err := filterer.Filter(rctsForSelectCollectedTrxs, mocks.MockConvertedPayload) Expect(err).ToNot(HaveOccurred()) @@ -188,7 +192,7 @@ var _ = Describe("Filterer", func() { CID: mocks.State2IPLD.Cid().String(), })) - payload8, err := filterer.Filter(rctTopicsAndContractFilterFail, mocks.MockConvertedPayload) + payload8, err := filterer.Filter(rctTopicsAndAddressFilterFail, mocks.MockConvertedPayload) Expect(err).ToNot(HaveOccurred()) iplds8, ok := payload8.(eth.IPLDs) Expect(ok).To(BeTrue()) diff --git a/pkg/super_node/eth/indexer.go b/pkg/super_node/eth/indexer.go index bbef66ef..6c16d058 100644 --- a/pkg/super_node/eth/indexer.go +++ b/pkg/super_node/eth/indexer.go @@ -19,15 +19,13 @@ package eth import ( "fmt" - "github.com/ethereum/go-ethereum/crypto" - - "github.com/vulcanize/vulcanizedb/pkg/super_node/shared" - "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" "github.com/jmoiron/sqlx" log "github.com/sirupsen/logrus" "github.com/vulcanize/vulcanizedb/pkg/postgres" + "github.com/vulcanize/vulcanizedb/pkg/super_node/shared" ) var ( @@ -129,8 +127,8 @@ func (in *CIDIndexer) indexTransactionAndReceiptCIDs(tx *sqlx.Tx, payload *CIDPa } func (in *CIDIndexer) indexReceiptCID(tx *sqlx.Tx, cidMeta ReceiptModel, txID int64) error { - _, err := tx.Exec(`INSERT INTO eth.receipt_cids (tx_id, cid, contract, topic0s, topic1s, topic2s, topic3s) VALUES ($1, $2, $3, $4, $5, $6, $7)`, - txID, cidMeta.CID, cidMeta.Contract, cidMeta.Topic0s, cidMeta.Topic1s, cidMeta.Topic2s, cidMeta.Topic3s) + _, err := tx.Exec(`INSERT INTO eth.receipt_cids (tx_id, cid, contract, topic0s, topic1s, topic2s, topic3s, log_contracts) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`, + txID, cidMeta.CID, cidMeta.Contract, cidMeta.Topic0s, cidMeta.Topic1s, cidMeta.Topic2s, cidMeta.Topic3s, cidMeta.LogContracts) return err } diff --git a/pkg/super_node/eth/indexer_test.go b/pkg/super_node/eth/indexer_test.go index 0a0e89bf..471a3c2b 100644 --- a/pkg/super_node/eth/indexer_test.go +++ b/pkg/super_node/eth/indexer_test.go @@ -68,9 +68,10 @@ var _ = Describe("Indexer", func() { WHERE header_cids.block_number = $1` err = db.Select(&trxs, pgStr, 1) Expect(err).ToNot(HaveOccurred()) - Expect(len(trxs)).To(Equal(2)) + Expect(len(trxs)).To(Equal(3)) Expect(shared.ListContainsString(trxs, mocks.Trx1CID.String())).To(BeTrue()) Expect(shared.ListContainsString(trxs, mocks.Trx2CID.String())).To(BeTrue()) + Expect(shared.ListContainsString(trxs, mocks.Trx3CID.String())).To(BeTrue()) // check receipts were properly indexed rcts := make([]string, 0) pgStr = `SELECT receipt_cids.cid FROM eth.receipt_cids, eth.transaction_cids, eth.header_cids @@ -79,9 +80,10 @@ var _ = Describe("Indexer", func() { AND header_cids.block_number = $1` err = db.Select(&rcts, pgStr, 1) Expect(err).ToNot(HaveOccurred()) - Expect(len(rcts)).To(Equal(2)) + Expect(len(rcts)).To(Equal(3)) Expect(shared.ListContainsString(rcts, mocks.Rct1CID.String())).To(BeTrue()) Expect(shared.ListContainsString(rcts, mocks.Rct2CID.String())).To(BeTrue()) + Expect(shared.ListContainsString(rcts, mocks.Rct3CID.String())).To(BeTrue()) // check that state nodes were properly indexed stateNodes := make([]eth.StateNodeModel, 0) pgStr = `SELECT state_cids.cid, state_cids.state_leaf_key, state_cids.node_type, state_cids.state_path, state_cids.header_id diff --git a/pkg/super_node/eth/mocks/test_data.go b/pkg/super_node/eth/mocks/test_data.go index dfefcc71..8e981a51 100644 --- a/pkg/super_node/eth/mocks/test_data.go +++ b/pkg/super_node/eth/mocks/test_data.go @@ -53,64 +53,84 @@ var ( Difficulty: big.NewInt(5000000), Extra: []byte{}, } - MockTransactions, MockReceipts, senderAddr = createTransactionsAndReceipts() + MockTransactions, MockReceipts, SenderAddr = createTransactionsAndReceipts() ReceiptsRlp, _ = rlp.EncodeToBytes(MockReceipts) MockBlock = types.NewBlock(&MockHeader, MockTransactions, nil, MockReceipts) MockBlockRlp, _ = rlp.EncodeToBytes(MockBlock) MockHeaderRlp, _ = rlp.EncodeToBytes(MockBlock.Header()) Address = common.HexToAddress("0xaE9BEa628c4Ce503DcFD7E305CaB4e29E7476592") AnotherAddress = common.HexToAddress("0xaE9BEa628c4Ce503DcFD7E305CaB4e29E7476593") + ContractAddress = crypto.CreateAddress(SenderAddr, MockTransactions[2].Nonce()) + NullAddr = common.HexToAddress("0x0000000000000000000000000000000000000000") mockTopic11 = common.HexToHash("0x04") mockTopic12 = common.HexToHash("0x06") mockTopic21 = common.HexToHash("0x05") mockTopic22 = common.HexToHash("0x07") MockLog1 = &types.Log{ - Topics: []common.Hash{mockTopic11, mockTopic12}, - Data: []byte{}, + Address: Address, + Topics: []common.Hash{mockTopic11, mockTopic12}, + Data: []byte{}, } MockLog2 = &types.Log{ - Topics: []common.Hash{mockTopic21, mockTopic22}, - Data: []byte{}, + Address: AnotherAddress, + Topics: []common.Hash{mockTopic21, mockTopic22}, + Data: []byte{}, } HeaderCID, _ = ipld.RawdataToCid(ipld.MEthHeader, MockHeaderRlp, multihash.KECCAK_256) Trx1CID, _ = ipld.RawdataToCid(ipld.MEthTx, MockTransactions.GetRlp(0), multihash.KECCAK_256) Trx2CID, _ = ipld.RawdataToCid(ipld.MEthTx, MockTransactions.GetRlp(1), multihash.KECCAK_256) + Trx3CID, _ = ipld.RawdataToCid(ipld.MEthTx, MockTransactions.GetRlp(2), multihash.KECCAK_256) Rct1CID, _ = ipld.RawdataToCid(ipld.MEthTxReceipt, MockReceipts.GetRlp(0), multihash.KECCAK_256) Rct2CID, _ = ipld.RawdataToCid(ipld.MEthTxReceipt, MockReceipts.GetRlp(1), multihash.KECCAK_256) + Rct3CID, _ = ipld.RawdataToCid(ipld.MEthTxReceipt, MockReceipts.GetRlp(2), multihash.KECCAK_256) State1CID, _ = ipld.RawdataToCid(ipld.MEthStateTrie, ContractLeafNode, multihash.KECCAK_256) State2CID, _ = ipld.RawdataToCid(ipld.MEthStateTrie, AccountLeafNode, multihash.KECCAK_256) StorageCID, _ = ipld.RawdataToCid(ipld.MEthStorageTrie, StorageLeafNode, multihash.KECCAK_256) MockTrxMeta = []eth.TxModel{ { CID: "", // This is empty until we go to publish to ipfs - Src: senderAddr.Hex(), + Src: SenderAddr.Hex(), Dst: Address.String(), Index: 0, TxHash: MockTransactions[0].Hash().String(), }, { CID: "", - Src: senderAddr.Hex(), + Src: SenderAddr.Hex(), Dst: AnotherAddress.String(), Index: 1, TxHash: MockTransactions[1].Hash().String(), }, + { + CID: "", + Src: SenderAddr.Hex(), + Dst: "", + Index: 2, + TxHash: MockTransactions[2].Hash().String(), + }, } MockTrxMetaPostPublsh = []eth.TxModel{ { CID: Trx1CID.String(), // This is empty until we go to publish to ipfs - Src: senderAddr.Hex(), + Src: SenderAddr.Hex(), Dst: Address.String(), Index: 0, TxHash: MockTransactions[0].Hash().String(), }, { CID: Trx2CID.String(), - Src: senderAddr.Hex(), + Src: SenderAddr.Hex(), Dst: AnotherAddress.String(), Index: 1, TxHash: MockTransactions[1].Hash().String(), }, + { + CID: Trx3CID.String(), + Src: SenderAddr.Hex(), + Dst: "", + Index: 2, + TxHash: MockTransactions[2].Hash().String(), + }, } MockRctMeta = []eth.ReceiptModel{ { @@ -121,7 +141,10 @@ var ( Topic1s: []string{ mockTopic12.String(), }, - Contract: Address.String(), + Contract: "", + LogContracts: []string{ + Address.String(), + }, }, { CID: "", @@ -131,7 +154,15 @@ var ( Topic1s: []string{ mockTopic22.String(), }, - Contract: AnotherAddress.String(), + Contract: "", + LogContracts: []string{ + AnotherAddress.String(), + }, + }, + { + CID: "", + Contract: ContractAddress.String(), + LogContracts: []string{}, }, } MockRctMetaPostPublish = []eth.ReceiptModel{ @@ -143,7 +174,10 @@ var ( Topic1s: []string{ mockTopic12.String(), }, - Contract: Address.String(), + Contract: "", + LogContracts: []string{ + Address.String(), + }, }, { CID: Rct2CID.String(), @@ -153,7 +187,15 @@ var ( Topic1s: []string{ mockTopic22.String(), }, - Contract: AnotherAddress.String(), + Contract: "", + LogContracts: []string{ + AnotherAddress.String(), + }, + }, + { + CID: Rct3CID.String(), + Contract: ContractAddress.String(), + LogContracts: []string{}, }, } @@ -171,7 +213,6 @@ var ( contractRoot = "0x821e2556a290c86405f8160a2d662042a431ba456b9db265c79bb837c04be5f0" contractCodeHash = common.HexToHash("0x753f98a8d4328b15636e46f66f2cb4bc860100aa17967cc145fcd17d1d4710ea") contractPathHash = crypto.Keccak256Hash([]byte{'\x06'}) - ContractAddress = common.HexToAddress("0x703c4b2bD70c169f5717101CaeE543299Fc946C7") ContractLeafKey = testhelpers.AddressToLeafKey(ContractAddress) ContractAccount, _ = rlp.EncodeToBytes(state.Account{ Nonce: nonce1, @@ -310,6 +351,7 @@ var ( ReceiptCIDs: map[common.Hash]eth.ReceiptModel{ MockTransactions[0].Hash(): MockRctMetaPostPublish[0], MockTransactions[1].Hash(): MockRctMetaPostPublish[1], + MockTransactions[2].Hash(): MockRctMetaPostPublish[2], }, StateNodeCIDs: MockStateMetaPostPublish, StorageNodeCIDs: map[common.Hash][]eth.StorageNodeModel{ @@ -372,8 +414,10 @@ var ( HeaderIPLD, _ = blocks.NewBlockWithCid(MockHeaderRlp, HeaderCID) Trx1IPLD, _ = blocks.NewBlockWithCid(MockTransactions.GetRlp(0), Trx1CID) Trx2IPLD, _ = blocks.NewBlockWithCid(MockTransactions.GetRlp(1), Trx2CID) + Trx3IPLD, _ = blocks.NewBlockWithCid(MockTransactions.GetRlp(2), Trx3CID) Rct1IPLD, _ = blocks.NewBlockWithCid(MockReceipts.GetRlp(0), Rct1CID) Rct2IPLD, _ = blocks.NewBlockWithCid(MockReceipts.GetRlp(1), Rct2CID) + Rct3IPLD, _ = blocks.NewBlockWithCid(MockReceipts.GetRlp(2), Rct3CID) State1IPLD, _ = blocks.NewBlockWithCid(ContractLeafNode, State1CID) State2IPLD, _ = blocks.NewBlockWithCid(AccountLeafNode, State2CID) StorageIPLD, _ = blocks.NewBlockWithCid(StorageLeafNode, StorageCID) @@ -393,6 +437,10 @@ var ( Data: Trx2IPLD.RawData(), CID: Trx2IPLD.Cid().String(), }, + { + Data: Trx3IPLD.RawData(), + CID: Trx3IPLD.Cid().String(), + }, }, Receipts: []ipfs.BlockModel{ { @@ -403,6 +451,10 @@ var ( Data: Rct2IPLD.RawData(), CID: Rct2IPLD.Cid().String(), }, + { + Data: Rct3IPLD.RawData(), + CID: Rct3IPLD.Cid().String(), + }, }, StateNodes: []eth2.StateNode{ { @@ -444,6 +496,7 @@ func createTransactionsAndReceipts() (types.Transactions, types.Receipts, common // make transactions trx1 := types.NewTransaction(0, Address, big.NewInt(1000), 50, big.NewInt(100), []byte{}) trx2 := types.NewTransaction(1, AnotherAddress, big.NewInt(2000), 100, big.NewInt(200), []byte{}) + trx3 := types.NewContractCreation(2, big.NewInt(1500), 75, big.NewInt(150), []byte{0, 1, 2, 3, 4, 5}) transactionSigner := types.MakeSigner(params.MainnetChainConfig, BlockNumber) mockCurve := elliptic.P256() mockPrvKey, err := ecdsa.GenerateKey(mockCurve, rand.Reader) @@ -458,7 +511,11 @@ func createTransactionsAndReceipts() (types.Transactions, types.Receipts, common if err != nil { log.Fatal(err) } - senderAddr, err := types.Sender(transactionSigner, signedTrx1) // same for both trx + signedTrx3, err := types.SignTx(trx3, transactionSigner, mockPrvKey) + if err != nil { + log.Fatal(err) + } + SenderAddr, err := types.Sender(transactionSigner, signedTrx1) // same for both trx if err != nil { log.Fatal(err) } @@ -469,5 +526,8 @@ func createTransactionsAndReceipts() (types.Transactions, types.Receipts, common mockReceipt2 := types.NewReceipt(common.HexToHash("0x1").Bytes(), false, 100) mockReceipt2.Logs = []*types.Log{MockLog2} mockReceipt2.TxHash = signedTrx2.Hash() - return types.Transactions{signedTrx1, signedTrx2}, types.Receipts{mockReceipt1, mockReceipt2}, senderAddr + mockReceipt3 := types.NewReceipt(common.HexToHash("0x2").Bytes(), false, 75) + mockReceipt3.Logs = []*types.Log{} + mockReceipt3.TxHash = signedTrx3.Hash() + return types.Transactions{signedTrx1, signedTrx2, signedTrx3}, types.Receipts{mockReceipt1, mockReceipt2, mockReceipt3}, SenderAddr } diff --git a/pkg/super_node/eth/models.go b/pkg/super_node/eth/models.go index b2a84b44..52740799 100644 --- a/pkg/super_node/eth/models.go +++ b/pkg/super_node/eth/models.go @@ -34,6 +34,7 @@ type HeaderModel struct { RctRoot string `db:"receipt_root"` Bloom []byte `db:"bloom"` Timestamp uint64 `db:"timestamp"` + TimesValidated int64 `db:"times_validated"` } // UncleModel is the db model for eth.uncle_cids @@ -59,14 +60,15 @@ type TxModel struct { // ReceiptModel is the db model for eth.receipt_cids type ReceiptModel struct { - ID int64 `db:"id"` - TxID int64 `db:"tx_id"` - CID string `db:"cid"` - Contract string `db:"contract"` - Topic0s pq.StringArray `db:"topic0s"` - Topic1s pq.StringArray `db:"topic1s"` - Topic2s pq.StringArray `db:"topic2s"` - Topic3s pq.StringArray `db:"topic3s"` + ID int64 `db:"id"` + TxID int64 `db:"tx_id"` + CID string `db:"cid"` + Contract string `db:"contract"` + LogContracts pq.StringArray `db:"log_contracts"` + Topic0s pq.StringArray `db:"topic0s"` + Topic1s pq.StringArray `db:"topic1s"` + Topic2s pq.StringArray `db:"topic2s"` + Topic3s pq.StringArray `db:"topic3s"` } // StateNodeModel is the db model for eth.state_cids diff --git a/pkg/super_node/eth/publisher.go b/pkg/super_node/eth/publisher.go index 0d09c403..21a9c01a 100644 --- a/pkg/super_node/eth/publisher.go +++ b/pkg/super_node/eth/publisher.go @@ -188,12 +188,13 @@ func (pub *IPLDPublisher) publishReceipts(receipts []*ipld.EthReceipt, receiptTr return nil, err } rctCids[rct.TxHash] = ReceiptModel{ - CID: cid, - Contract: receiptMeta[i].Contract, - Topic0s: receiptMeta[i].Topic0s, - Topic1s: receiptMeta[i].Topic1s, - Topic2s: receiptMeta[i].Topic2s, - Topic3s: receiptMeta[i].Topic3s, + CID: cid, + Contract: receiptMeta[i].Contract, + Topic0s: receiptMeta[i].Topic0s, + Topic1s: receiptMeta[i].Topic1s, + Topic2s: receiptMeta[i].Topic2s, + Topic3s: receiptMeta[i].Topic3s, + LogContracts: receiptMeta[i].LogContracts, } } for _, rctNode := range receiptTrie { diff --git a/pkg/super_node/eth/publisher_test.go b/pkg/super_node/eth/publisher_test.go index 62c517de..ca0aa3d6 100644 --- a/pkg/super_node/eth/publisher_test.go +++ b/pkg/super_node/eth/publisher_test.go @@ -55,10 +55,12 @@ var _ = Describe("Publisher", func() { mockTrxDagPutter.CIDsToReturn = map[common.Hash]string{ common.BytesToHash(mocks.Trx1IPLD.RawData()): mocks.Trx1CID.String(), common.BytesToHash(mocks.Trx2IPLD.RawData()): mocks.Trx2CID.String(), + common.BytesToHash(mocks.Trx3IPLD.RawData()): mocks.Trx3CID.String(), } mockRctDagPutter.CIDsToReturn = map[common.Hash]string{ common.BytesToHash(mocks.Rct1IPLD.RawData()): mocks.Rct1CID.String(), common.BytesToHash(mocks.Rct2IPLD.RawData()): mocks.Rct2CID.String(), + common.BytesToHash(mocks.Rct3IPLD.RawData()): mocks.Rct3CID.String(), } mockStateDagPutter.CIDsToReturn = map[common.Hash]string{ common.BytesToHash(mocks.State1IPLD.RawData()): mocks.State1CID.String(), @@ -86,12 +88,14 @@ var _ = Describe("Publisher", func() { Expect(cidPayload.HeaderCID.Reward).To(Equal(mocks.MockCIDPayload.HeaderCID.Reward)) Expect(cidPayload.UncleCIDs).To(Equal(mocks.MockCIDPayload.UncleCIDs)) Expect(cidPayload.HeaderCID).To(Equal(mocks.MockCIDPayload.HeaderCID)) - Expect(len(cidPayload.TransactionCIDs)).To(Equal(2)) + Expect(len(cidPayload.TransactionCIDs)).To(Equal(3)) Expect(cidPayload.TransactionCIDs[0]).To(Equal(mocks.MockCIDPayload.TransactionCIDs[0])) Expect(cidPayload.TransactionCIDs[1]).To(Equal(mocks.MockCIDPayload.TransactionCIDs[1])) - Expect(len(cidPayload.ReceiptCIDs)).To(Equal(2)) + Expect(cidPayload.TransactionCIDs[2]).To(Equal(mocks.MockCIDPayload.TransactionCIDs[2])) + Expect(len(cidPayload.ReceiptCIDs)).To(Equal(3)) Expect(cidPayload.ReceiptCIDs[mocks.MockTransactions[0].Hash()]).To(Equal(mocks.MockCIDPayload.ReceiptCIDs[mocks.MockTransactions[0].Hash()])) Expect(cidPayload.ReceiptCIDs[mocks.MockTransactions[1].Hash()]).To(Equal(mocks.MockCIDPayload.ReceiptCIDs[mocks.MockTransactions[1].Hash()])) + Expect(cidPayload.ReceiptCIDs[mocks.MockTransactions[2].Hash()]).To(Equal(mocks.MockCIDPayload.ReceiptCIDs[mocks.MockTransactions[2].Hash()])) Expect(len(cidPayload.StateNodeCIDs)).To(Equal(2)) Expect(cidPayload.StateNodeCIDs[0]).To(Equal(mocks.MockCIDPayload.StateNodeCIDs[0])) Expect(cidPayload.StateNodeCIDs[1]).To(Equal(mocks.MockCIDPayload.StateNodeCIDs[1])) diff --git a/pkg/super_node/eth/retriever.go b/pkg/super_node/eth/retriever.go index 125b0769..c537a808 100644 --- a/pkg/super_node/eth/retriever.go +++ b/pkg/super_node/eth/retriever.go @@ -216,17 +216,17 @@ func (ecr *CIDRetriever) RetrieveRctCIDsByHeaderID(tx *sqlx.Tx, rctFilter Receip args := make([]interface{}, 0, 4) pgStr := `SELECT receipt_cids.id, receipt_cids.tx_id, receipt_cids.cid, receipt_cids.contract, receipt_cids.topic0s, receipt_cids.topic1s, - receipt_cids.topic2s, receipt_cids.topic3s + receipt_cids.topic2s, receipt_cids.topic3s, receipt_cids.log_contracts FROM eth.receipt_cids, eth.transaction_cids, eth.header_cids WHERE receipt_cids.tx_id = transaction_cids.id AND transaction_cids.header_id = header_cids.id AND header_cids.id = $1` id := 2 args = append(args, headerID) - if len(rctFilter.Contracts) > 0 { - // Filter on contract addresses if there are any - pgStr += fmt.Sprintf(` AND ((receipt_cids.contract = ANY($%d::VARCHAR(66)[])`, id) - args = append(args, pq.Array(rctFilter.Contracts)) + if len(rctFilter.LogAddresses) > 0 { + // Filter on log contract addresses if there are any + pgStr += fmt.Sprintf(` AND ((receipt_cids.log_contracts && $%d::VARCHAR(66)[]`, id) + args = append(args, pq.Array(rctFilter.LogAddresses)) id++ // Filter on topics if there are any if hasTopics(rctFilter.Topics) { @@ -296,7 +296,7 @@ func (ecr *CIDRetriever) RetrieveRctCIDs(tx *sqlx.Tx, rctFilter ReceiptFilter, b args := make([]interface{}, 0, 5) pgStr := `SELECT receipt_cids.id, receipt_cids.tx_id, receipt_cids.cid, receipt_cids.contract, receipt_cids.topic0s, receipt_cids.topic1s, - receipt_cids.topic2s, receipt_cids.topic3s + receipt_cids.topic2s, receipt_cids.topic3s, receipt_cids.log_contracts FROM eth.receipt_cids, eth.transaction_cids, eth.header_cids WHERE receipt_cids.tx_id = transaction_cids.id AND transaction_cids.header_id = header_cids.id` @@ -311,10 +311,10 @@ func (ecr *CIDRetriever) RetrieveRctCIDs(tx *sqlx.Tx, rctFilter ReceiptFilter, b args = append(args, blockHash.String()) id++ } - if len(rctFilter.Contracts) > 0 { - // Filter on contract addresses if there are any - pgStr += fmt.Sprintf(` AND ((receipt_cids.contract = ANY($%d::VARCHAR(66)[])`, id) - args = append(args, pq.Array(rctFilter.Contracts)) + if len(rctFilter.LogAddresses) > 0 { + // Filter on log contract addresses if there are any + pgStr += fmt.Sprintf(` AND ((receipt_cids.log_contracts && $%d::VARCHAR(66)[]`, id) + args = append(args, pq.Array(rctFilter.LogAddresses)) id++ // Filter on topics if there are any if hasTopics(rctFilter.Topics) { @@ -588,7 +588,7 @@ func (ecr *CIDRetriever) RetrieveReceiptCIDsByTxIDs(tx *sqlx.Tx, txIDs []int64) log.Debugf("retrieving receipt cids for tx ids %v", txIDs) pgStr := `SELECT receipt_cids.id, receipt_cids.tx_id, receipt_cids.cid, receipt_cids.contract, receipt_cids.topic0s, receipt_cids.topic1s, - receipt_cids.topic2s, receipt_cids.topic3s + receipt_cids.topic2s, receipt_cids.topic3s, receipt_cids.log_contracts FROM eth.receipt_cids, eth.transaction_cids WHERE tx_id = ANY($1::INTEGER[]) AND receipt_cids.tx_id = transaction_cids.id diff --git a/pkg/super_node/eth/retriever_test.go b/pkg/super_node/eth/retriever_test.go index d2de0e46..c822f344 100644 --- a/pkg/super_node/eth/retriever_test.go +++ b/pkg/super_node/eth/retriever_test.go @@ -41,7 +41,7 @@ var ( StateFilter: eth.StateFilter{}, StorageFilter: eth.StorageFilter{}, } - rctContractFilter = ð.SubscriptionSettings{ + rctAddressFilter = ð.SubscriptionSettings{ Start: big.NewInt(0), End: big.NewInt(1), HeaderFilter: eth.HeaderFilter{ @@ -51,7 +51,7 @@ var ( Off: true, }, ReceiptFilter: eth.ReceiptFilter{ - Contracts: []string{mocks.AnotherAddress.String()}, + LogAddresses: []string{mocks.Address.String()}, }, StateFilter: eth.StateFilter{ Off: true, @@ -79,7 +79,7 @@ var ( Off: true, }, } - rctTopicsAndContractFilter = ð.SubscriptionSettings{ + rctTopicsAndAddressFilter = ð.SubscriptionSettings{ Start: big.NewInt(0), End: big.NewInt(1), HeaderFilter: eth.HeaderFilter{ @@ -93,7 +93,7 @@ var ( {"0x0000000000000000000000000000000000000000000000000000000000000004"}, {"0x0000000000000000000000000000000000000000000000000000000000000006"}, }, - Contracts: []string{mocks.Address.String()}, + LogAddresses: []string{mocks.Address.String()}, }, StateFilter: eth.StateFilter{ Off: true, @@ -102,7 +102,7 @@ var ( Off: true, }, } - rctTopicsAndContractFilterFail = ð.SubscriptionSettings{ + rctTopicsAndAddressFilterFail = ð.SubscriptionSettings{ Start: big.NewInt(0), End: big.NewInt(1), HeaderFilter: eth.HeaderFilter{ @@ -116,7 +116,7 @@ var ( {"0x0000000000000000000000000000000000000000000000000000000000000004"}, {"0x0000000000000000000000000000000000000000000000000000000000000007"}, // This topic won't match on the mocks.Address.String() contract receipt }, - Contracts: []string{mocks.Address.String()}, + LogAddresses: []string{mocks.Address.String()}, }, StateFilter: eth.StateFilter{ Off: true, @@ -125,7 +125,7 @@ var ( Off: true, }, } - rctContractsAndTopicFilter = ð.SubscriptionSettings{ + rctAddressesAndTopicFilter = ð.SubscriptionSettings{ Start: big.NewInt(0), End: big.NewInt(1), HeaderFilter: eth.HeaderFilter{ @@ -135,8 +135,8 @@ var ( Off: true, }, ReceiptFilter: eth.ReceiptFilter{ - Topics: [][]string{{"0x0000000000000000000000000000000000000000000000000000000000000005"}}, - Contracts: []string{mocks.Address.String(), mocks.AnotherAddress.String()}, + Topics: [][]string{{"0x0000000000000000000000000000000000000000000000000000000000000005"}}, + LogAddresses: []string{mocks.Address.String(), mocks.AnotherAddress.String()}, }, StateFilter: eth.StateFilter{ Off: true, @@ -153,9 +153,9 @@ var ( }, TxFilter: eth.TxFilter{}, // Trx filter open so we will collect all trxs, therefore we will also collect all corresponding rcts despite rct filter ReceiptFilter: eth.ReceiptFilter{ - MatchTxs: true, - Topics: [][]string{{"0x0000000000000000000000000000000000000000000000000000000000000006"}}, // Topic0 isn't one of the topic0s we have - Contracts: []string{"0x0000000000000000000000000000000000000002"}, // Contract isn't one of the contracts we have + MatchTxs: true, + Topics: [][]string{{"0x0000000000000000000000000000000000000000000000000000000000000006"}}, // Topic0 isn't one of the topic0s we have + LogAddresses: []string{"0x0000000000000000000000000000000000000002"}, // Contract isn't one of the contracts we have }, StateFilter: eth.StateFilter{ Off: true, @@ -174,9 +174,9 @@ var ( Dst: []string{mocks.AnotherAddress.String()}, // We only filter for one of the trxs so we will only get the one corresponding receipt }, ReceiptFilter: eth.ReceiptFilter{ - MatchTxs: true, - Topics: [][]string{{"0x0000000000000000000000000000000000000000000000000000000000000006"}}, // Topic0 isn't one of the topic0s we have - Contracts: []string{"0x0000000000000000000000000000000000000002"}, // Contract isn't one of the contracts we have + MatchTxs: true, + Topics: [][]string{{"0x0000000000000000000000000000000000000000000000000000000000000006"}}, // Topic0 isn't one of the topic0s we have + LogAddresses: []string{"0x0000000000000000000000000000000000000002"}, // Contract isn't one of the contracts we have }, StateFilter: eth.StateFilter{ Off: true, @@ -240,12 +240,14 @@ var _ = Describe("Retriever", func() { expectedHeaderCID.ID = cidWrapper.Header.ID expectedHeaderCID.NodeID = cidWrapper.Header.NodeID Expect(cidWrapper.Header).To(Equal(expectedHeaderCID)) - Expect(len(cidWrapper.Transactions)).To(Equal(2)) + Expect(len(cidWrapper.Transactions)).To(Equal(3)) Expect(eth.TxModelsContainsCID(cidWrapper.Transactions, mocks.MockCIDWrapper.Transactions[0].CID)).To(BeTrue()) Expect(eth.TxModelsContainsCID(cidWrapper.Transactions, mocks.MockCIDWrapper.Transactions[1].CID)).To(BeTrue()) - Expect(len(cidWrapper.Receipts)).To(Equal(2)) + Expect(eth.TxModelsContainsCID(cidWrapper.Transactions, mocks.MockCIDWrapper.Transactions[2].CID)).To(BeTrue()) + Expect(len(cidWrapper.Receipts)).To(Equal(3)) Expect(eth.ReceiptModelsContainsCID(cidWrapper.Receipts, mocks.MockCIDWrapper.Receipts[0].CID)).To(BeTrue()) Expect(eth.ReceiptModelsContainsCID(cidWrapper.Receipts, mocks.MockCIDWrapper.Receipts[1].CID)).To(BeTrue()) + Expect(eth.ReceiptModelsContainsCID(cidWrapper.Receipts, mocks.MockCIDWrapper.Receipts[2].CID)).To(BeTrue()) Expect(len(cidWrapper.StateNodes)).To(Equal(2)) for _, stateNode := range cidWrapper.StateNodes { if stateNode.CID == mocks.State1CID.String() { @@ -267,7 +269,7 @@ var _ = Describe("Retriever", func() { }) It("Applies filters from the provided config.Subscription", func() { - cids1, empty, err := retriever.Retrieve(rctContractFilter, 1) + cids1, empty, err := retriever.Retrieve(rctAddressFilter, 1) Expect(err).ToNot(HaveOccurred()) Expect(empty).ToNot(BeTrue()) Expect(len(cids1)).To(Equal(1)) @@ -279,7 +281,7 @@ var _ = Describe("Retriever", func() { Expect(len(cidWrapper1.StateNodes)).To(Equal(0)) Expect(len(cidWrapper1.StorageNodes)).To(Equal(0)) Expect(len(cidWrapper1.Receipts)).To(Equal(1)) - expectedReceiptCID := mocks.MockCIDWrapper.Receipts[1] + expectedReceiptCID := mocks.MockCIDWrapper.Receipts[0] expectedReceiptCID.ID = cidWrapper1.Receipts[0].ID expectedReceiptCID.TxID = cidWrapper1.Receipts[0].TxID Expect(cidWrapper1.Receipts[0]).To(Equal(expectedReceiptCID)) @@ -301,7 +303,7 @@ var _ = Describe("Retriever", func() { expectedReceiptCID.TxID = cidWrapper2.Receipts[0].TxID Expect(cidWrapper2.Receipts[0]).To(Equal(expectedReceiptCID)) - cids3, empty, err := retriever.Retrieve(rctTopicsAndContractFilter, 1) + cids3, empty, err := retriever.Retrieve(rctTopicsAndAddressFilter, 1) Expect(err).ToNot(HaveOccurred()) Expect(empty).ToNot(BeTrue()) Expect(len(cids3)).To(Equal(1)) @@ -318,7 +320,7 @@ var _ = Describe("Retriever", func() { expectedReceiptCID.TxID = cidWrapper3.Receipts[0].TxID Expect(cidWrapper3.Receipts[0]).To(Equal(expectedReceiptCID)) - cids4, empty, err := retriever.Retrieve(rctContractsAndTopicFilter, 1) + cids4, empty, err := retriever.Retrieve(rctAddressesAndTopicFilter, 1) Expect(err).ToNot(HaveOccurred()) Expect(empty).ToNot(BeTrue()) Expect(len(cids4)).To(Equal(1)) @@ -343,14 +345,16 @@ var _ = Describe("Retriever", func() { Expect(ok).To(BeTrue()) Expect(cidWrapper5.BlockNumber).To(Equal(mocks.MockCIDWrapper.BlockNumber)) Expect(cidWrapper5.Header).To(Equal(eth.HeaderModel{})) - Expect(len(cidWrapper5.Transactions)).To(Equal(2)) + Expect(len(cidWrapper5.Transactions)).To(Equal(3)) Expect(eth.TxModelsContainsCID(cidWrapper5.Transactions, mocks.Trx1CID.String())).To(BeTrue()) Expect(eth.TxModelsContainsCID(cidWrapper5.Transactions, mocks.Trx2CID.String())).To(BeTrue()) + Expect(eth.TxModelsContainsCID(cidWrapper5.Transactions, mocks.Trx3CID.String())).To(BeTrue()) Expect(len(cidWrapper5.StateNodes)).To(Equal(0)) Expect(len(cidWrapper5.StorageNodes)).To(Equal(0)) - Expect(len(cidWrapper5.Receipts)).To(Equal(2)) + Expect(len(cidWrapper5.Receipts)).To(Equal(3)) Expect(eth.ReceiptModelsContainsCID(cidWrapper5.Receipts, mocks.Rct1CID.String())).To(BeTrue()) Expect(eth.ReceiptModelsContainsCID(cidWrapper5.Receipts, mocks.Rct2CID.String())).To(BeTrue()) + Expect(eth.ReceiptModelsContainsCID(cidWrapper5.Receipts, mocks.Rct3CID.String())).To(BeTrue()) cids6, empty, err := retriever.Retrieve(rctsForSelectCollectedTrxs, 1) Expect(err).ToNot(HaveOccurred()) @@ -394,7 +398,7 @@ var _ = Describe("Retriever", func() { Path: []byte{'\x0c'}, })) - _, empty, err = retriever.Retrieve(rctTopicsAndContractFilterFail, 1) + _, empty, err = retriever.Retrieve(rctTopicsAndAddressFilterFail, 1) Expect(err).ToNot(HaveOccurred()) Expect(empty).To(BeTrue()) }) diff --git a/pkg/super_node/eth/subscription_config.go b/pkg/super_node/eth/subscription_config.go index a6d563e6..d8ac70d0 100644 --- a/pkg/super_node/eth/subscription_config.go +++ b/pkg/super_node/eth/subscription_config.go @@ -54,9 +54,9 @@ type TxFilter struct { type ReceiptFilter struct { Off bool // TODO: change this so that we filter for receipts first and we always return the corresponding transaction - MatchTxs bool // turn on to retrieve receipts that pair with retrieved transactions - Contracts []string - Topics [][]string + MatchTxs bool // turn on to retrieve receipts that pair with retrieved transactions + LogAddresses []string // receipt contains logs from the provided addresses + Topics [][]string } // StateFilter contains filter settings for state @@ -103,10 +103,10 @@ func NewEthSubscriptionConfig() (*SubscriptionSettings, error) { topics[2] = viper.GetStringSlice("superNode.ethSubscription.receiptFilter.topic2s") topics[3] = viper.GetStringSlice("superNode.ethSubscription.receiptFilter.topic3s") sc.ReceiptFilter = ReceiptFilter{ - Off: viper.GetBool("superNode.ethSubscription.receiptFilter.off"), - MatchTxs: viper.GetBool("superNode.ethSubscription.receiptFilter.matchTxs"), - Contracts: viper.GetStringSlice("superNode.ethSubscription.receiptFilter.contracts"), - Topics: topics, + Off: viper.GetBool("superNode.ethSubscription.receiptFilter.off"), + MatchTxs: viper.GetBool("superNode.ethSubscription.receiptFilter.matchTxs"), + LogAddresses: viper.GetStringSlice("superNode.ethSubscription.receiptFilter.contracts"), + Topics: topics, } // Below defaults to two false, and a slice of length 0 // Which means we get all state leafs by default, but no intermediate nodes diff --git a/pkg/super_node/shared/functions.go b/pkg/super_node/shared/functions.go index efb1cd64..ef338fb7 100644 --- a/pkg/super_node/shared/functions.go +++ b/pkg/super_node/shared/functions.go @@ -20,6 +20,7 @@ import ( "bytes" "github.com/ethereum/go-ethereum/common" + "github.com/vulcanize/vulcanizedb/pkg/ipfs" ) @@ -53,10 +54,18 @@ func ListContainsGap(gapList []Gap, gap Gap) bool { return false } -// HandleNullAddr converts a nil pointer to an address to a zero-valued hex address string -func HandleNullAddr(to *common.Address) string { +// HandleNullAddrPointer will return an emtpy string for a nil address pointer +func HandleNullAddrPointer(to *common.Address) string { if to == nil { - return "0x0000000000000000000000000000000000000000000000000000000000000000" + return "" + } + return to.Hex() +} + +// HandleNullAddr will return an empty string for a a null address +func HandleNullAddr(to common.Address) string { + if to.Hex() == "0x0000000000000000000000000000000000000000" { + return "" } return to.Hex() } diff --git a/pkg/watcher/eth/converter.go b/pkg/watcher/eth/converter.go index 98e4702b..6e290d82 100644 --- a/pkg/watcher/eth/converter.go +++ b/pkg/watcher/eth/converter.go @@ -94,8 +94,8 @@ func (pc *WatcherConverter) Convert(ethIPLDs eth.IPLDs) (*eth.CIDPayload, error) } // Tx data cids.TransactionCIDs[i] = eth.TxModel{ - Dst: shared.HandleNullAddr(tx.To()), - Src: shared.HandleNullAddr(&from), + Dst: shared.HandleNullAddrPointer(tx.To()), + Src: shared.HandleNullAddr(from), TxHash: tx.Hash().String(), Index: int64(i), CID: txIPLD.CID, @@ -115,25 +115,27 @@ func (pc *WatcherConverter) Convert(ethIPLDs eth.IPLDs) (*eth.CIDPayload, error) } for i, receipt := range receipts { matchedTx := transactions[i] - if matchedTx.To() != nil { - receipt.ContractAddress = *transactions[i].To() - } topicSets := make([][]string, 4) + mappedContracts := make(map[string]bool) // use map to avoid duplicate addresses for _, log := range receipt.Logs { - for i := range topicSets { - if i < len(log.Topics) { - topicSets[i] = append(topicSets[i], log.Topics[i].Hex()) - } + for i, topic := range log.Topics { + topicSets[i] = append(topicSets[i], topic.Hex()) } + mappedContracts[log.Address.String()] = true + } + logContracts := make([]string, 0, len(mappedContracts)) + for addr := range mappedContracts { + logContracts = append(logContracts, addr) } // Rct data cids.ReceiptCIDs[matchedTx.Hash()] = eth.ReceiptModel{ - CID: ethIPLDs.Receipts[i].CID, - Topic0s: topicSets[0], - Topic1s: topicSets[1], - Topic2s: topicSets[2], - Topic3s: topicSets[3], - Contract: receipt.ContractAddress.Hex(), + CID: ethIPLDs.Receipts[i].CID, + Topic0s: topicSets[0], + Topic1s: topicSets[1], + Topic2s: topicSets[2], + Topic3s: topicSets[3], + Contract: receipt.ContractAddress.Hex(), + LogContracts: logContracts, } } minerReward := common2.CalcEthBlockReward(&header, uncles, transactions, receipts)