forked from cerc-io/laconicd-deprecated
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:
parent
8bb8b40b32
commit
eab81bc578
@ -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
|
||||
|
@ -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: ðHash,
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
@ -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
|
||||
// ----------------------------------------------------------------------------
|
||||
|
@ -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")
|
||||
|
@ -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()
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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())
|
||||
}
|
||||
|
@ -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(
|
||||
|
Loading…
Reference in New Issue
Block a user