block and tx logs bloom and tx receipt logs (#119)

* Add bloom filter to tx receipt

* wip tx receipt logs to be tested

* Added Bloom - Height Mapping
* keeper.go sets <height:bloombits>
* keeper.go gets <height> --> bloombits

* updating and querying bloom by block (to be tested with real logs)

* fix bug with contract address return

* added error handling instead of logging
This commit is contained in:
Austin Abell 2019-10-04 15:32:56 -04:00 committed by GitHub
parent 8bb8b40b32
commit eab81bc578
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 202 additions and 22 deletions

View File

@ -4,6 +4,7 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"math/big" "math/big"
"strconv"
emintcrypto "github.com/cosmos/ethermint/crypto" emintcrypto "github.com/cosmos/ethermint/crypto"
emintkeys "github.com/cosmos/ethermint/keys" emintkeys "github.com/cosmos/ethermint/keys"
@ -18,6 +19,7 @@ import (
"github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/context"
@ -393,12 +395,20 @@ func (e *PublicEthAPI) getEthBlockByNumber(value int64, fullTx bool) (map[string
} }
} }
return formatBlock(header, block.Block.Size(), gasLimit, gasUsed, transactions), nil res, _, err := e.cliCtx.Query(fmt.Sprintf("custom/%s/%s/%s", types.ModuleName, evm.QueryLogsBloom, strconv.FormatInt(block.Block.Height, 10)))
if err != nil {
return nil, err
}
var out types.QueryBloomFilter
e.cliCtx.Codec.MustUnmarshalJSON(res, &out)
return formatBlock(header, block.Block.Size(), gasLimit, gasUsed, transactions, out.Bloom), nil
} }
func formatBlock( func formatBlock(
header tmtypes.Header, size int, gasLimit int64, header tmtypes.Header, size int, gasLimit int64,
gasUsed *big.Int, transactions []interface{}, gasUsed *big.Int, transactions []interface{}, bloom ethtypes.Bloom,
) map[string]interface{} { ) map[string]interface{} {
return map[string]interface{}{ return map[string]interface{}{
"number": hexutil.Uint64(header.Height), "number": hexutil.Uint64(header.Height),
@ -406,7 +416,7 @@ func formatBlock(
"parentHash": hexutil.Bytes(header.LastBlockID.Hash), "parentHash": hexutil.Bytes(header.LastBlockID.Hash),
"nonce": nil, // PoW specific "nonce": nil, // PoW specific
"sha3Uncles": nil, // No uncles in Tendermint "sha3Uncles": nil, // No uncles in Tendermint
"logsBloom": "", // TODO: Complete with #55 "logsBloom": bloom,
"transactionsRoot": hexutil.Bytes(header.DataHash), "transactionsRoot": hexutil.Bytes(header.DataHash),
"stateRoot": hexutil.Bytes(header.AppHash), "stateRoot": hexutil.Bytes(header.AppHash),
"miner": hexutil.Bytes(header.ValidatorsHash), "miner": hexutil.Bytes(header.ValidatorsHash),
@ -584,6 +594,17 @@ func (e *PublicEthAPI) GetTransactionReceipt(hash common.Hash) (map[string]inter
status = hexutil.Uint(0) status = hexutil.Uint(0)
} }
res, _, err := e.cliCtx.Query(fmt.Sprintf("custom/%s/%s/%s", types.ModuleName, evm.QueryTxLogs, hash.Hex()))
if err != nil {
return nil, err
}
var logs types.QueryTxLogs
e.cliCtx.Codec.MustUnmarshalJSON(res, &logs)
// TODO: change hard coded indexing of bytes
bloomFilter := ethtypes.BytesToBloom(tx.TxResult.GetData()[20:])
fields := map[string]interface{}{ fields := map[string]interface{}{
"blockHash": blockHash, "blockHash": blockHash,
"blockNumber": hexutil.Uint64(tx.Height), "blockNumber": hexutil.Uint64(tx.Height),
@ -594,13 +615,15 @@ func (e *PublicEthAPI) GetTransactionReceipt(hash common.Hash) (map[string]inter
"gasUsed": hexutil.Uint64(tx.TxResult.GasUsed), "gasUsed": hexutil.Uint64(tx.TxResult.GasUsed),
"cumulativeGasUsed": nil, // ignore until needed "cumulativeGasUsed": nil, // ignore until needed
"contractAddress": nil, "contractAddress": nil,
"logs": nil, // TODO: Do with #55 (eth_getLogs output) "logs": logs.Logs,
"logsBloom": nil, "logsBloom": bloomFilter,
"status": status, "status": status,
} }
if common.BytesToAddress(tx.TxResult.GetData()) != (common.Address{}) { contractAddress := common.BytesToAddress(tx.TxResult.GetData()[:20])
fields["contractAddress"] = hexutil.Bytes(tx.TxResult.GetData()) if contractAddress != (common.Address{}) {
// TODO: change hard coded indexing of first 20 bytes
fields["contractAddress"] = contractAddress
} }
return fields, nil return fields, nil

View File

@ -47,6 +47,15 @@ func handleETHTxMsg(ctx sdk.Context, keeper Keeper, msg types.EthereumTxMsg) sdk
return emint.ErrInvalidSender(err.Error()).Result() return emint.ErrInvalidSender(err.Error()).Result()
} }
// Encode transaction by default Tx encoder
txEncoder := authutils.GetTxEncoder(types.ModuleCdc)
txBytes, err := txEncoder(msg)
if err != nil {
return sdk.ErrInternal(err.Error()).Result()
}
txHash := tm.Tx(txBytes).Hash()
ethHash := common.BytesToHash(txHash)
st := types.StateTransition{ st := types.StateTransition{
Sender: sender, Sender: sender,
AccountNonce: msg.Data.AccountNonce, AccountNonce: msg.Data.AccountNonce,
@ -57,21 +66,15 @@ func handleETHTxMsg(ctx sdk.Context, keeper Keeper, msg types.EthereumTxMsg) sdk
Payload: msg.Data.Payload, Payload: msg.Data.Payload,
Csdb: keeper.csdb, Csdb: keeper.csdb,
ChainID: intChainID, ChainID: intChainID,
THash: &ethHash,
} }
// Encode transaction by default Tx encoder
txEncoder := authutils.GetTxEncoder(types.ModuleCdc)
txBytes, err := txEncoder(msg)
if err != nil {
return sdk.ErrInternal(err.Error()).Result()
}
txHash := tm.Tx(txBytes).Hash()
// Prepare db for logs // Prepare db for logs
keeper.csdb.Prepare(common.BytesToHash(txHash), common.Hash{}, keeper.txCount.get()) keeper.csdb.Prepare(ethHash, common.Hash{}, keeper.txCount.get())
keeper.txCount.increment() keeper.txCount.increment()
return st.TransitionCSDB(ctx) res, bloom := st.TransitionCSDB(ctx)
keeper.bloom.Or(keeper.bloom, bloom)
return res
} }
func handleEmintMsg(ctx sdk.Context, keeper Keeper, msg types.EmintMsg) sdk.Result { func handleEmintMsg(ctx sdk.Context, keeper Keeper, msg types.EmintMsg) sdk.Result {
@ -105,5 +108,6 @@ func handleEmintMsg(ctx sdk.Context, keeper Keeper, msg types.EmintMsg) sdk.Resu
keeper.csdb.Prepare(common.Hash{}, common.Hash{}, keeper.txCount.get()) // Cannot provide tx hash keeper.csdb.Prepare(common.Hash{}, common.Hash{}, keeper.txCount.get()) // Cannot provide tx hash
keeper.txCount.increment() keeper.txCount.increment()
return st.TransitionCSDB(ctx) res, _ := st.TransitionCSDB(ctx)
return res
} }

View File

@ -24,6 +24,7 @@ type Keeper struct {
cdc *codec.Codec cdc *codec.Codec
blockKey sdk.StoreKey blockKey sdk.StoreKey
txCount *count txCount *count
bloom *big.Int
} }
type count int type count int
@ -48,6 +49,7 @@ func NewKeeper(ak auth.AccountKeeper, storageKey, codeKey sdk.StoreKey,
cdc: cdc, cdc: cdc,
blockKey: blockKey, blockKey: blockKey,
txCount: new(count), txCount: new(count),
bloom: big.NewInt(0),
} }
} }
@ -75,6 +77,31 @@ func (k *Keeper) GetBlockHashMapping(ctx sdk.Context, hash []byte) (height int64
return return
} }
// ----------------------------------------------------------------------------
// Block bloom bits mapping functions
// May be removed when using only as module (only required by rpc api)
// ----------------------------------------------------------------------------
// SetBlockBloomMapping sets the mapping from block height to bloom bits
func (k *Keeper) SetBlockBloomMapping(ctx sdk.Context, bloom ethtypes.Bloom, height int64) {
store := ctx.KVStore(k.blockKey)
heightHash := k.cdc.MustMarshalBinaryLengthPrefixed(height)
if !bytes.Equal(heightHash, []byte{}) {
store.Set(heightHash, bloom.Bytes())
}
}
// GetBlockBloomMapping gets bloombits from block height
func (k *Keeper) GetBlockBloomMapping(ctx sdk.Context, height int64) ethtypes.Bloom {
store := ctx.KVStore(k.blockKey)
heightHash := k.cdc.MustMarshalBinaryLengthPrefixed(height)
bloom := store.Get(heightHash)
if bytes.Equal(heightHash, []byte{}) {
panic(fmt.Errorf("block with bloombits %s not found", bloom))
}
return ethtypes.BytesToBloom(bloom)
}
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Genesis // Genesis
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------

