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"
"fmt"
"math/big"
"strconv"
emintcrypto "github.com/cosmos/ethermint/crypto"
emintkeys "github.com/cosmos/ethermint/keys"
@ -18,6 +19,7 @@ import (
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rlp"
"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(
header tmtypes.Header, size int, gasLimit int64,
gasUsed *big.Int, transactions []interface{},
gasUsed *big.Int, transactions []interface{}, bloom ethtypes.Bloom,
) map[string]interface{} {
return map[string]interface{}{
"number": hexutil.Uint64(header.Height),
@ -406,7 +416,7 @@ func formatBlock(
"parentHash": hexutil.Bytes(header.LastBlockID.Hash),
"nonce": nil, // PoW specific
"sha3Uncles": nil, // No uncles in Tendermint
"logsBloom": "", // TODO: Complete with #55
"logsBloom": bloom,
"transactionsRoot": hexutil.Bytes(header.DataHash),
"stateRoot": hexutil.Bytes(header.AppHash),
"miner": hexutil.Bytes(header.ValidatorsHash),
@ -584,6 +594,17 @@ func (e *PublicEthAPI) GetTransactionReceipt(hash common.Hash) (map[string]inter
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{}{
"blockHash": blockHash,
"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),
"cumulativeGasUsed": nil, // ignore until needed
"contractAddress": nil,
"logs": nil, // TODO: Do with #55 (eth_getLogs output)
"logsBloom": nil,
"logs": logs.Logs,
"logsBloom": bloomFilter,
"status": status,
}
if common.BytesToAddress(tx.TxResult.GetData()) != (common.Address{}) {
fields["contractAddress"] = hexutil.Bytes(tx.TxResult.GetData())
contractAddress := common.BytesToAddress(tx.TxResult.GetData()[:20])
if contractAddress != (common.Address{}) {
// TODO: change hard coded indexing of first 20 bytes
fields["contractAddress"] = contractAddress
}
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()
}
// 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{
Sender: sender,
AccountNonce: msg.Data.AccountNonce,
@ -57,21 +66,15 @@ func handleETHTxMsg(ctx sdk.Context, keeper Keeper, msg types.EthereumTxMsg) sdk
Payload: msg.Data.Payload,
Csdb: keeper.csdb,
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
keeper.csdb.Prepare(common.BytesToHash(txHash), common.Hash{}, keeper.txCount.get())
keeper.csdb.Prepare(ethHash, common.Hash{}, keeper.txCount.get())
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 {
@ -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.txCount.increment()
return st.TransitionCSDB(ctx)
res, _ := st.TransitionCSDB(ctx)
return res
}

View File

@ -24,6 +24,7 @@ type Keeper struct {
cdc *codec.Codec
blockKey sdk.StoreKey
txCount *count
bloom *big.Int
}
type count int
@ -48,6 +49,7 @@ func NewKeeper(ak auth.AccountKeeper, storageKey, codeKey sdk.StoreKey,
cdc: cdc,
blockKey: blockKey,
txCount: new(count),
bloom: big.NewInt(0),
}
}
@ -75,6 +77,31 @@ func (k *Keeper) GetBlockHashMapping(ctx sdk.Context, hash []byte) (height int64
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
// ----------------------------------------------------------------------------

View File

@ -14,6 +14,7 @@ import (
evmtypes "github.com/cosmos/ethermint/x/evm/types"
ethcmn "github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/stretchr/testify/require"
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, []byte{0x43, 0x32}, 8)
// Test block height mapping functionality
testBloom := ethtypes.BytesToBloom([]byte{0x1, 0x3})
ek.SetBlockBloomMapping(ctx, testBloom, 4)
// Get those state transitions
require.Equal(t, ek.GetBalance(ctx, address).Cmp(big.NewInt(5)), 0)
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, []byte{0x43, 0x32}), int64(8))
require.Equal(t, ek.GetBlockBloomMapping(ctx, 4), testBloom)
// commit stateDB
_, err = ek.Commit(ctx, false)
require.NoError(t, err, "failed to commit StateDB")

View File

@ -2,6 +2,7 @@ package evm
import (
"encoding/json"
"math/big"
"github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/codec"
@ -9,6 +10,7 @@ import (
"github.com/cosmos/cosmos-sdk/types/module"
"github.com/cosmos/ethermint/x/evm/client/cli"
"github.com/cosmos/ethermint/x/evm/types"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/gorilla/mux"
"github.com/spf13/cobra"
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
func (am AppModule) BeginBlock(ctx sdk.Context, bl abci.RequestBeginBlock) {
// 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.bloom = big.NewInt(0)
am.keeper.txCount.reset()
}

View File

@ -1,6 +1,8 @@
package evm
import (
"strconv"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/ethermint/utils"
@ -20,6 +22,8 @@ const (
QueryCode = "code"
QueryNonce = "nonce"
QueryHashToHeight = "hashToHeight"
QueryTxLogs = "txLogs"
QueryLogsBloom = "logsBloom"
)
// NewQuerier is the module level router for state queries
@ -40,6 +44,10 @@ func NewQuerier(keeper Keeper) sdk.Querier {
return queryNonce(ctx, path, keeper)
case QueryHashToHeight:
return queryHashToHeight(ctx, path, keeper)
case QueryTxLogs:
return queryTxLogs(ctx, path, keeper)
case QueryLogsBloom:
return queryBlockLogsBloom(ctx, path, keeper)
default:
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
}
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"
"testing"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/ethermint/crypto"
"github.com/cosmos/ethermint/utils"
"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/stretchr/testify/require"
)
@ -174,3 +177,38 @@ func TestMarshalAndUnmarshalData(t *testing.T) {
require.NoError(t, err)
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
import (
"fmt"
ethtypes "github.com/ethereum/go-ethereum/core/types"
)
// QueryResProtocolVersion is response type for protocol version query
type QueryResProtocolVersion struct {
Version string `json:"version"`
@ -53,3 +59,21 @@ type QueryResNonce struct {
func (q QueryResNonce) String() string {
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/core"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
sdk "github.com/cosmos/cosmos-sdk/types"
@ -23,10 +24,11 @@ type StateTransition struct {
Payload []byte
Csdb *CommitStateDB
ChainID *big.Int
THash *common.Hash
}
// 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
// Create context for evm
@ -61,7 +63,7 @@ func (st StateTransition) TransitionCSDB(ctx sdk.Context) sdk.Result {
// handle errors
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)
@ -74,7 +76,19 @@ func (st StateTransition) TransitionCSDB(ctx sdk.Context) sdk.Result {
// 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(