View File

@ -14,6 +14,7 @@ import (
evmtypes "github.com/cosmos/ethermint/x/evm/types" evmtypes "github.com/cosmos/ethermint/x/evm/types"
ethcmn "github.com/ethereum/go-ethereum/common" ethcmn "github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types" abci "github.com/tendermint/tendermint/abci/types"
@ -84,6 +85,10 @@ func TestDBStorage(t *testing.T) {
ek.SetBlockHashMapping(ctx, ethcmn.FromHex("0x0d87a3a5f73140f46aac1bf419263e4e94e87c292f25007700ab7f2060e2af68"), 7) ek.SetBlockHashMapping(ctx, ethcmn.FromHex("0x0d87a3a5f73140f46aac1bf419263e4e94e87c292f25007700ab7f2060e2af68"), 7)
ek.SetBlockHashMapping(ctx, []byte{0x43, 0x32}, 8) ek.SetBlockHashMapping(ctx, []byte{0x43, 0x32}, 8)
// Test block height mapping functionality
testBloom := ethtypes.BytesToBloom([]byte{0x1, 0x3})
ek.SetBlockBloomMapping(ctx, testBloom, 4)
// Get those state transitions // Get those state transitions
require.Equal(t, ek.GetBalance(ctx, address).Cmp(big.NewInt(5)), 0) require.Equal(t, ek.GetBalance(ctx, address).Cmp(big.NewInt(5)), 0)
require.Equal(t, ek.GetNonce(ctx, address), uint64(4)) require.Equal(t, ek.GetNonce(ctx, address), uint64(4))
@ -93,6 +98,8 @@ func TestDBStorage(t *testing.T) {
require.Equal(t, ek.GetBlockHashMapping(ctx, ethcmn.FromHex("0x0d87a3a5f73140f46aac1bf419263e4e94e87c292f25007700ab7f2060e2af68")), int64(7)) require.Equal(t, ek.GetBlockHashMapping(ctx, ethcmn.FromHex("0x0d87a3a5f73140f46aac1bf419263e4e94e87c292f25007700ab7f2060e2af68")), int64(7))
require.Equal(t, ek.GetBlockHashMapping(ctx, []byte{0x43, 0x32}), int64(8)) require.Equal(t, ek.GetBlockHashMapping(ctx, []byte{0x43, 0x32}), int64(8))
require.Equal(t, ek.GetBlockBloomMapping(ctx, 4), testBloom)
// commit stateDB // commit stateDB
_, err = ek.Commit(ctx, false) _, err = ek.Commit(ctx, false)
require.NoError(t, err, "failed to commit StateDB") require.NoError(t, err, "failed to commit StateDB")

View File

@ -2,6 +2,7 @@ package evm
import ( import (
"encoding/json" "encoding/json"
"math/big"
"github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/codec"
@ -9,6 +10,7 @@ import (
"github.com/cosmos/cosmos-sdk/types/module" "github.com/cosmos/cosmos-sdk/types/module"
"github.com/cosmos/ethermint/x/evm/client/cli" "github.com/cosmos/ethermint/x/evm/client/cli"
"github.com/cosmos/ethermint/x/evm/types" "github.com/cosmos/ethermint/x/evm/types"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/spf13/cobra" "github.com/spf13/cobra"
abci "github.com/tendermint/tendermint/abci/types" abci "github.com/tendermint/tendermint/abci/types"
@ -106,7 +108,10 @@ func (am AppModule) NewQuerierHandler() sdk.Querier {
// BeginBlock function for module at start of each block // BeginBlock function for module at start of each block
func (am AppModule) BeginBlock(ctx sdk.Context, bl abci.RequestBeginBlock) { func (am AppModule) BeginBlock(ctx sdk.Context, bl abci.RequestBeginBlock) {
// Consider removing this when using evm as module without web3 API // Consider removing this when using evm as module without web3 API
bloom := ethtypes.BytesToBloom(am.keeper.bloom.Bytes())
am.keeper.SetBlockBloomMapping(ctx, bloom, bl.Header.GetHeight()-1)
am.keeper.SetBlockHashMapping(ctx, bl.Header.LastBlockId.GetHash(), bl.Header.GetHeight()-1) am.keeper.SetBlockHashMapping(ctx, bl.Header.LastBlockId.GetHash(), bl.Header.GetHeight()-1)
am.keeper.bloom = big.NewInt(0)
am.keeper.txCount.reset() am.keeper.txCount.reset()
} }

View File

@ -1,6 +1,8 @@
package evm package evm
import ( import (
"strconv"
"github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/ethermint/utils" "github.com/cosmos/ethermint/utils"
@ -20,6 +22,8 @@ const (
QueryCode = "code" QueryCode = "code"
QueryNonce = "nonce" QueryNonce = "nonce"
QueryHashToHeight = "hashToHeight" QueryHashToHeight = "hashToHeight"
QueryTxLogs = "txLogs"
QueryLogsBloom = "logsBloom"
) )
// NewQuerier is the module level router for state queries // NewQuerier is the module level router for state queries
@ -40,6 +44,10 @@ func NewQuerier(keeper Keeper) sdk.Querier {
return queryNonce(ctx, path, keeper) return queryNonce(ctx, path, keeper)
case QueryHashToHeight: case QueryHashToHeight:
return queryHashToHeight(ctx, path, keeper) return queryHashToHeight(ctx, path, keeper)
case QueryTxLogs:
return queryTxLogs(ctx, path, keeper)
case QueryLogsBloom:
return queryBlockLogsBloom(ctx, path, keeper)
default: default:
return nil, sdk.ErrUnknownRequest("unknown query endpoint") return nil, sdk.ErrUnknownRequest("unknown query endpoint")
} }
@ -129,3 +137,33 @@ func queryHashToHeight(ctx sdk.Context, path []string, keeper Keeper) ([]byte, s
return res, nil return res, nil
} }
func queryBlockLogsBloom(ctx sdk.Context, path []string, keeper Keeper) ([]byte, sdk.Error) {
num, err := strconv.ParseInt(path[1], 10, 64)
if err != nil {
panic("could not unmarshall block number: " + err.Error())
}
bloom := keeper.GetBlockBloomMapping(ctx, num)
bRes := types.QueryBloomFilter{Bloom: bloom}
res, err := codec.MarshalJSONIndent(keeper.cdc, bRes)
if err != nil {
panic("could not marshal result to JSON: " + err.Error())
}
return res, nil
}
func queryTxLogs(ctx sdk.Context, path []string, keeper Keeper) ([]byte, sdk.Error) {
txHash := ethcmn.HexToHash(path[1])
logs := keeper.GetLogs(ctx, txHash)
bRes := types.QueryTxLogs{Logs: logs}
res, err := codec.MarshalJSONIndent(keeper.cdc, bRes)
if err != nil {
panic("could not marshal result to JSON: " + err.Error())
}
return res, nil
}

View File

@ -6,9 +6,12 @@ import (
"math/big" "math/big"
"testing" "testing"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/ethermint/crypto" "github.com/cosmos/ethermint/crypto"
"github.com/cosmos/ethermint/utils" "github.com/cosmos/ethermint/utils"
"github.com/ethereum/go-ethereum/common"
ethcmn "github.com/ethereum/go-ethereum/common" ethcmn "github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -174,3 +177,38 @@ func TestMarshalAndUnmarshalData(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, e, *e2) require.Equal(t, e, *e2)
} }
func TestMarshalAndUnmarshalLogs(t *testing.T) {
var cdc = codec.New()
logs := []*ethtypes.Log{
{
Address: common.BytesToAddress([]byte{0x11}),
TxHash: common.HexToHash("0x01"),
// May need to find workaround since Topics is required to unmarshal from JSON
Topics: []common.Hash{},
Removed: true,
},
{Address: common.BytesToAddress([]byte{0x01, 0x11}), Topics: []common.Hash{}},
}
raw, err := codec.MarshalJSONIndent(cdc, logs)
require.NoError(t, err)
var logs2 []*ethtypes.Log
err = cdc.UnmarshalJSON(raw, &logs2)
require.NoError(t, err)
require.Len(t, logs2, 2)
require.Equal(t, logs[0].Address, logs2[0].Address)
require.Equal(t, logs[0].TxHash, logs2[0].TxHash)
require.True(t, logs[0].Removed)
emptyLogs := []*ethtypes.Log{}
raw, err = codec.MarshalJSONIndent(cdc, emptyLogs)
require.NoError(t, err)
err = cdc.UnmarshalJSON(raw, &logs2)
require.NoError(t, err)
}

View File

@ -1,5 +1,11 @@
package types package types
import (
"fmt"
ethtypes "github.com/ethereum/go-ethereum/core/types"
)
// QueryResProtocolVersion is response type for protocol version query // QueryResProtocolVersion is response type for protocol version query
type QueryResProtocolVersion struct { type QueryResProtocolVersion struct {
Version string `json:"version"` Version string `json:"version"`
@ -53,3 +59,21 @@ type QueryResNonce struct {
func (q QueryResNonce) String() string { func (q QueryResNonce) String() string {
return string(q.Nonce) return string(q.Nonce)
} }
// QueryTxLogs is response type for tx logs query
type QueryTxLogs struct {
Logs []*ethtypes.Log `json:"logs"`
}
func (q QueryTxLogs) String() string {
return string(fmt.Sprintf("%+v", q.Logs))
}
// QueryBloomFilter is response type for tx logs query
type QueryBloomFilter struct {
Bloom ethtypes.Bloom `json:"bloom"`
}
func (q QueryBloomFilter) String() string {
return string(q.Bloom.Bytes())
}

View File

@ -6,6 +6,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
@ -23,10 +24,11 @@ type StateTransition struct {
Payload []byte Payload []byte
Csdb *CommitStateDB Csdb *CommitStateDB
ChainID *big.Int ChainID *big.Int
THash *common.Hash
} }
// TransitionCSDB performs an evm state transition from a transaction // TransitionCSDB performs an evm state transition from a transaction
func (st StateTransition) TransitionCSDB(ctx sdk.Context) sdk.Result { func (st StateTransition) TransitionCSDB(ctx sdk.Context) (sdk.Result, *big.Int) {
contractCreation := st.Recipient == nil contractCreation := st.Recipient == nil
// Create context for evm // Create context for evm
@ -61,7 +63,7 @@ func (st StateTransition) TransitionCSDB(ctx sdk.Context) sdk.Result {
// handle errors // handle errors
if vmerr != nil { if vmerr != nil {
return emint.ErrVMExecution(vmerr.Error()).Result() return emint.ErrVMExecution(vmerr.Error()).Result(), nil
} }
// Refund remaining gas from tx (Check these values and ensure gas is being consumed correctly) // Refund remaining gas from tx (Check these values and ensure gas is being consumed correctly)
@ -74,7 +76,19 @@ func (st StateTransition) TransitionCSDB(ctx sdk.Context) sdk.Result {
// TODO: Consume gas from sender // TODO: Consume gas from sender
return sdk.Result{Data: addr.Bytes(), GasUsed: st.GasLimit - leftOverGas} // Generate bloom filter to be saved in tx receipt data
bloomInt := big.NewInt(0)
var bloomFilter ethtypes.Bloom
if st.THash != nil {
logs := st.Csdb.GetLogs(*st.THash)
bloomInt = ethtypes.LogsBloom(logs)
bloomFilter = ethtypes.BytesToBloom(bloomInt.Bytes())
}
// TODO: coniditionally add either/ both of these to return data
returnData := append(addr.Bytes(), bloomFilter.Bytes()...)
return sdk.Result{Data: returnData, GasUsed: st.GasLimit - leftOverGas}, bloomInt
} }
func refundGas( func refundGas(