From 61b0d317eefead3db1b9966380acf3b05dc2a6e7 Mon Sep 17 00:00:00 2001 From: Federico Kunze Date: Tue, 19 May 2020 18:06:29 -0400 Subject: [PATCH 01/36] rpc: event websocket subscription --- rpc/backend.go | 16 +- rpc/filter_api.go | 385 +++++++++++++++++++++++++++++++++++----- rpc/filter_system.go | 20 +++ rpc/filters.go | 67 ++----- x/evm/keeper/querier.go | 11 ++ 5 files changed, 400 insertions(+), 99 deletions(-) create mode 100644 rpc/filter_system.go diff --git a/rpc/backend.go b/rpc/backend.go index fafe92c72..93ae92f4d 100644 --- a/rpc/backend.go +++ b/rpc/backend.go @@ -19,6 +19,7 @@ import ( type Backend interface { // Used by block filter; also used for polling BlockNumber() (hexutil.Uint64, error) + // HeaderByNumber(blockNum BlockNumber) (ethtypes.Header, error) GetBlockByNumber(blockNum BlockNumber, fullTx bool) (map[string]interface{}, error) GetBlockByHash(hash common.Hash, fullTx bool) (map[string]interface{}, error) getEthBlockByNumber(height int64, fullTx bool) (map[string]interface{}, error) @@ -32,12 +33,13 @@ type Backend interface { // TODO: Bloom methods } -// EmintBackend implements Backend +// EthermintBackend implements the Backend interface type EthermintBackend struct { cliCtx context.CLIContext gasLimit int64 } +// NewEthermintBackend creates a new ethermint backend instance. func NewEthermintBackend(cliCtx context.CLIContext) *EthermintBackend { return &EthermintBackend{ cliCtx: cliCtx, @@ -45,6 +47,18 @@ func NewEthermintBackend(cliCtx context.CLIContext) *EthermintBackend { } } +// // HeaderByNumber returns the current block number. +// func (e *EthermintBackend) HeaderByNumber(blockNum BlockNumber) (ethtypes.Header, error) +// res, _, err := e.cliCtx.QueryWithData(fmt.Sprintf("custom/%s/blockNumber", types.ModuleName), nil) +// if err != nil { +// return hexutil.Uint64(0), err +// } + +// var out types.QueryResBlockNumber +// e.cliCtx.Codec.MustUnmarshalJSON(res, &out) +// return hexutil.Uint64(out.Number), nil +// } + // BlockNumber returns the current block number. func (e *EthermintBackend) BlockNumber() (hexutil.Uint64, error) { res, _, err := e.cliCtx.QueryWithData(fmt.Sprintf("custom/%s/blockNumber", types.ModuleName), nil) diff --git a/rpc/filter_api.go b/rpc/filter_api.go index 8a7711463..d4346a05d 100644 --- a/rpc/filter_api.go +++ b/rpc/filter_api.go @@ -1,79 +1,382 @@ package rpc import ( - "errors" + "context" + "fmt" + "sync" - "github.com/cosmos/cosmos-sdk/client/context" + abci "github.com/tendermint/tendermint/abci/types" + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth/filters" + "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/rpc" + + clientcontext "github.com/cosmos/cosmos-sdk/client/context" ) -// PublicFilterAPI is the eth_ prefixed set of APIs in the Web3 JSON-RPC spec. +// PublicFilterAPI offers support to create and manage filters. This will allow external clients to retrieve various +// information related to the Ethereum protocol such as blocks, transactions and logs. type PublicFilterAPI struct { - cliCtx context.CLIContext - backend Backend - filters map[rpc.ID]*Filter // ID to filter; TODO: change to sync.Map in case of concurrent writes + cliCtx clientcontext.CLIContext + backend Backend + mux *event.TypeMux + quit chan struct{} + events EventSystem + filtersMu sync.Mutex + filters map[rpc.ID]*Filter // ID to filter; TODO: change to sync.Map in case of concurrent writes } -// NewPublicEthAPI creates an instance of the public ETH Web3 API. -func NewPublicFilterAPI(cliCtx context.CLIContext, backend Backend) *PublicFilterAPI { +// NewPublicFilterAPI returns a new PublicFilterAPI instance. +func NewPublicFilterAPI(cliCtx clientcontext.CLIContext, backend Backend) *PublicFilterAPI { return &PublicFilterAPI{ cliCtx: cliCtx, backend: backend, filters: make(map[rpc.ID]*Filter), + events: nil, // TODO: create concrete type } + + // TODO: implement timeout loop } -// NewFilter instantiates a new filter. -func (e *PublicFilterAPI) NewFilter(criteria filters.FilterCriteria) rpc.ID { - id := rpc.NewID() - e.filters[id] = NewFilter(e.backend, &criteria) - return id +// NewPendingTransactionFilter creates a filter that fetches pending transaction hashes +// as transactions enter the pending state. +// +// It is part of the filter package because this filter can be used through the +// `eth_getFilterChanges` polling method that is also used for log filters. +// +// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newpendingtransactionfilter +func (api *PublicFilterAPI) NewPendingTransactionFilter() rpc.ID { + var ( + pendingTxs = make(chan []common.Hash) + pendingTxSub = api.events.SubscribePendingTxs(pendingTxs) + ) + + api.filtersMu.Lock() + api.filters[pendingTxSub.ID] = NewFilter(api.backend, &filters.FilterCriteria{}, filters.PendingTransactionsSubscription) + api.filtersMu.Unlock() + + go func() { + for { + select { + case ph := <-pendingTxs: + api.filtersMu.Lock() + if f, found := api.filters[pendingTxSub.ID]; found { + f.hashes = append(f.hashes, ph...) + } + api.filtersMu.Unlock() + case <-pendingTxSub.Err(): + api.filtersMu.Lock() + delete(api.filters, pendingTxSub.ID) + api.filtersMu.Unlock() + return + } + } + }() + + return pendingTxSub.ID } -// NewBlockFilter instantiates a new block filter. -func (e *PublicFilterAPI) NewBlockFilter() rpc.ID { - id := rpc.NewID() - e.filters[id] = NewBlockFilter(e.backend) - return id +// NewPendingTransactions creates a subscription that is triggered each time a transaction +// enters the transaction pool and was signed from one of the transactions this nodes manages. +func (api *PublicFilterAPI) NewPendingTransactions(ctx context.Context) (*rpc.Subscription, error) { + notifier, supported := rpc.NotifierFromContext(ctx) + if !supported { + return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported + } + + rpcSub := notifier.CreateSubscription() + + go func() { + txHashes := make(chan []common.Hash, 128) + pendingTxSub := api.events.SubscribePendingTxs(txHashes) + + for { + select { + case hashes := <-txHashes: + // To keep the original behaviour, send a single tx hash in one notification. + // TODO(rjl493456442) Send a batch of tx hashes in one notification + for _, h := range hashes { + notifier.Notify(rpcSub.ID, h) + } + case <-rpcSub.Err(): + pendingTxSub.Unsubscribe() + return + case <-notifier.Closed(): + pendingTxSub.Unsubscribe() + return + } + } + }() + + return rpcSub, nil } -// NewPendingTransactionFilter instantiates a new pending transaction filter. -func (e *PublicFilterAPI) NewPendingTransactionFilter() rpc.ID { - id := rpc.NewID() - e.filters[id] = NewPendingTransactionFilter(e.backend) - return id +// NewBlockFilter creates a filter that fetches blocks that are imported into the chain. +// It is part of the filter package since polling goes with eth_getFilterChanges. +// +// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newblockfilter +func (api *PublicFilterAPI) NewBlockFilter() rpc.ID { + var ( + headers = make(chan abci.Header) + headerSub = api.events.SubscribeNewHeads(headers) + ) + + api.filtersMu.Lock() + api.filters[headerSub.ID] = NewFilter(api.backend, &filters.FilterCriteria{}, filters.BlocksSubscription) + api.filtersMu.Unlock() + + go func() { + for { + select { + case h := <-headers: + api.filtersMu.Lock() + if f, found := api.filters[headerSub.ID]; found { + f.hashes = append(f.hashes, common.BytesToHash(h.AppHash)) + } + api.filtersMu.Unlock() + case <-headerSub.Err(): + api.filtersMu.Lock() + delete(api.filters, headerSub.ID) + api.filtersMu.Unlock() + return + } + } + }() + + return headerSub.ID } -// UninstallFilter uninstalls a filter with the given ID. -func (e *PublicFilterAPI) UninstallFilter(id rpc.ID) bool { - e.filters[id].uninstallFilter() - delete(e.filters, id) - return true +// NewHeads send a notification each time a new (header) block is appended to the chain. +func (api *PublicFilterAPI) NewHeads(ctx context.Context) (*rpc.Subscription, error) { + notifier, supported := rpc.NotifierFromContext(ctx) + if !supported { + return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported + } + + rpcSub := notifier.CreateSubscription() + + go func() { + headers := make(chan abci.Header) + headersSub := api.events.SubscribeNewHeads(headers) + + for { + select { + case h := <-headers: + notifier.Notify(rpcSub.ID, h) + case <-rpcSub.Err(): + headersSub.Unsubscribe() + return + case <-notifier.Closed(): + headersSub.Unsubscribe() + return + } + } + }() + + return rpcSub, nil } -// GetFilterChanges returns an array of changes since the last poll. -// If the filter is a log filter, it returns an array of Logs. -// If the filter is a block filter, it returns an array of block hashes. -// If the filter is a pending transaction filter, it returns an array of transaction hashes. -func (e *PublicFilterAPI) GetFilterChanges(id rpc.ID) (interface{}, error) { - if e.filters[id] == nil { - return nil, errors.New("invalid filter ID") +// Logs creates a subscription that fires for all new log that match the given filter criteria. +func (api *PublicFilterAPI) Logs(ctx context.Context, crit filters.FilterCriteria) (*rpc.Subscription, error) { + notifier, supported := rpc.NotifierFromContext(ctx) + if !supported { + return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported + } + + var ( + rpcSub = notifier.CreateSubscription() + matchedLogs = make(chan []*types.Log) + ) + + logsSub, err := api.events.SubscribeLogs(ethereum.FilterQuery(crit), matchedLogs) + if err != nil { + return nil, err } - return e.filters[id].getFilterChanges() + + go func() { + + for { + select { + case logs := <-matchedLogs: + for _, log := range logs { + notifier.Notify(rpcSub.ID, &log) + } + case <-rpcSub.Err(): // client send an unsubscribe request + logsSub.Unsubscribe() + return + case <-notifier.Closed(): // connection dropped + logsSub.Unsubscribe() + return + } + } + }() + + return rpcSub, nil } -// GetFilterLogs returns an array of all logs matching filter with given id. -func (e *PublicFilterAPI) GetFilterLogs(id rpc.ID) ([]*ethtypes.Log, error) { - return e.filters[id].getFilterLogs() +// NewFilter creates a new filter and returns the filter id. It can be +// used to retrieve logs when the state changes. This method cannot be +// used to fetch logs that are already stored in the state. +// +// Default criteria for the from and to block are "latest". +// Using "latest" as block number will return logs for mined blocks. +// Using "pending" as block number returns logs for not yet mined (pending) blocks. +// In case logs are removed (chain reorg) previously returned logs are returned +// again but with the removed property set to true. +// +// In case "fromBlock" > "toBlock" an error is returned. +// +// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newfilter +func (api *PublicFilterAPI) NewFilter(criteria filters.FilterCriteria) (rpc.ID, error) { + logs := make(chan []*types.Log) + logsSub, err := api.events.SubscribeLogs(ethereum.FilterQuery(criteria), logs) + if err != nil { + return rpc.ID(""), err + } + + api.filtersMu.Lock() + api.filters[logsSub.ID] = NewFilter(api.backend, &criteria, filters.LogsSubscription) + api.filtersMu.Unlock() + + go func() { + for { + select { + case l := <-logs: + api.filtersMu.Lock() + if f, found := api.filters[logsSub.ID]; found { + f.logs = append(f.logs, l...) + } + api.filtersMu.Unlock() + case <-logsSub.Err(): + api.filtersMu.Lock() + delete(api.filters, logsSub.ID) + api.filtersMu.Unlock() + return + } + } + }() + + return logsSub.ID, nil } // GetLogs returns logs matching the given argument that are stored within the state. // // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getlogs -func (e *PublicFilterAPI) GetLogs(criteria filters.FilterCriteria) ([]*ethtypes.Log, error) { - filter := NewFilter(e.backend, &criteria) +func (api *PublicFilterAPI) GetLogs(criteria filters.FilterCriteria) ([]*ethtypes.Log, error) { + filter := NewFilter(api.backend, &criteria, filters.LogsSubscription) return filter.getFilterLogs() + // var filter *Filter + // if crit.BlockHash != nil { + // // Block filter requested, construct a single-shot filter + // filter = NewBlockFilter(api.backend, *crit.BlockHash, crit.Addresses, crit.Topics) + // } else { + // // Convert the RPC block numbers into internal representations + // begin := rpc.LatestBlockNumber.Int64() + // if crit.FromBlock != nil { + // begin = crit.FromBlock.Int64() + // } + // end := rpc.LatestBlockNumber.Int64() + // if crit.ToBlock != nil { + // end = crit.ToBlock.Int64() + // } + // // Construct the range filter + // filter = NewRangeFilter(api.backend, begin, end, crit.Addresses, crit.Topics) + // } + // // Run the filter and return all the logs + // logs, err := filter.Logs(ctx) + // if err != nil { + // return nil, err + // } + // return returnLogs(logs), err +} + +// UninstallFilter removes the filter with the given filter id. +// +// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_uninstallfilter +func (api *PublicFilterAPI) UninstallFilter(id rpc.ID) bool { + api.filtersMu.Lock() + f, found := api.filters[id] + if found { + delete(api.filters, id) + } + api.filtersMu.Unlock() + if !found { + return false + } + + // TODO: f.s.Unsubscribe() + f.uninstallFilter() + return true +} + +// GetFilterLogs returns the logs for the filter with the given id. +// If the filter could not be found an empty array of logs is returned. +// +// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getfilterlogs +func (api *PublicFilterAPI) GetFilterLogs(id rpc.ID) ([]*ethtypes.Log, error) { + api.filtersMu.Lock() + f, found := api.filters[id] + api.filtersMu.Unlock() + + if !found { + return nil, fmt.Errorf("filter %s not found", id) + } + + if f.typ != filters.LogsSubscription { + return nil, fmt.Errorf("filter %s doesn't have a LogsSubscription type", id) + } + + return api.filters[id].getFilterLogs() +} + +// GetFilterChanges returns the logs for the filter with the given id since +// last time it was called. This can be used for polling. +// +// For pending transaction and block filters the result is []common.Hash. +// (pending)Log filters return []Log. +// +// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getfilterchanges +func (api *PublicFilterAPI) GetFilterChanges(id rpc.ID) (interface{}, error) { + api.filtersMu.Lock() + defer api.filtersMu.Unlock() + + f, found := api.filters[id] + if !found { + return nil, fmt.Errorf("filter %s not found", id) + } + + switch f.typ { + case filters.PendingTransactionsSubscription, filters.BlocksSubscription: + hashes := f.hashes + f.hashes = nil + return returnHashes(hashes), nil + case filters.LogsSubscription, filters.MinedAndPendingLogsSubscription: + logs := f.logs + f.logs = nil + return returnLogs(logs), nil + default: + return nil, fmt.Errorf("invalid filter %s type %d", id, f.typ) + } +} + +// returnHashes is a helper that will return an empty hash array case the given hash array is nil, +// otherwise the given hashes array is returned. +func returnHashes(hashes []common.Hash) []common.Hash { + if hashes == nil { + return []common.Hash{} + } + return hashes +} + +// returnLogs is a helper that will return an empty log array in case the given logs array is nil, +// otherwise the given logs array is returned. +func returnLogs(logs []*types.Log) []*types.Log { + if logs == nil { + return []*types.Log{} + } + return logs } diff --git a/rpc/filter_system.go b/rpc/filter_system.go new file mode 100644 index 000000000..81317b56c --- /dev/null +++ b/rpc/filter_system.go @@ -0,0 +1,20 @@ +package rpc + +import ( + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/eth/filters" + + abci "github.com/tendermint/tendermint/abci/types" +) + +// EventSystem creates subscriptions, processes events and broadcasts them to the +// subscription which match the subscription criteria. +type EventSystem interface { + SubscribeLogs(crit ethereum.FilterQuery, logs chan []*types.Log) (*filters.Subscription, error) + SubscribeNewHeads(headers chan abci.Header) *filters.Subscription + SubscribePendingTxs(hashes chan []common.Hash) *filters.Subscription +} + +// TODO: create concrete type diff --git a/rpc/filters.go b/rpc/filters.go index 4f71edc07..0651d1bc4 100644 --- a/rpc/filters.go +++ b/rpc/filters.go @@ -17,43 +17,25 @@ import ( Used to set the criteria passed in from RPC params */ -const blockFilter = "block" -const pendingTxFilter = "pending" -const logFilter = "log" - // Filter can be used to retrieve and filter logs, blocks, or pending transactions. type Filter struct { - backend Backend + backend Backend + fromBlock, toBlock *big.Int // start and end block numbers addresses []common.Address // contract addresses to watch topics [][]common.Hash // log topics to watch for blockHash *common.Hash // Block hash if filtering a single block - typ string + typ filters.Type // filter type hashes []common.Hash // filtered block or transaction hashes - logs []*ethtypes.Log //nolint // filtered logs + logs []*ethtypes.Log // filtered logs stopped bool // set to true once filter in uninstalled err error } // NewFilter returns a new Filter -func NewFilter(backend Backend, criteria *filters.FilterCriteria) *Filter { - filter := &Filter{ - backend: backend, - fromBlock: criteria.FromBlock, - toBlock: criteria.ToBlock, - addresses: criteria.Addresses, - topics: criteria.Topics, - typ: logFilter, - stopped: false, - } - - return filter -} - -// NewFilterWithBlockHash returns a new Filter with a blockHash. -func NewFilterWithBlockHash(backend Backend, criteria *filters.FilterCriteria) *Filter { +func NewFilter(backend Backend, criteria *filters.FilterCriteria, filterType filters.Type) *Filter { return &Filter{ backend: backend, fromBlock: criteria.FromBlock, @@ -61,25 +43,11 @@ func NewFilterWithBlockHash(backend Backend, criteria *filters.FilterCriteria) * addresses: criteria.Addresses, topics: criteria.Topics, blockHash: criteria.BlockHash, - typ: logFilter, + typ: filterType, + stopped: false, } } -// NewBlockFilter creates a new filter that notifies when a block arrives. -func NewBlockFilter(backend Backend) *Filter { - filter := NewFilter(backend, &filters.FilterCriteria{}) - filter.typ = blockFilter - - go func() { - err := filter.pollForBlocks() - if err != nil { - filter.err = err - } - }() - - return filter -} - func (f *Filter) pollForBlocks() error { prev := hexutil.Uint64(0) @@ -148,28 +116,13 @@ func contains(slice []common.Hash, item common.Hash) bool { return ok } -// NewPendingTransactionFilter creates a new filter that notifies when a pending transaction arrives. -func NewPendingTransactionFilter(backend Backend) *Filter { - filter := NewFilter(backend, &filters.FilterCriteria{}) - filter.typ = pendingTxFilter - - go func() { - err := filter.pollForTransactions() - if err != nil { - filter.err = err - } - }() - - return filter -} - func (f *Filter) uninstallFilter() { f.stopped = true } func (f *Filter) getFilterChanges() (interface{}, error) { switch f.typ { - case blockFilter: + case filters.BlocksSubscription: if f.err != nil { return nil, f.err } @@ -179,7 +132,7 @@ func (f *Filter) getFilterChanges() (interface{}, error) { f.hashes = []common.Hash{} return blocks, nil - case pendingTxFilter: + case filters.PendingTransactionsSubscription: if f.err != nil { return nil, f.err } @@ -188,7 +141,7 @@ func (f *Filter) getFilterChanges() (interface{}, error) { copy(txs, f.hashes) f.hashes = []common.Hash{} return txs, nil - case logFilter: + case filters.LogsSubscription: return f.getFilterLogs() } diff --git a/x/evm/keeper/querier.go b/x/evm/keeper/querier.go index 2fed9c26b..1367b5771 100644 --- a/x/evm/keeper/querier.go +++ b/x/evm/keeper/querier.go @@ -87,6 +87,17 @@ func queryBlockNumber(ctx sdk.Context, keeper Keeper) ([]byte, error) { return bz, nil } +// func queryHeaderByNumber(ctx sdk.Context, keeper Keeper) ([]byte, error) { +// num := ctx.BlockHeight() +// bnRes := types.QueryResBlockNumber{Number: num} +// bz, err := codec.MarshalJSONIndent(keeper.cdc, bnRes) +// if err != nil { +// return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error()) +// } + +// return bz, nil +// } + func queryStorage(ctx sdk.Context, path []string, keeper Keeper) ([]byte, error) { addr := ethcmn.HexToAddress(path[1]) key := ethcmn.HexToHash(path[2]) From aabb8ffabbe2c39556155e4c6f5b21739909118b Mon Sep 17 00:00:00 2001 From: Federico Kunze Date: Fri, 22 May 2020 13:52:13 -0400 Subject: [PATCH 02/36] rpc: use tendermint event subscriptions --- rpc/filter_api.go | 91 +++++++++++++++++++++++------------- rpc/filter_system.go | 105 +++++++++++++++++++++++++++++++++++++++--- x/evm/handler.go | 14 ++++++ x/evm/types/events.go | 2 + 4 files changed, 173 insertions(+), 39 deletions(-) diff --git a/rpc/filter_api.go b/rpc/filter_api.go index d4346a05d..174fb8ba0 100644 --- a/rpc/filter_api.go +++ b/rpc/filter_api.go @@ -5,7 +5,7 @@ import ( "fmt" "sync" - abci "github.com/tendermint/tendermint/abci/types" + tmtypes "github.com/tendermint/tendermint/types" ethereum "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" @@ -36,7 +36,10 @@ func NewPublicFilterAPI(cliCtx clientcontext.CLIContext, backend Backend) *Publi cliCtx: cliCtx, backend: backend, filters: make(map[rpc.ID]*Filter), - events: nil, // TODO: create concrete type + events: TendermintEvents{ + ctx: context.Background(), + client: cliCtx.Client, + }, } // TODO: implement timeout loop @@ -88,6 +91,7 @@ func (api *PublicFilterAPI) NewPendingTransactions(ctx context.Context) (*rpc.Su return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported } + api.events = api.events.WithContext(ctx) rpcSub := notifier.CreateSubscription() go func() { @@ -120,34 +124,39 @@ func (api *PublicFilterAPI) NewPendingTransactions(ctx context.Context) (*rpc.Su // // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newblockfilter func (api *PublicFilterAPI) NewBlockFilter() rpc.ID { - var ( - headers = make(chan abci.Header) - headerSub = api.events.SubscribeNewHeads(headers) - ) + subscriberID := rpc.NewID() + eventCh, err := api.events.SubscribeNewHeads(subscriberID) + if err != nil { + // return an empty id + return rpc.ID("") + } api.filtersMu.Lock() - api.filters[headerSub.ID] = NewFilter(api.backend, &filters.FilterCriteria{}, filters.BlocksSubscription) + api.filters[subscriberID] = NewFilter(api.backend, &filters.FilterCriteria{}, filters.BlocksSubscription) api.filtersMu.Unlock() go func() { for { select { - case h := <-headers: - api.filtersMu.Lock() - if f, found := api.filters[headerSub.ID]; found { - f.hashes = append(f.hashes, common.BytesToHash(h.AppHash)) + case event := <-eventCh: + evHeader, ok := event.Data.(tmtypes.EventDataNewBlockHeader) + if !ok { + // remove filter from map + api.filtersMu.Lock() + delete(api.filters, subscriberID) + api.filtersMu.Unlock() + return } - api.filtersMu.Unlock() - case <-headerSub.Err(): api.filtersMu.Lock() - delete(api.filters, headerSub.ID) + if f, found := api.filters[subscriberID]; found { + f.hashes = append(f.hashes, common.BytesToHash(evHeader.Header.Hash())) + } api.filtersMu.Unlock() - return } } }() - return headerSub.ID + return subscriberID } // NewHeads send a notification each time a new (header) block is appended to the chain. @@ -157,27 +166,35 @@ func (api *PublicFilterAPI) NewHeads(ctx context.Context) (*rpc.Subscription, er return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported } + api.events = api.events.WithContext(ctx) rpcSub := notifier.CreateSubscription() + var err error go func() { - headers := make(chan abci.Header) - headersSub := api.events.SubscribeNewHeads(headers) + eventCh, err := api.events.SubscribeNewHeads(rpcSub.ID) + if err != nil { + return + } for { select { - case h := <-headers: - notifier.Notify(rpcSub.ID, h) + case event := <-eventCh: + evHeader, ok := event.Data.(tmtypes.EventDataNewBlockHeader) + if !ok { + return + } + notifier.Notify(rpcSub.ID, evHeader.Header) case <-rpcSub.Err(): - headersSub.Unsubscribe() + err = api.events.UnsubscribeHeads(rpcSub.ID) return case <-notifier.Closed(): - headersSub.Unsubscribe() + err = api.events.UnsubscribeHeads(rpcSub.ID) return } } }() - return rpcSub, nil + return rpcSub, err } // Logs creates a subscription that fires for all new log that match the given filter criteria. @@ -187,12 +204,15 @@ func (api *PublicFilterAPI) Logs(ctx context.Context, crit filters.FilterCriteri return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported } + api.events = api.events.WithContext(ctx) + var ( - rpcSub = notifier.CreateSubscription() - matchedLogs = make(chan []*types.Log) + rpcSub = notifier.CreateSubscription() ) - logsSub, err := api.events.SubscribeLogs(ethereum.FilterQuery(crit), matchedLogs) + // filterCriteria := ethereum.FilterQuery(crit) + + eventCh, err := api.events.SubscribeLogs(rpcSub.ID) if err != nil { return nil, err } @@ -201,21 +221,28 @@ func (api *PublicFilterAPI) Logs(ctx context.Context, crit filters.FilterCriteri for { select { - case logs := <-matchedLogs: - for _, log := range logs { - notifier.Notify(rpcSub.ID, &log) + case event := <-eventCh: + _, ok := event.Data.(tmtypes.EventDataTx) + if !ok { + return } + + // eventTx.Height // TODO: use filter criteria + + // for _, log := range logs { + // notifier.Notify(rpcSub.ID, &log) + // } case <-rpcSub.Err(): // client send an unsubscribe request - logsSub.Unsubscribe() + err = api.events.UnsubscribeLogs(rpcSub.ID) return case <-notifier.Closed(): // connection dropped - logsSub.Unsubscribe() + err = api.events.UnsubscribeLogs(rpcSub.ID) return } } }() - return rpcSub, nil + return rpcSub, err } // NewFilter creates a new filter and returns the filter id. It can be diff --git a/rpc/filter_system.go b/rpc/filter_system.go index 81317b56c..726b9ccd4 100644 --- a/rpc/filter_system.go +++ b/rpc/filter_system.go @@ -1,20 +1,111 @@ package rpc import ( - ethereum "github.com/ethereum/go-ethereum" + "context" + + rpcclient "github.com/tendermint/tendermint/rpc/client" + coretypes "github.com/tendermint/tendermint/rpc/core/types" + tmtypes "github.com/tendermint/tendermint/types" + "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth/filters" - - abci "github.com/tendermint/tendermint/abci/types" + "github.com/ethereum/go-ethereum/rpc" ) // EventSystem creates subscriptions, processes events and broadcasts them to the // subscription which match the subscription criteria. type EventSystem interface { - SubscribeLogs(crit ethereum.FilterQuery, logs chan []*types.Log) (*filters.Subscription, error) - SubscribeNewHeads(headers chan abci.Header) *filters.Subscription + WithContext(ctx context.Context) EventSystem + SubscribeLogs(subscriberID rpc.ID) (eventCh <-chan coretypes.ResultEvent, err error) + UnsubscribeLogs(subscriberID rpc.ID) (err error) + SubscribeNewHeads(subscriberID rpc.ID) (eventCh <-chan coretypes.ResultEvent, err error) + UnsubscribeHeads(subscriberID rpc.ID) (err error) SubscribePendingTxs(hashes chan []common.Hash) *filters.Subscription } -// TODO: create concrete type +var _ EventSystem = &TendermintEvents{} + +// TendermintEvents implements the EventSystem using Tendermint's RPC client. +type TendermintEvents struct { + ctx context.Context + client rpcclient.Client +} + +// WithContext sets the a given context to the +func (te *TendermintEvents) WithContext(ctx context.Context) EventSystem { + te.ctx = ctx + return te +} + +func (te TendermintEvents) SubscribeLogs(subscriberID rpc.ID) (eventCh <-chan coretypes.ResultEvent, err error) { + // var from, to rpc.BlockNumber + // if crit.FromBlock == nil { + // from = rpc.LatestBlockNumber + // } else { + // from = rpc.BlockNumber(crit.FromBlock.Int64()) + // } + // if crit.ToBlock == nil { + // to = rpc.LatestBlockNumber + // } else { + // to = rpc.BlockNumber(crit.ToBlock.Int64()) + // } + + // // TODO: filter logs + + // // only interested in pending logs + // if from == rpc.PendingBlockNumber && to == rpc.PendingBlockNumber { + // return es.subscribePendingLogs(crit, logs), nil + // } + // // only interested in new mined logs + // if from == rpc.LatestBlockNumber && to == rpc.LatestBlockNumber { + // return te.subscribeLogs(subscriberID) + // } + // // only interested in mined logs within a specific block range + // if from >= 0 && to >= 0 && to >= from { + // return te.subscribeLogs(subscriberID) + // } + // // interested in mined logs from a specific block number, new logs and pending logs + // if from >= rpc.LatestBlockNumber && to == rpc.PendingBlockNumber { + // return tc.subscribeMinedPendingLogs(subscriberID), nil + // } + // // interested in logs from a specific block number to new mined blocks + // if from >= 0 && to == rpc.LatestBlockNumber { + // return te.subscribeLogs(subscriberID) + // } + + return te.subscribeLogs(subscriberID) + + // return nil, fmt.Errorf("invalid from and to block combination: from > to") +} + +func (te TendermintEvents) SubscribeNewHeads(subscriberID rpc.ID) (eventCh <-chan coretypes.ResultEvent, err error) { + return te.client.Subscribe( + te.ctx, string(subscriberID), + tmtypes.QueryForEvent(tmtypes.EventNewBlockHeader).String(), + ) +} + +func (te TendermintEvents) UnsubscribeHeads(subscriberID rpc.ID) (err error) { + return te.client.Unsubscribe( + te.ctx, string(subscriberID), + tmtypes.QueryForEvent(tmtypes.EventNewBlockHeader).String(), + ) +} + +func (te TendermintEvents) SubscribePendingTxs(hashes chan []common.Hash) *filters.Subscription { + return &filters.Subscription{} +} + +func (te TendermintEvents) subscribeLogs(subscriberID rpc.ID) (eventCh <-chan coretypes.ResultEvent, err error) { + return te.client.Subscribe( + te.ctx, string(subscriberID), + tmtypes.QueryForEvent(tmtypes.EventTx).String(), + ) +} + +func (te TendermintEvents) UnsubscribeLogs(subscriberID rpc.ID) (err error) { + return te.client.Unsubscribe( + te.ctx, string(subscriberID), + tmtypes.QueryForEvent(tmtypes.EventTx).String(), + ) +} diff --git a/x/evm/handler.go b/x/evm/handler.go index d2c1f12c7..9d78a6237 100644 --- a/x/evm/handler.go +++ b/x/evm/handler.go @@ -100,6 +100,20 @@ func handleMsgEthereumTx(ctx sdk.Context, k Keeper, msg types.MsgEthereumTx) (*s ) } + for _, log := range executionResult.Logs { + if log == nil { + continue + } + + ctx.EventManager().EmitEvent( + sdk.NewEvent( + types.EventTypeLog, + sdk.NewAttribute(types.AttributeKeyContractAddress, log.Address.String()), + sdk.NewAttribute(types.AttributeKeyContractAddress, log.Data), + ), + ) + } + // set the events to the result executionResult.Result.Events = ctx.EventManager().Events().ToABCIEvents() return executionResult.Result, nil diff --git a/x/evm/types/events.go b/x/evm/types/events.go index e5dca425a..72f6c06a2 100644 --- a/x/evm/types/events.go +++ b/x/evm/types/events.go @@ -5,6 +5,8 @@ const ( EventTypeEthermint = TypeMsgEthermint EventTypeEthereumTx = TypeMsgEthereumTx + EventTypeLog = "log" + AttributeKeyContractAddress = "contract" AttributeKeyRecipient = "recipient" AttributeValueCategory = ModuleName From 9c9b18ae2194bcb8aa58358966b4e6e6ceb8ebe1 Mon Sep 17 00:00:00 2001 From: Federico Kunze Date: Fri, 22 May 2020 14:43:11 -0400 Subject: [PATCH 03/36] new log events --- rpc/filter_api.go | 42 ++++++++++++++++++--------- rpc/filter_system.go | 67 ++++++++++---------------------------------- 2 files changed, 44 insertions(+), 65 deletions(-) diff --git a/rpc/filter_api.go b/rpc/filter_api.go index 174fb8ba0..87bc2db53 100644 --- a/rpc/filter_api.go +++ b/rpc/filter_api.go @@ -7,7 +7,6 @@ import ( tmtypes "github.com/tendermint/tendermint/types" - ethereum "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" ethtypes "github.com/ethereum/go-ethereum/core/types" @@ -16,6 +15,8 @@ import ( "github.com/ethereum/go-ethereum/rpc" clientcontext "github.com/cosmos/cosmos-sdk/client/context" + + evmtypes "github.com/cosmos/ethermint/x/evm/types" ) // PublicFilterAPI offers support to create and manage filters. This will allow external clients to retrieve various @@ -181,6 +182,7 @@ func (api *PublicFilterAPI) NewHeads(ctx context.Context) (*rpc.Subscription, er case event := <-eventCh: evHeader, ok := event.Data.(tmtypes.EventDataNewBlockHeader) if !ok { + err = fmt.Errorf("invalid event data %T, expected %s", event.Data, tmtypes.EventNewBlockHeader) return } notifier.Notify(rpcSub.ID, evHeader.Header) @@ -224,6 +226,7 @@ func (api *PublicFilterAPI) Logs(ctx context.Context, crit filters.FilterCriteri case event := <-eventCh: _, ok := event.Data.(tmtypes.EventDataTx) if !ok { + err = fmt.Errorf("invalid event data %T, expected %s", event.Data, tmtypes.EventTx) return } @@ -259,35 +262,48 @@ func (api *PublicFilterAPI) Logs(ctx context.Context, crit filters.FilterCriteri // // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newfilter func (api *PublicFilterAPI) NewFilter(criteria filters.FilterCriteria) (rpc.ID, error) { - logs := make(chan []*types.Log) - logsSub, err := api.events.SubscribeLogs(ethereum.FilterQuery(criteria), logs) + subscriberID := rpc.NewID() + eventCh, err := api.events.SubscribeLogs(subscriberID) if err != nil { return rpc.ID(""), err } + // filterCriteria = ethereum.FilterQuery(criteria) + api.filtersMu.Lock() - api.filters[logsSub.ID] = NewFilter(api.backend, &criteria, filters.LogsSubscription) + api.filters[subscriberID] = NewFilter(api.backend, &criteria, filters.LogsSubscription) api.filtersMu.Unlock() go func() { for { select { - case l := <-logs: - api.filtersMu.Lock() - if f, found := api.filters[logsSub.ID]; found { - f.logs = append(f.logs, l...) + case event := <-eventCh: + eventTx, ok := event.Data.(tmtypes.EventDataTx) + if !ok { + err = fmt.Errorf("invalid event data %T, expected %s", event.Data, tmtypes.EventTx) + return } - api.filtersMu.Unlock() - case <-logsSub.Err(): + + _, err := bytesToEthTx(api.cliCtx, eventTx.Tx) + if err != nil { + return + } + + data, err := evmtypes.DecodeResultData(eventTx.TxResult.Result.Data) + if err != nil { + return + } + api.filtersMu.Lock() - delete(api.filters, logsSub.ID) + if f, found := api.filters[subscriberID]; found { + f.logs = append(f.logs, data.Logs...) + } api.filtersMu.Unlock() - return } } }() - return logsSub.ID, nil + return subscriberID, nil } // GetLogs returns logs matching the given argument that are stored within the state. diff --git a/rpc/filter_system.go b/rpc/filter_system.go index 726b9ccd4..4344d7a95 100644 --- a/rpc/filter_system.go +++ b/rpc/filter_system.go @@ -20,7 +20,7 @@ type EventSystem interface { UnsubscribeLogs(subscriberID rpc.ID) (err error) SubscribeNewHeads(subscriberID rpc.ID) (eventCh <-chan coretypes.ResultEvent, err error) UnsubscribeHeads(subscriberID rpc.ID) (err error) - SubscribePendingTxs(hashes chan []common.Hash) *filters.Subscription + // SubscribePendingTxs(hashes chan []common.Hash) *filters.Subscription } var _ EventSystem = &TendermintEvents{} @@ -37,45 +37,22 @@ func (te *TendermintEvents) WithContext(ctx context.Context) EventSystem { return te } +// SubscribeLogs subscribes to new incoming MsgEthereumTx or MsgEthermint transactions +// TODO: +// - subscribe based on Msg Type +// - subscribe to logs based on filter criteria func (te TendermintEvents) SubscribeLogs(subscriberID rpc.ID) (eventCh <-chan coretypes.ResultEvent, err error) { - // var from, to rpc.BlockNumber - // if crit.FromBlock == nil { - // from = rpc.LatestBlockNumber - // } else { - // from = rpc.BlockNumber(crit.FromBlock.Int64()) - // } - // if crit.ToBlock == nil { - // to = rpc.LatestBlockNumber - // } else { - // to = rpc.BlockNumber(crit.ToBlock.Int64()) - // } - - // // TODO: filter logs - - // // only interested in pending logs - // if from == rpc.PendingBlockNumber && to == rpc.PendingBlockNumber { - // return es.subscribePendingLogs(crit, logs), nil - // } - // // only interested in new mined logs - // if from == rpc.LatestBlockNumber && to == rpc.LatestBlockNumber { - // return te.subscribeLogs(subscriberID) - // } - // // only interested in mined logs within a specific block range - // if from >= 0 && to >= 0 && to >= from { - // return te.subscribeLogs(subscriberID) - // } - // // interested in mined logs from a specific block number, new logs and pending logs - // if from >= rpc.LatestBlockNumber && to == rpc.PendingBlockNumber { - // return tc.subscribeMinedPendingLogs(subscriberID), nil - // } - // // interested in logs from a specific block number to new mined blocks - // if from >= 0 && to == rpc.LatestBlockNumber { - // return te.subscribeLogs(subscriberID) - // } - - return te.subscribeLogs(subscriberID) + return te.client.Subscribe( + te.ctx, string(subscriberID), + tmtypes.QueryForEvent(tmtypes.EventTx).String(), + ) +} - // return nil, fmt.Errorf("invalid from and to block combination: from > to") +func (te TendermintEvents) UnsubscribeLogs(subscriberID rpc.ID) (err error) { + return te.client.Unsubscribe( + te.ctx, string(subscriberID), + tmtypes.QueryForEvent(tmtypes.EventTx).String(), + ) } func (te TendermintEvents) SubscribeNewHeads(subscriberID rpc.ID) (eventCh <-chan coretypes.ResultEvent, err error) { @@ -95,17 +72,3 @@ func (te TendermintEvents) UnsubscribeHeads(subscriberID rpc.ID) (err error) { func (te TendermintEvents) SubscribePendingTxs(hashes chan []common.Hash) *filters.Subscription { return &filters.Subscription{} } - -func (te TendermintEvents) subscribeLogs(subscriberID rpc.ID) (eventCh <-chan coretypes.ResultEvent, err error) { - return te.client.Subscribe( - te.ctx, string(subscriberID), - tmtypes.QueryForEvent(tmtypes.EventTx).String(), - ) -} - -func (te TendermintEvents) UnsubscribeLogs(subscriberID rpc.ID) (err error) { - return te.client.Unsubscribe( - te.ctx, string(subscriberID), - tmtypes.QueryForEvent(tmtypes.EventTx).String(), - ) -} From bd83591c67978c50149b6854e3100762228bd176 Mon Sep 17 00:00:00 2001 From: Federico Kunze Date: Mon, 25 May 2020 14:49:51 -0400 Subject: [PATCH 04/36] filter evm transactions --- rpc/filter_api.go | 74 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 57 insertions(+), 17 deletions(-) diff --git a/rpc/filter_api.go b/rpc/filter_api.go index 87bc2db53..0cca44af4 100644 --- a/rpc/filter_api.go +++ b/rpc/filter_api.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "sync" + "time" tmtypes "github.com/tendermint/tendermint/types" @@ -59,6 +60,11 @@ func (api *PublicFilterAPI) NewPendingTransactionFilter() rpc.ID { pendingTxSub = api.events.SubscribePendingTxs(pendingTxs) ) + ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) + defer cancelFn() + + api.events = api.events.WithContext(ctx) + api.filtersMu.Lock() api.filters[pendingTxSub.ID] = NewFilter(api.backend, &filters.FilterCriteria{}, filters.PendingTransactionsSubscription) api.filtersMu.Unlock() @@ -92,6 +98,9 @@ func (api *PublicFilterAPI) NewPendingTransactions(ctx context.Context) (*rpc.Su return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported } + ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) + defer cancelFn() + api.events = api.events.WithContext(ctx) rpcSub := notifier.CreateSubscription() @@ -132,6 +141,11 @@ func (api *PublicFilterAPI) NewBlockFilter() rpc.ID { return rpc.ID("") } + ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) + defer cancelFn() + + api.events = api.events.WithContext(ctx) + api.filtersMu.Lock() api.filters[subscriberID] = NewFilter(api.backend, &filters.FilterCriteria{}, filters.BlocksSubscription) api.filtersMu.Unlock() @@ -142,10 +156,6 @@ func (api *PublicFilterAPI) NewBlockFilter() rpc.ID { case event := <-eventCh: evHeader, ok := event.Data.(tmtypes.EventDataNewBlockHeader) if !ok { - // remove filter from map - api.filtersMu.Lock() - delete(api.filters, subscriberID) - api.filtersMu.Unlock() return } api.filtersMu.Lock() @@ -167,6 +177,9 @@ func (api *PublicFilterAPI) NewHeads(ctx context.Context) (*rpc.Subscription, er return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported } + ctx, cancelFn := context.WithTimeout(ctx, 5*time.Second) + defer cancelFn() + api.events = api.events.WithContext(ctx) rpcSub := notifier.CreateSubscription() @@ -206,6 +219,9 @@ func (api *PublicFilterAPI) Logs(ctx context.Context, crit filters.FilterCriteri return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported } + ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) + defer cancelFn() + api.events = api.events.WithContext(ctx) var ( @@ -220,21 +236,36 @@ func (api *PublicFilterAPI) Logs(ctx context.Context, crit filters.FilterCriteri } go func() { - for { select { case event := <-eventCh: - _, ok := event.Data.(tmtypes.EventDataTx) + // filter only events from EVM module txs + _, isMsgEthermint := event.Events[evmtypes.TypeMsgEthermint] + _, isMsgEthereumTx := event.Events[evmtypes.TypeMsgEthereumTx] + + if !(isMsgEthermint || isMsgEthereumTx) { + // ignore transaction + return + } + + // get the + dataTx, ok := event.Data.(tmtypes.EventDataTx) if !ok { err = fmt.Errorf("invalid event data %T, expected %s", event.Data, tmtypes.EventTx) return } - // eventTx.Height // TODO: use filter criteria + resultData, err := evmtypes.DecodeResultData(dataTx.TxResult.Result.Data) + if err != nil { + return + } + + // TODO: use filter criteria + // dataTx.Height - // for _, log := range logs { - // notifier.Notify(rpcSub.ID, &log) - // } + for _, log := range resultData.Logs { + notifier.Notify(rpcSub.ID, &log) + } case <-rpcSub.Err(): // client send an unsubscribe request err = api.events.UnsubscribeLogs(rpcSub.ID) return @@ -270,6 +301,11 @@ func (api *PublicFilterAPI) NewFilter(criteria filters.FilterCriteria) (rpc.ID, // filterCriteria = ethereum.FilterQuery(criteria) + ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) + defer cancelFn() + + api.events = api.events.WithContext(ctx) + api.filtersMu.Lock() api.filters[subscriberID] = NewFilter(api.backend, &criteria, filters.LogsSubscription) api.filtersMu.Unlock() @@ -278,25 +314,29 @@ func (api *PublicFilterAPI) NewFilter(criteria filters.FilterCriteria) (rpc.ID, for { select { case event := <-eventCh: - eventTx, ok := event.Data.(tmtypes.EventDataTx) - if !ok { - err = fmt.Errorf("invalid event data %T, expected %s", event.Data, tmtypes.EventTx) + // filter only events from EVM module txs + _, isMsgEthermint := event.Events[evmtypes.TypeMsgEthermint] + _, isMsgEthereumTx := event.Events[evmtypes.TypeMsgEthereumTx] + + if !(isMsgEthermint || isMsgEthereumTx) { + // ignore transaction return } - _, err := bytesToEthTx(api.cliCtx, eventTx.Tx) - if err != nil { + dataTx, ok := event.Data.(tmtypes.EventDataTx) + if !ok { + err = fmt.Errorf("invalid event data %T, expected %s", event.Data, tmtypes.EventTx) return } - data, err := evmtypes.DecodeResultData(eventTx.TxResult.Result.Data) + resultData, err := evmtypes.DecodeResultData(dataTx.TxResult.Result.Data) if err != nil { return } api.filtersMu.Lock() if f, found := api.filters[subscriberID]; found { - f.logs = append(f.logs, data.Logs...) + f.logs = append(f.logs, resultData.Logs...) } api.filtersMu.Unlock() } From d0e0d1be5ae0f8238b9bff5f581395f8a3e758e8 Mon Sep 17 00:00:00 2001 From: Federico Kunze Date: Mon, 25 May 2020 17:31:57 -0400 Subject: [PATCH 05/36] filter logs --- rpc/apis.go | 25 +++--- rpc/backend.go | 2 +- rpc/filter_api.go | 192 +++++++++++++++++++++---------------------- rpc/filter_system.go | 12 ++- x/evm/handler.go | 14 ---- 5 files changed, 121 insertions(+), 124 deletions(-) diff --git a/rpc/apis.go b/rpc/apis.go index e6672dd93..c8d0adb6d 100644 --- a/rpc/apis.go +++ b/rpc/apis.go @@ -8,10 +8,15 @@ import ( "github.com/ethereum/go-ethereum/rpc" ) -const Web3Namespace = "web3" -const EthNamespace = "eth" -const PersonalNamespace = "personal" -const NetNamespace = "net" +// RPC namespaces and API version +const ( + Web3Namespace = "web3" + EthNamespace = "eth" + PersonalNamespace = "personal" + NetNamespace = "net" + + apiVersion = "1.0" +) // GetRPCAPIs returns the list of all APIs func GetRPCAPIs(cliCtx context.CLIContext, key emintcrypto.PrivKeySecp256k1) []rpc.API { @@ -20,31 +25,31 @@ func GetRPCAPIs(cliCtx context.CLIContext, key emintcrypto.PrivKeySecp256k1) []r return []rpc.API{ { Namespace: Web3Namespace, - Version: "1.0", + Version: apiVersion, Service: NewPublicWeb3API(), Public: true, }, { Namespace: EthNamespace, - Version: "1.0", + Version: apiVersion, Service: NewPublicEthAPI(cliCtx, backend, nonceLock, key), Public: true, }, { Namespace: PersonalNamespace, - Version: "1.0", + Version: apiVersion, Service: NewPersonalEthAPI(cliCtx, nonceLock), Public: false, }, { Namespace: EthNamespace, - Version: "1.0", - Service: NewPublicFilterAPI(cliCtx, backend), + Version: apiVersion, + Service: NewPublicFilterAPI(cliCtx, backend, 5), Public: true, }, { Namespace: NetNamespace, - Version: "1.0", + Version: apiVersion, Service: NewPublicNetAPI(cliCtx), Public: true, }, diff --git a/rpc/backend.go b/rpc/backend.go index 93ae92f4d..7e44cef8a 100644 --- a/rpc/backend.go +++ b/rpc/backend.go @@ -194,7 +194,7 @@ func (e *EthermintBackend) PendingTransactions() ([]*Transaction, error) { return nil, err } - transactions := make([]*Transaction, 0, 100) + transactions := make([]*Transaction, pendingTxs.Count) for _, tx := range pendingTxs.Txs { ethTx, err := bytesToEthTx(e.cliCtx, tx) if err != nil { diff --git a/rpc/filter_api.go b/rpc/filter_api.go index 0cca44af4..1cc2c16d8 100644 --- a/rpc/filter_api.go +++ b/rpc/filter_api.go @@ -33,101 +33,102 @@ type PublicFilterAPI struct { } // NewPublicFilterAPI returns a new PublicFilterAPI instance. -func NewPublicFilterAPI(cliCtx clientcontext.CLIContext, backend Backend) *PublicFilterAPI { +func NewPublicFilterAPI(cliCtx clientcontext.CLIContext, backend Backend, timeoutSec int64) *PublicFilterAPI { return &PublicFilterAPI{ cliCtx: cliCtx, backend: backend, filters: make(map[rpc.ID]*Filter), - events: TendermintEvents{ - ctx: context.Background(), - client: cliCtx.Client, + events: &TendermintEvents{ + ctx: context.Background(), + client: cliCtx.Client, + timeout: time.Duration(timeoutSec) * time.Second, }, } // TODO: implement timeout loop } -// NewPendingTransactionFilter creates a filter that fetches pending transaction hashes -// as transactions enter the pending state. -// -// It is part of the filter package because this filter can be used through the -// `eth_getFilterChanges` polling method that is also used for log filters. -// -// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newpendingtransactionfilter -func (api *PublicFilterAPI) NewPendingTransactionFilter() rpc.ID { - var ( - pendingTxs = make(chan []common.Hash) - pendingTxSub = api.events.SubscribePendingTxs(pendingTxs) - ) - - ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) - defer cancelFn() - - api.events = api.events.WithContext(ctx) - - api.filtersMu.Lock() - api.filters[pendingTxSub.ID] = NewFilter(api.backend, &filters.FilterCriteria{}, filters.PendingTransactionsSubscription) - api.filtersMu.Unlock() - - go func() { - for { - select { - case ph := <-pendingTxs: - api.filtersMu.Lock() - if f, found := api.filters[pendingTxSub.ID]; found { - f.hashes = append(f.hashes, ph...) - } - api.filtersMu.Unlock() - case <-pendingTxSub.Err(): - api.filtersMu.Lock() - delete(api.filters, pendingTxSub.ID) - api.filtersMu.Unlock() - return - } - } - }() - - return pendingTxSub.ID -} - -// NewPendingTransactions creates a subscription that is triggered each time a transaction -// enters the transaction pool and was signed from one of the transactions this nodes manages. -func (api *PublicFilterAPI) NewPendingTransactions(ctx context.Context) (*rpc.Subscription, error) { - notifier, supported := rpc.NotifierFromContext(ctx) - if !supported { - return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported - } - - ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) - defer cancelFn() - - api.events = api.events.WithContext(ctx) - rpcSub := notifier.CreateSubscription() - - go func() { - txHashes := make(chan []common.Hash, 128) - pendingTxSub := api.events.SubscribePendingTxs(txHashes) - - for { - select { - case hashes := <-txHashes: - // To keep the original behaviour, send a single tx hash in one notification. - // TODO(rjl493456442) Send a batch of tx hashes in one notification - for _, h := range hashes { - notifier.Notify(rpcSub.ID, h) - } - case <-rpcSub.Err(): - pendingTxSub.Unsubscribe() - return - case <-notifier.Closed(): - pendingTxSub.Unsubscribe() - return - } - } - }() - - return rpcSub, nil -} +// // NewPendingTransactionFilter creates a filter that fetches pending transaction hashes +// // as transactions enter the pending state. +// // +// // It is part of the filter package because this filter can be used through the +// // `eth_getFilterChanges` polling method that is also used for log filters. +// // +// // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newpendingtransactionfilter +// func (api *PublicFilterAPI) NewPendingTransactionFilter() rpc.ID { +// var ( +// pendingTxs = make(chan []common.Hash) +// pendingTxSub = api.events.SubscribePendingTxs(pendingTxs) +// ) + +// ctx, cancelFn := context.WithTimeout(context.Background(), api.events.GetTimeout()) +// defer cancelFn() + +// api.events = api.events.WithContext(ctx) + +// api.filtersMu.Lock() +// api.filters[pendingTxSub.ID] = NewFilter(api.backend, &filters.FilterCriteria{}, filters.PendingTransactionsSubscription) +// api.filtersMu.Unlock() + +// go func() { +// for { +// select { +// case ph := <-pendingTxs: +// api.filtersMu.Lock() +// if f, found := api.filters[pendingTxSub.ID]; found { +// f.hashes = append(f.hashes, ph...) +// } +// api.filtersMu.Unlock() +// case <-pendingTxSub.Err(): +// api.filtersMu.Lock() +// delete(api.filters, pendingTxSub.ID) +// api.filtersMu.Unlock() +// return +// } +// } +// }() + +// return pendingTxSub.ID +// } + +// // NewPendingTransactions creates a subscription that is triggered each time a transaction +// // enters the transaction pool and was signed from one of the transactions this nodes manages. +// func (api *PublicFilterAPI) NewPendingTransactions(ctx context.Context) (*rpc.Subscription, error) { +// notifier, supported := rpc.NotifierFromContext(ctx) +// if !supported { +// return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported +// } + +// ctx, cancelFn := context.WithTimeout(context.Background(), api.events.GetTimeout()) +// defer cancelFn() + +// api.events = api.events.WithContext(ctx) +// rpcSub := notifier.CreateSubscription() + +// go func() { +// txHashes := make(chan []common.Hash, 128) +// pendingTxSub := api.events.SubscribePendingTxs(txHashes) + +// for { +// select { +// case hashes := <-txHashes: +// // To keep the original behaviour, send a single tx hash in one notification. +// // TODO(rjl493456442) Send a batch of tx hashes in one notification +// for _, h := range hashes { +// notifier.Notify(rpcSub.ID, h) +// } +// case <-rpcSub.Err(): +// pendingTxSub.Unsubscribe() +// return +// case <-notifier.Closed(): +// pendingTxSub.Unsubscribe() +// return +// } +// } +// }() + +// return rpcSub, nil +// } // NewBlockFilter creates a filter that fetches blocks that are imported into the chain. // It is part of the filter package since polling goes with eth_getFilterChanges. @@ -141,7 +142,7 @@ func (api *PublicFilterAPI) NewBlockFilter() rpc.ID { return rpc.ID("") } - ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancelFn := context.WithTimeout(context.Background(), api.events.GetTimeout()) defer cancelFn() api.events = api.events.WithContext(ctx) @@ -177,7 +178,7 @@ func (api *PublicFilterAPI) NewHeads(ctx context.Context) (*rpc.Subscription, er return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported } - ctx, cancelFn := context.WithTimeout(ctx, 5*time.Second) + ctx, cancelFn := context.WithTimeout(ctx, api.events.GetTimeout()) defer cancelFn() api.events = api.events.WithContext(ctx) @@ -219,7 +220,7 @@ func (api *PublicFilterAPI) Logs(ctx context.Context, crit filters.FilterCriteri return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported } - ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancelFn := context.WithTimeout(context.Background(), api.events.GetTimeout()) defer cancelFn() api.events = api.events.WithContext(ctx) @@ -228,8 +229,6 @@ func (api *PublicFilterAPI) Logs(ctx context.Context, crit filters.FilterCriteri rpcSub = notifier.CreateSubscription() ) - // filterCriteria := ethereum.FilterQuery(crit) - eventCh, err := api.events.SubscribeLogs(rpcSub.ID) if err != nil { return nil, err @@ -260,10 +259,9 @@ func (api *PublicFilterAPI) Logs(ctx context.Context, crit filters.FilterCriteri return } - // TODO: use filter criteria - // dataTx.Height + logs := filterLogs(resultData.Logs, crit.FromBlock, crit.ToBlock, crit.Addresses, crit.Topics) - for _, log := range resultData.Logs { + for _, log := range logs { notifier.Notify(rpcSub.ID, &log) } case <-rpcSub.Err(): // client send an unsubscribe request @@ -299,9 +297,7 @@ func (api *PublicFilterAPI) NewFilter(criteria filters.FilterCriteria) (rpc.ID, return rpc.ID(""), err } - // filterCriteria = ethereum.FilterQuery(criteria) - - ctx, cancelFn := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancelFn := context.WithTimeout(context.Background(), api.events.GetTimeout()) defer cancelFn() api.events = api.events.WithContext(ctx) @@ -334,9 +330,11 @@ func (api *PublicFilterAPI) NewFilter(criteria filters.FilterCriteria) (rpc.ID, return } + logs := filterLogs(resultData.Logs, criteria.FromBlock, criteria.ToBlock, criteria.Addresses, criteria.Topics) + api.filtersMu.Lock() if f, found := api.filters[subscriberID]; found { - f.logs = append(f.logs, resultData.Logs...) + f.logs = append(f.logs, logs...) } api.filtersMu.Unlock() } diff --git a/rpc/filter_system.go b/rpc/filter_system.go index 4344d7a95..51e496f27 100644 --- a/rpc/filter_system.go +++ b/rpc/filter_system.go @@ -2,6 +2,7 @@ package rpc import ( "context" + "time" rpcclient "github.com/tendermint/tendermint/rpc/client" coretypes "github.com/tendermint/tendermint/rpc/core/types" @@ -16,6 +17,7 @@ import ( // subscription which match the subscription criteria. type EventSystem interface { WithContext(ctx context.Context) EventSystem + GetTimeout() time.Duration SubscribeLogs(subscriberID rpc.ID) (eventCh <-chan coretypes.ResultEvent, err error) UnsubscribeLogs(subscriberID rpc.ID) (err error) SubscribeNewHeads(subscriberID rpc.ID) (eventCh <-chan coretypes.ResultEvent, err error) @@ -27,8 +29,9 @@ var _ EventSystem = &TendermintEvents{} // TendermintEvents implements the EventSystem using Tendermint's RPC client. type TendermintEvents struct { - ctx context.Context - client rpcclient.Client + ctx context.Context + client rpcclient.Client + timeout time.Duration } // WithContext sets the a given context to the @@ -37,6 +40,11 @@ func (te *TendermintEvents) WithContext(ctx context.Context) EventSystem { return te } +// GetTimeout returns the default timeout in seconds +func (te TendermintEvents) GetTimeout() time.Duration { + return te.timeout +} + // SubscribeLogs subscribes to new incoming MsgEthereumTx or MsgEthermint transactions // TODO: // - subscribe based on Msg Type diff --git a/x/evm/handler.go b/x/evm/handler.go index 9d78a6237..d2c1f12c7 100644 --- a/x/evm/handler.go +++ b/x/evm/handler.go @@ -100,20 +100,6 @@ func handleMsgEthereumTx(ctx sdk.Context, k Keeper, msg types.MsgEthereumTx) (*s ) } - for _, log := range executionResult.Logs { - if log == nil { - continue - } - - ctx.EventManager().EmitEvent( - sdk.NewEvent( - types.EventTypeLog, - sdk.NewAttribute(types.AttributeKeyContractAddress, log.Address.String()), - sdk.NewAttribute(types.AttributeKeyContractAddress, log.Data), - ), - ) - } - // set the events to the result executionResult.Result.Events = ctx.EventManager().Events().ToABCIEvents() return executionResult.Result, nil From 0df44f088469b87d7df66e6e8b005404848e015a Mon Sep 17 00:00:00 2001 From: Federico Kunze Date: Wed, 27 May 2020 22:33:25 -0400 Subject: [PATCH 06/36] wip: refactor filters --- rpc/filter_api.go | 44 +++++++-- rpc/filter_system.go | 3 - rpc/filters.go | 225 +++---------------------------------------- rpc/net_api.go | 2 +- 4 files changed, 50 insertions(+), 224 deletions(-) diff --git a/rpc/filter_api.go b/rpc/filter_api.go index 1cc2c16d8..8e0d1b877 100644 --- a/rpc/filter_api.go +++ b/rpc/filter_api.go @@ -20,6 +20,10 @@ import ( evmtypes "github.com/cosmos/ethermint/x/evm/types" ) +var ( + deadline = 5 * time.Minute // consider a filter inactive if it has not been polled for within deadline +) + // PublicFilterAPI offers support to create and manage filters. This will allow external clients to retrieve various // information related to the Ethereum protocol such as blocks, transactions and logs. type PublicFilterAPI struct { @@ -29,12 +33,12 @@ type PublicFilterAPI struct { quit chan struct{} events EventSystem filtersMu sync.Mutex - filters map[rpc.ID]*Filter // ID to filter; TODO: change to sync.Map in case of concurrent writes + filters map[rpc.ID]*Filter } // NewPublicFilterAPI returns a new PublicFilterAPI instance. func NewPublicFilterAPI(cliCtx clientcontext.CLIContext, backend Backend, timeoutSec int64) *PublicFilterAPI { - return &PublicFilterAPI{ + api := &PublicFilterAPI{ cliCtx: cliCtx, backend: backend, filters: make(map[rpc.ID]*Filter), @@ -45,7 +49,30 @@ func NewPublicFilterAPI(cliCtx clientcontext.CLIContext, backend Backend, timeou }, } - // TODO: implement timeout loop + go api.timeoutLoop() + + return api +} + +// timeoutLoop runs every 5 minutes and deletes filters that have not been recently used. +// Tt is started when the api is created. +func (api *PublicFilterAPI) timeoutLoop() { + ticker := time.NewTicker(5 * time.Minute) + defer ticker.Stop() + for { + <-ticker.C + api.filtersMu.Lock() + for id, f := range api.filters { + select { + case <-f.deadline.C: + f.Unsubscribe() + delete(api.filters, id) + default: + continue + } + } + api.filtersMu.Unlock() + } } // // NewPendingTransactionFilter creates a filter that fetches pending transaction hashes @@ -148,7 +175,7 @@ func (api *PublicFilterAPI) NewBlockFilter() rpc.ID { api.events = api.events.WithContext(ctx) api.filtersMu.Lock() - api.filters[subscriberID] = NewFilter(api.backend, &filters.FilterCriteria{}, filters.BlocksSubscription) + api.filters[subscriberID] = NewFilter(api.backend, filters.BlocksSubscription, filters.FilterCriteria{}) api.filtersMu.Unlock() go func() { @@ -303,7 +330,7 @@ func (api *PublicFilterAPI) NewFilter(criteria filters.FilterCriteria) (rpc.ID, api.events = api.events.WithContext(ctx) api.filtersMu.Lock() - api.filters[subscriberID] = NewFilter(api.backend, &criteria, filters.LogsSubscription) + api.filters[subscriberID] = NewFilter(api.backend, filters.LogsSubscription, criteria) api.filtersMu.Unlock() go func() { @@ -348,7 +375,7 @@ func (api *PublicFilterAPI) NewFilter(criteria filters.FilterCriteria) (rpc.ID, // // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getlogs func (api *PublicFilterAPI) GetLogs(criteria filters.FilterCriteria) ([]*ethtypes.Log, error) { - filter := NewFilter(api.backend, &criteria, filters.LogsSubscription) + filter := NewFilter(api.backend, filters.LogsSubscription, criteria) return filter.getFilterLogs() // var filter *Filter // if crit.BlockHash != nil { @@ -389,8 +416,7 @@ func (api *PublicFilterAPI) UninstallFilter(id rpc.ID) bool { return false } - // TODO: f.s.Unsubscribe() - f.uninstallFilter() + f.Unsubscribe() return true } @@ -411,7 +437,7 @@ func (api *PublicFilterAPI) GetFilterLogs(id rpc.ID) ([]*ethtypes.Log, error) { return nil, fmt.Errorf("filter %s doesn't have a LogsSubscription type", id) } - return api.filters[id].getFilterLogs() + return api.filters[id].Logs() } // GetFilterChanges returns the logs for the filter with the given id since diff --git a/rpc/filter_system.go b/rpc/filter_system.go index 51e496f27..04d38f169 100644 --- a/rpc/filter_system.go +++ b/rpc/filter_system.go @@ -46,9 +46,6 @@ func (te TendermintEvents) GetTimeout() time.Duration { } // SubscribeLogs subscribes to new incoming MsgEthereumTx or MsgEthermint transactions -// TODO: -// - subscribe based on Msg Type -// - subscribe to logs based on filter criteria func (te TendermintEvents) SubscribeLogs(subscriberID rpc.ID) (eventCh <-chan coretypes.ResultEvent, err error) { return te.client.Subscribe( te.ctx, string(subscriberID), diff --git a/rpc/filters.go b/rpc/filters.go index 0651d1bc4..450d00b4e 100644 --- a/rpc/filters.go +++ b/rpc/filters.go @@ -1,15 +1,13 @@ package rpc import ( - "errors" "math/big" "time" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/bloombits" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth/filters" - "github.com/ethereum/go-ethereum/log" ) /* @@ -17,224 +15,29 @@ import ( Used to set the criteria passed in from RPC params */ -// Filter can be used to retrieve and filter logs, blocks, or pending transactions. type Filter struct { backend Backend - fromBlock, toBlock *big.Int // start and end block numbers - addresses []common.Address // contract addresses to watch - topics [][]common.Hash // log topics to watch for - blockHash *common.Hash // Block hash if filtering a single block + typ filters.Type // filter type + deadline *time.Timer // filter is inactiv when deadline triggers + hashes []common.Hash // filtered block or transaction hashes + criteria filters.FilterCriteria - typ filters.Type // filter type - hashes []common.Hash // filtered block or transaction hashes - logs []*ethtypes.Log // filtered logs - stopped bool // set to true once filter in uninstalled + logs []*ethtypes.Log // filtered logs - err error + matcher *bloombits.Matcher + + subscription bool // associated subscription in event system } // NewFilter returns a new Filter -func NewFilter(backend Backend, criteria *filters.FilterCriteria, filterType filters.Type) *Filter { +func NewFilter(backend Backend, filterType filters.Type, criteria filters.FilterCriteria) *Filter { return &Filter{ - backend: backend, - fromBlock: criteria.FromBlock, - toBlock: criteria.ToBlock, - addresses: criteria.Addresses, - topics: criteria.Topics, - blockHash: criteria.BlockHash, - typ: filterType, - stopped: false, - } -} - -func (f *Filter) pollForBlocks() error { - prev := hexutil.Uint64(0) - - for { - if f.stopped { - return nil - } - - num, err := f.backend.BlockNumber() - if err != nil { - return err - } - - if num == prev { - continue - } - - block, err := f.backend.GetBlockByNumber(BlockNumber(num), false) - if err != nil { - return err - } - - hashBytes, ok := block["hash"].(hexutil.Bytes) - if !ok { - return errors.New("could not convert block hash to hexutil.Bytes") - } - - hash := common.BytesToHash(hashBytes) - f.hashes = append(f.hashes, hash) - - prev = num - - // TODO: should we add a delay? - } -} - -func (f *Filter) pollForTransactions() error { - for { - if f.stopped { - return nil - } - - txs, err := f.backend.PendingTransactions() - if err != nil { - return err - } - - for _, tx := range txs { - if !contains(f.hashes, tx.Hash) { - f.hashes = append(f.hashes, tx.Hash) - } - } - - <-time.After(1 * time.Second) - - } -} - -func contains(slice []common.Hash, item common.Hash) bool { - set := make(map[common.Hash]struct{}, len(slice)) - for _, s := range slice { - set[s] = struct{}{} + backend: backend, + typ: filterType, + deadline: time.NewTimer(deadline), + criteria: criteria, } - - _, ok := set[item] - return ok -} - -func (f *Filter) uninstallFilter() { - f.stopped = true -} - -func (f *Filter) getFilterChanges() (interface{}, error) { - switch f.typ { - case filters.BlocksSubscription: - if f.err != nil { - return nil, f.err - } - - blocks := make([]common.Hash, len(f.hashes)) - copy(blocks, f.hashes) - f.hashes = []common.Hash{} - - return blocks, nil - case filters.PendingTransactionsSubscription: - if f.err != nil { - return nil, f.err - } - - txs := make([]common.Hash, len(f.hashes)) - copy(txs, f.hashes) - f.hashes = []common.Hash{} - return txs, nil - case filters.LogsSubscription: - return f.getFilterLogs() - } - - return nil, errors.New("unsupported filter") -} - -func (f *Filter) getFilterLogs() ([]*ethtypes.Log, error) { - ret := []*ethtypes.Log{} - - // filter specific block only - if f.blockHash != nil { - block, err := f.backend.GetBlockByHash(*f.blockHash, true) - if err != nil { - return nil, err - } - - // if the logsBloom == 0, there are no logs in that block - if txs, ok := block["transactions"].([]common.Hash); !ok { - return ret, nil - } else if len(txs) != 0 { - return f.checkMatches(block) - } - } - - // filter range of blocks - num, err := f.backend.BlockNumber() - if err != nil { - return nil, err - } - - // if f.fromBlock is set to 0, set it to the latest block number - if f.fromBlock == nil || f.fromBlock.Cmp(big.NewInt(0)) == 0 { - f.fromBlock = big.NewInt(int64(num)) - } - - // if f.toBlock is set to 0, set it to the latest block number - if f.toBlock == nil || f.toBlock.Cmp(big.NewInt(0)) == 0 { - f.toBlock = big.NewInt(int64(num)) - } - - log.Debug("[ethAPI] Retrieving filter logs", "fromBlock", f.fromBlock, "toBlock", f.toBlock, - "topics", f.topics, "addresses", f.addresses) - - from := f.fromBlock.Int64() - to := f.toBlock.Int64() - - for i := from; i <= to; i++ { - block, err := f.backend.GetBlockByNumber(NewBlockNumber(big.NewInt(i)), true) - if err != nil { - f.err = err - log.Debug("[ethAPI] Cannot get block", "block", block["number"], "error", err) - break - } - - log.Debug("[ethAPI] filtering", "block", block) - - // TODO: block logsBloom is often set in the wrong block - // if the logsBloom == 0, there are no logs in that block - - if txs, ok := block["transactions"].([]common.Hash); !ok { - continue - } else if len(txs) != 0 { - logs, err := f.checkMatches(block) - if err != nil { - f.err = err - break - } - - ret = append(ret, logs...) - } - } - - return ret, nil -} - -func (f *Filter) checkMatches(block map[string]interface{}) ([]*ethtypes.Log, error) { - transactions, ok := block["transactions"].([]common.Hash) - if !ok { - return nil, errors.New("invalid block transactions") - } - - unfiltered := []*ethtypes.Log{} - - for _, tx := range transactions { - logs, err := f.backend.GetTransactionLogs(common.BytesToHash(tx[:])) - if err != nil { - return nil, err - } - - unfiltered = append(unfiltered, logs...) - } - - return filterLogs(unfiltered, f.fromBlock, f.toBlock, f.addresses, f.topics), nil } // filterLogs creates a slice of logs matching the given criteria. diff --git a/rpc/net_api.go b/rpc/net_api.go index 83c6cab45..c67e2a73b 100644 --- a/rpc/net_api.go +++ b/rpc/net_api.go @@ -14,7 +14,7 @@ type PublicNetAPI struct { networkVersion uint64 } -// NewPersonalEthAPI creates an instance of the public ETH Web3 API. +// NewPublicNetAPI creates an instance of the public Net Web3 API. func NewPublicNetAPI(cliCtx context.CLIContext) *PublicNetAPI { chainID := viper.GetString(flags.FlagChainID) // parse the chainID from a integer string From 0670d8a3bb79ae8941e6b64262030efcc3cc7c67 Mon Sep 17 00:00:00 2001 From: Federico Kunze Date: Thu, 28 May 2020 18:31:01 -0400 Subject: [PATCH 07/36] remove custom BlockNumber --- rpc/backend.go | 24 +++++++--- rpc/eth_api.go | 18 ++++---- rpc/filters.go | 123 ++++++++++++++++++++++++++++++++++++++++++++++++- rpc/types.go | 66 -------------------------- 4 files changed, 148 insertions(+), 83 deletions(-) delete mode 100644 rpc/types.go diff --git a/rpc/backend.go b/rpc/backend.go index 7e44cef8a..72c059602 100644 --- a/rpc/backend.go +++ b/rpc/backend.go @@ -12,6 +12,7 @@ import ( "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/rpc" ) // Backend implements the functionality needed to filter changes. @@ -19,11 +20,14 @@ import ( type Backend interface { // Used by block filter; also used for polling BlockNumber() (hexutil.Uint64, error) - // HeaderByNumber(blockNum BlockNumber) (ethtypes.Header, error) - GetBlockByNumber(blockNum BlockNumber, fullTx bool) (map[string]interface{}, error) + HeaderByNumber(blockNum rpc.BlockNumber) (*ethtypes.Header, error) + HeaderByHash(blockHash common.Hash) (*ethtypes.Header, error) + GetBlockByNumber(blockNum rpc.BlockNumber, fullTx bool) (map[string]interface{}, error) GetBlockByHash(hash common.Hash, fullTx bool) (map[string]interface{}, error) getEthBlockByNumber(height int64, fullTx bool) (map[string]interface{}, error) getGasLimit() (int64, error) + // returns the logs of a given block + GetLogs(blockHash common.Hash) ([][]*ethtypes.Log, error) // Used by pending transaction filter PendingTransactions() ([]*Transaction, error) @@ -72,14 +76,14 @@ func (e *EthermintBackend) BlockNumber() (hexutil.Uint64, error) { } // GetBlockByNumber returns the block identified by number. -func (e *EthermintBackend) GetBlockByNumber(blockNum BlockNumber, fullTx bool) (map[string]interface{}, error) { +func (e *EthermintBackend) GetBlockByNumber(blockNum rpc.BlockNumber, fullTx bool) (map[string]interface{}, error) { value := blockNum.Int64() return e.getEthBlockByNumber(value, fullTx) } // GetBlockByHash returns the block identified by hash. func (e *EthermintBackend) GetBlockByHash(hash common.Hash, fullTx bool) (map[string]interface{}, error) { - res, _, err := e.cliCtx.Query(fmt.Sprintf("custom/%s/%s/%s", types.ModuleName, evm.QueryHashToHeight, hash.Hex())) + res, height, err := e.cliCtx.Query(fmt.Sprintf("custom/%s/%s/%s", types.ModuleName, evm.QueryHashToHeight, hash.Hex())) if err != nil { return nil, err } @@ -89,6 +93,7 @@ func (e *EthermintBackend) GetBlockByHash(hash common.Hash, fullTx bool) (map[st return nil, err } + e.cliCtx = e.cliCtx.WithHeight(height) return e.getEthBlockByNumber(out.Number, fullTx) } @@ -169,11 +174,12 @@ func (e *EthermintBackend) getGasLimit() (int64, error) { } // GetTransactionLogs returns the logs given a transaction hash. +// It returns an error if there's an encoding error. +// If no logs are found for the tx hash, the error is nil. func (e *EthermintBackend) GetTransactionLogs(txHash common.Hash) ([]*ethtypes.Log, error) { - // do we need to use the block height somewhere? ctx := e.cliCtx - res, _, err := ctx.QueryWithData(fmt.Sprintf("custom/%s/%s/%s", types.ModuleName, evm.QueryTransactionLogs, txHash.Hex()), nil) + res, height, err := ctx.QueryWithData(fmt.Sprintf("custom/%s/%s/%s", types.ModuleName, evm.QueryTransactionLogs, txHash.Hex()), nil) if err != nil { return nil, err } @@ -183,6 +189,7 @@ func (e *EthermintBackend) GetTransactionLogs(txHash common.Hash) ([]*ethtypes.L return nil, err } + e.cliCtx = e.cliCtx.WithHeight(height) return out.Logs, nil } @@ -212,3 +219,8 @@ func (e *EthermintBackend) PendingTransactions() ([]*Transaction, error) { return transactions, nil } + + +func (e *EthermintBackend) GetLogs(blockHash common.Hash) ([][]*ethtypes.Log, error) { + e.cliCtx.Client. +} diff --git a/rpc/eth_api.go b/rpc/eth_api.go index 71c72bb69..a45224588 100644 --- a/rpc/eth_api.go +++ b/rpc/eth_api.go @@ -148,7 +148,7 @@ func (e *PublicEthAPI) BlockNumber() (hexutil.Uint64, error) { } // GetBalance returns the provided account's balance up to the provided block number. -func (e *PublicEthAPI) GetBalance(address common.Address, blockNum BlockNumber) (*hexutil.Big, error) { +func (e *PublicEthAPI) GetBalance(address common.Address, blockNum rpc.BlockNumber) (*hexutil.Big, error) { ctx := e.cliCtx.WithHeight(blockNum.Int64()) res, _, err := ctx.QueryWithData(fmt.Sprintf("custom/%s/balance/%s", types.ModuleName, address.Hex()), nil) if err != nil { @@ -166,7 +166,7 @@ func (e *PublicEthAPI) GetBalance(address common.Address, blockNum BlockNumber) } // GetStorageAt returns the contract storage at the given address, block number, and key. -func (e *PublicEthAPI) GetStorageAt(address common.Address, key string, blockNum BlockNumber) (hexutil.Bytes, error) { +func (e *PublicEthAPI) GetStorageAt(address common.Address, key string, blockNum rpc.BlockNumber) (hexutil.Bytes, error) { ctx := e.cliCtx.WithHeight(blockNum.Int64()) res, _, err := ctx.QueryWithData(fmt.Sprintf("custom/%s/storage/%s/%s", types.ModuleName, address.Hex(), key), nil) if err != nil { @@ -179,7 +179,7 @@ func (e *PublicEthAPI) GetStorageAt(address common.Address, key string, blockNum } // GetTransactionCount returns the number of transactions at the given address up to the given block number. -func (e *PublicEthAPI) GetTransactionCount(address common.Address, blockNum BlockNumber) (*hexutil.Uint64, error) { +func (e *PublicEthAPI) GetTransactionCount(address common.Address, blockNum rpc.BlockNumber) (*hexutil.Uint64, error) { ctx := e.cliCtx.WithHeight(blockNum.Int64()) // Get nonce (sequence) from account @@ -210,7 +210,7 @@ func (e *PublicEthAPI) GetBlockTransactionCountByHash(hash common.Hash) *hexutil } // GetBlockTransactionCountByNumber returns the number of transactions in the block identified by number. -func (e *PublicEthAPI) GetBlockTransactionCountByNumber(blockNum BlockNumber) *hexutil.Uint { +func (e *PublicEthAPI) GetBlockTransactionCountByNumber(blockNum rpc.BlockNumber) *hexutil.Uint { height := blockNum.Int64() return e.getBlockTransactionCountByNumber(height) } @@ -232,12 +232,12 @@ func (e *PublicEthAPI) GetUncleCountByBlockHash(hash common.Hash) hexutil.Uint { } // GetUncleCountByBlockNumber returns the number of uncles in the block idenfied by number. Always zero. -func (e *PublicEthAPI) GetUncleCountByBlockNumber(blockNum BlockNumber) hexutil.Uint { +func (e *PublicEthAPI) GetUncleCountByBlockNumber(blockNum rpc.BlockNumber) hexutil.Uint { return 0 } // GetCode returns the contract code at the given address and block number. -func (e *PublicEthAPI) GetCode(address common.Address, blockNumber BlockNumber) (hexutil.Bytes, error) { +func (e *PublicEthAPI) GetCode(address common.Address, blockNumber rpc.BlockNumber) (hexutil.Bytes, error) { ctx := e.cliCtx.WithHeight(blockNumber.Int64()) res, _, err := ctx.QueryWithData(fmt.Sprintf("custom/%s/%s/%s", types.ModuleName, evm.QueryCode, address.Hex()), nil) if err != nil { @@ -495,7 +495,7 @@ func (e *PublicEthAPI) GetBlockByHash(hash common.Hash, fullTx bool) (map[string } // GetBlockByNumber returns the block identified by number. -func (e *PublicEthAPI) GetBlockByNumber(blockNum BlockNumber, fullTx bool) (map[string]interface{}, error) { +func (e *PublicEthAPI) GetBlockByNumber(blockNum rpc.BlockNumber, fullTx bool) (map[string]interface{}, error) { return e.backend.GetBlockByNumber(blockNum, fullTx) } @@ -647,7 +647,7 @@ func (e *PublicEthAPI) GetTransactionByBlockHashAndIndex(hash common.Hash, idx h } // GetTransactionByBlockNumberAndIndex returns the transaction identified by number and index. -func (e *PublicEthAPI) GetTransactionByBlockNumberAndIndex(blockNum BlockNumber, idx hexutil.Uint) (*Transaction, error) { +func (e *PublicEthAPI) GetTransactionByBlockNumberAndIndex(blockNum rpc.BlockNumber, idx hexutil.Uint) (*Transaction, error) { value := blockNum.Int64() return e.getTransactionByBlockNumberAndIndex(value, idx) } @@ -782,7 +782,7 @@ type StorageResult struct { } // GetProof returns an account object with proof and any storage proofs -func (e *PublicEthAPI) GetProof(address common.Address, storageKeys []string, block BlockNumber) (*AccountResult, error) { +func (e *PublicEthAPI) GetProof(address common.Address, storageKeys []string, block rpc.BlockNumber) (*AccountResult, error) { e.cliCtx = e.cliCtx.WithHeight(int64(block)) path := fmt.Sprintf("custom/%s/%s/%s", types.ModuleName, evm.QueryAccount, address.Hex()) diff --git a/rpc/filters.go b/rpc/filters.go index 450d00b4e..e83c01f21 100644 --- a/rpc/filters.go +++ b/rpc/filters.go @@ -1,6 +1,7 @@ package rpc import ( + "fmt" "math/big" "time" @@ -8,6 +9,7 @@ import ( "github.com/ethereum/go-ethereum/core/bloombits" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth/filters" + "github.com/ethereum/go-ethereum/rpc" ) /* @@ -23,8 +25,6 @@ type Filter struct { hashes []common.Hash // filtered block or transaction hashes criteria filters.FilterCriteria - logs []*ethtypes.Log // filtered logs - matcher *bloombits.Matcher subscription bool // associated subscription in event system @@ -40,6 +40,99 @@ func NewFilter(backend Backend, filterType filters.Type, criteria filters.Filter } } +func (f *Filter) Unsubscribe() { + if !f.subscription { + return + } + + // switch f.typ { + // case: + // } + +} + +// Logs searches the blockchain for matching log entries, returning all from the +// first block that contains matches, updating the start of the filter accordingly. +func (f *Filter) Logs() ([]*ethtypes.Log, error) { + logs := []*ethtypes.Log{} + var err error + // If we're doing singleton block filtering, execute and return + if f.criteria.BlockHash != nil || f.criteria.BlockHash != (&common.Hash{}) { + header, err := f.backend.HeaderByHash(*f.criteria.BlockHash) + if err != nil { + return nil, err + } + if header == nil { + return nil, fmt.Errorf("unknown block header %s", f.criteria.BlockHash.String()) + } + return f.blockLogs(header) + } + + // Figure out the limits of the filter range + header, err := f.backend.HeaderByNumber(rpc.LatestBlockNumber) + if err != nil { + return nil, err + } + + if header == nil { + return nil, nil + } + head := header.Number.Uint64() + + if f.criteria.FromBlock.Int64() == -1 { + f.criteria.FromBlock = big.NewInt(int64(head)) + } + if f.criteria.ToBlock.Int64() == -1 { + f.criteria.ToBlock = big.NewInt(int64(head)) + } + + for i := f.criteria.FromBlock.Int64(); i <= f.criteria.ToBlock.Int64(); i++ { + block, err := f.backend.GetBlockByNumber(rpc.BlockNumber(i), true) + if err != nil { + return logs, err + } + + txs, ok := block["transactions"].([]common.Hash) + if !ok || len(txs) == 0 { + continue + } + + logsMatched, err := f.checkMatches(txs) + if err != nil { + return logs, err + } + + logs = append(logs, logsMatched...) + } + + return logs, nil +} + +// blockLogs returns the logs matching the filter criteria within a single block. +func (f *Filter) blockLogs(header *ethtypes.Header) ([]*ethtypes.Log, error) { + if !bloomFilter(header.Bloom, f.criteria.Addresses, f.criteria.Topics) { + return []*ethtypes.Log{}, nil + } + + return f.checkMatches(header) +} + +func (f *Filter) checkMatches(transactions []common.Hash) ([]*ethtypes.Log, error) { + unfiltered := []*ethtypes.Log{} + for _, tx := range transactions { + logs, err := f.backend.GetTransactionLogs(tx) + if err != nil { + // ignore error if transaction didn't set any logs (eg: when tx type is not + // MsgEthereumTx or MsgEthermint) + continue + } + + unfiltered = append(unfiltered, logs...) + } + + return filterLogs(unfiltered, f.criteria.FromBlock, f.criteria.ToBlock, f.criteria.Addresses, f.criteria.Topics), nil +} + // filterLogs creates a slice of logs matching the given criteria. // [] -> anything // [A] -> A in first position of log topics, anything after @@ -89,3 +182,29 @@ func includes(addresses []common.Address, a common.Address) bool { return false } + +func bloomFilter(bloom ethtypes.Bloom, addresses []common.Address, topics [][]common.Hash) bool { + var included bool + if len(addresses) > 0 { + for _, addr := range addresses { + if ethtypes.BloomLookup(bloom, addr) { + included = true + break + } + } + if !included { + return false + } + } + + for _, sub := range topics { + included = len(sub) == 0 // empty rule set == wildcard + for _, topic := range sub { + if ethtypes.BloomLookup(bloom, topic) { + included = true + break + } + } + } + return included +} diff --git a/rpc/types.go b/rpc/types.go deleted file mode 100644 index b21314c0e..000000000 --- a/rpc/types.go +++ /dev/null @@ -1,66 +0,0 @@ -package rpc - -import ( - "fmt" - "math" - "math/big" - "strings" - - "github.com/ethereum/go-ethereum/common/hexutil" -) - -// BlockNumber represents decoding hex string to block values -type BlockNumber int64 - -const ( - // LatestBlockNumber mapping from "latest" to 0 for tm query - LatestBlockNumber = BlockNumber(0) - - // EarliestBlockNumber mapping from "earliest" to 1 for tm query (earliest query not supported) - EarliestBlockNumber = BlockNumber(1) -) - -func NewBlockNumber(n *big.Int) BlockNumber { - return BlockNumber(n.Int64()) -} - -// UnmarshalJSON parses the given JSON fragment into a BlockNumber. It supports: -// - "latest", "earliest" or "pending" as string arguments -// - the block number -// Returned errors: -// - an invalid block number error when the given argument isn't a known strings -// - an out of range error when the given block number is either too little or too large -func (bn *BlockNumber) UnmarshalJSON(data []byte) error { - input := strings.TrimSpace(string(data)) - if len(input) >= 2 && input[0] == '"' && input[len(input)-1] == '"' { - input = input[1 : len(input)-1] - } - - switch input { - case "earliest": - *bn = EarliestBlockNumber - return nil - case "latest": - *bn = LatestBlockNumber - return nil - case "pending": - *bn = LatestBlockNumber - return nil - } - - blckNum, err := hexutil.DecodeUint64(input) - if err != nil { - return err - } - if blckNum > math.MaxInt64 { - return fmt.Errorf("blocknumber too high") - } - - *bn = BlockNumber(blckNum) - return nil -} - -// Int64 converts block number to primitive type -func (bn BlockNumber) Int64() int64 { - return (int64)(bn) -} From 08542aad0c4a1da2992ddfb67b449b7c8a062b6e Mon Sep 17 00:00:00 2001 From: Federico Kunze Date: Mon, 1 Jun 2020 11:27:55 -0400 Subject: [PATCH 08/36] wip: refactor rpc --- rpc/backend.go | 37 +++++++++++++++++++++++-------------- rpc/eth_api.go | 5 +++-- x/evm/types/events.go | 2 -- 3 files changed, 26 insertions(+), 18 deletions(-) diff --git a/rpc/backend.go b/rpc/backend.go index 72c059602..95efc5727 100644 --- a/rpc/backend.go +++ b/rpc/backend.go @@ -51,18 +51,6 @@ func NewEthermintBackend(cliCtx context.CLIContext) *EthermintBackend { } } -// // HeaderByNumber returns the current block number. -// func (e *EthermintBackend) HeaderByNumber(blockNum BlockNumber) (ethtypes.Header, error) -// res, _, err := e.cliCtx.QueryWithData(fmt.Sprintf("custom/%s/blockNumber", types.ModuleName), nil) -// if err != nil { -// return hexutil.Uint64(0), err -// } - -// var out types.QueryResBlockNumber -// e.cliCtx.Codec.MustUnmarshalJSON(res, &out) -// return hexutil.Uint64(out.Number), nil -// } - // BlockNumber returns the current block number. func (e *EthermintBackend) BlockNumber() (hexutil.Uint64, error) { res, _, err := e.cliCtx.QueryWithData(fmt.Sprintf("custom/%s/blockNumber", types.ModuleName), nil) @@ -145,6 +133,28 @@ func (e *EthermintBackend) getEthBlockByNumber(height int64, fullTx bool) (map[s var out types.QueryBloomFilter e.cliCtx.Codec.MustUnmarshalJSON(res, &out) + ethHeader := ðtypes.Header{ + ParentHash: common.BytesToHash(header.LastBlockID.Hash.Bytes()), + UncleHash: common.Hash{}, + Coinbase: common.Address{}, + Root: common.BytesToHash(header.AppHash), + TxHash: common.BytesToHash(header.DataHash), + ReceiptHash: common.Hash{}, + Bloom: out.Bloom, + Difficulty: nil, + Number: big.NewInt(height), + GasLimit: uint64(gasLimit), + GasUsed: uint64(gasUsed.Int64()), + Time: uint64(header.Time.Unix()), + Extra: nil, + MixDigest: common.Hash{}, + Nonce: ethtypes.BlockNonce{}, + } + + ethtypes.NewTransaction() + + ethBlock := ethtypes.NewBlock(ethHeader, transactions, nil, nil) + return formatBlock(header, block.Block.Size(), gasLimit, gasUsed, transactions, out.Bloom), nil } @@ -220,7 +230,6 @@ func (e *EthermintBackend) PendingTransactions() ([]*Transaction, error) { return transactions, nil } - func (e *EthermintBackend) GetLogs(blockHash common.Hash) ([][]*ethtypes.Log, error) { - e.cliCtx.Client. + return [][]*ethtypes.Log{}, nil } diff --git a/rpc/eth_api.go b/rpc/eth_api.go index a45224588..f5244c561 100644 --- a/rpc/eth_api.go +++ b/rpc/eth_api.go @@ -532,7 +532,8 @@ func convertTransactionsToRPC(cliCtx context.CLIContext, txs []tmtypes.Tx, block for i, tx := range txs { ethTx, err := bytesToEthTx(cliCtx, tx) if err != nil { - return nil, nil, err + // continue to + continue } // TODO: Remove gas usage calculation if saving gasUsed per block gasUsed.Add(gasUsed, ethTx.Fee()) @@ -573,7 +574,7 @@ func bytesToEthTx(cliCtx context.CLIContext, bz []byte) (*types.MsgEthereumTx, e ethTx, ok := stdTx.(types.MsgEthereumTx) if !ok { - return nil, fmt.Errorf("invalid transaction type, must be an amino encoded Ethereum transaction") + return nil, fmt.Errorf("invalid transaction type %T, expected MsgEthereumTx", stdTx) } return ðTx, nil } diff --git a/x/evm/types/events.go b/x/evm/types/events.go index 72f6c06a2..e5dca425a 100644 --- a/x/evm/types/events.go +++ b/x/evm/types/events.go @@ -5,8 +5,6 @@ const ( EventTypeEthermint = TypeMsgEthermint EventTypeEthereumTx = TypeMsgEthereumTx - EventTypeLog = "log" - AttributeKeyContractAddress = "contract" AttributeKeyRecipient = "recipient" AttributeValueCategory = ModuleName From 44ae722ce09dd19b0c9ce3304446dca68284a906 Mon Sep 17 00:00:00 2001 From: Federico Kunze Date: Mon, 1 Jun 2020 18:53:14 -0400 Subject: [PATCH 09/36] HeaderByNumber and HeaderByHash --- rpc/backend.go | 116 ++++++++++++++++++++++++++++++++++++---------- rpc/eth_api.go | 2 +- rpc/filter_api.go | 54 +++++++++++---------- rpc/filters.go | 31 +++++++++++-- 4 files changed, 145 insertions(+), 58 deletions(-) diff --git a/rpc/backend.go b/rpc/backend.go index 95efc5727..e7b6215c2 100644 --- a/rpc/backend.go +++ b/rpc/backend.go @@ -37,6 +37,8 @@ type Backend interface { // TODO: Bloom methods } +var _ Backend = (*EthermintBackend)(nil) + // EthermintBackend implements the Backend interface type EthermintBackend struct { cliCtx context.CLIContext @@ -85,6 +87,63 @@ func (e *EthermintBackend) GetBlockByHash(hash common.Hash, fullTx bool) (map[st return e.getEthBlockByNumber(out.Number, fullTx) } +// HeaderByNumber returns the block header identified by height. +func (e *EthermintBackend) HeaderByNumber(blockNum rpc.BlockNumber) (*ethtypes.Header, error) { + return e.getBlockHeader(blockNum.Int64()) +} + +// HeaderByHash returns the block header identified by hash. +func (e *EthermintBackend) HeaderByHash(blockHash common.Hash) (*ethtypes.Header, error) { + res, height, err := e.cliCtx.Query(fmt.Sprintf("custom/%s/%s/%s", types.ModuleName, evm.QueryHashToHeight, blockHash.Hex())) + if err != nil { + return nil, err + } + var out types.QueryResBlockNumber + if err := e.cliCtx.Codec.UnmarshalJSON(res, &out); err != nil { + return nil, err + } + + e.cliCtx = e.cliCtx.WithHeight(height) + return e.getBlockHeader(out.Number) +} + +func (e *EthermintBackend) getBlockHeader(height int64) (*ethtypes.Header, error) { + block, err := e.cliCtx.Client.Block(&height) + if err != nil { + return nil, err + } + + res, _, err := e.cliCtx.Query(fmt.Sprintf("custom/%s/%s/%s", types.ModuleName, evm.QueryLogsBloom, strconv.FormatInt(height, 10))) + if err != nil { + return nil, err + } + + var bloomRes types.QueryBloomFilter + e.cliCtx.Codec.MustUnmarshalJSON(res, &bloomRes) + + header := block.Block.Header + ethHeader := ðtypes.Header{ + ParentHash: common.BytesToHash(header.LastBlockID.Hash.Bytes()), + UncleHash: common.Hash{}, + Coinbase: common.Address{}, + Root: common.BytesToHash(header.AppHash), + TxHash: common.BytesToHash(header.DataHash), + ReceiptHash: common.Hash{}, + Bloom: bloomRes.Bloom, + Difficulty: nil, + Number: big.NewInt(height), + // TODO: block gas limit + // GasLimit: uint64(gasLimit), + // GasUsed: uint64(gasUsed.Int64()), + Time: uint64(header.Time.Unix()), + Extra: nil, + MixDigest: common.Hash{}, + Nonce: ethtypes.BlockNonce{}, + } + + return ethHeader, nil +} + func (e *EthermintBackend) getEthBlockByNumber(height int64, fullTx bool) (map[string]interface{}, error) { // Remove this check when 0 query is fixed ref: (https://github.com/tendermint/tendermint/issues/4014) var blkNumPtr *int64 @@ -132,29 +191,6 @@ func (e *EthermintBackend) getEthBlockByNumber(height int64, fullTx bool) (map[s var out types.QueryBloomFilter e.cliCtx.Codec.MustUnmarshalJSON(res, &out) - - ethHeader := ðtypes.Header{ - ParentHash: common.BytesToHash(header.LastBlockID.Hash.Bytes()), - UncleHash: common.Hash{}, - Coinbase: common.Address{}, - Root: common.BytesToHash(header.AppHash), - TxHash: common.BytesToHash(header.DataHash), - ReceiptHash: common.Hash{}, - Bloom: out.Bloom, - Difficulty: nil, - Number: big.NewInt(height), - GasLimit: uint64(gasLimit), - GasUsed: uint64(gasUsed.Int64()), - Time: uint64(header.Time.Unix()), - Extra: nil, - MixDigest: common.Hash{}, - Nonce: ethtypes.BlockNonce{}, - } - - ethtypes.NewTransaction() - - ethBlock := ethtypes.NewBlock(ethHeader, transactions, nil, nil) - return formatBlock(header, block.Block.Size(), gasLimit, gasUsed, transactions, out.Bloom), nil } @@ -230,6 +266,38 @@ func (e *EthermintBackend) PendingTransactions() ([]*Transaction, error) { return transactions, nil } +// GetLogs returns all the logs from all the ethreum transactions in a block. func (e *EthermintBackend) GetLogs(blockHash common.Hash) ([][]*ethtypes.Log, error) { - return [][]*ethtypes.Log{}, nil + res, _, err := e.cliCtx.Query(fmt.Sprintf("custom/%s/%s/%s", types.ModuleName, evm.QueryHashToHeight, blockHash.Hex())) + if err != nil { + return nil, err + } + + var out types.QueryResBlockNumber + if err := e.cliCtx.Codec.UnmarshalJSON(res, &out); err != nil { + return nil, err + } + + block, err := e.cliCtx.Client.Block(&out.Number) + if err != nil { + return nil, err + } + + var blockLogs = [][]*ethtypes.Log{} + for _, tx := range block.Block.Txs { + // NOTE: we query the state in case the tx result logs are not persisted after an upgrade. + res, _, err := e.cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s/%s", types.ModuleName, evm.QueryTransactionLogs, common.BytesToHash(tx.Hash()).Hex()), nil) + if err != nil { + continue + } + + out := new(types.QueryETHLogs) + if err := e.cliCtx.Codec.UnmarshalJSON(res, &out); err != nil { + return nil, err + } + + blockLogs = append(blockLogs, out.Logs) + } + + return blockLogs, nil } diff --git a/rpc/eth_api.go b/rpc/eth_api.go index 7da6e2049..b52cfcfed 100644 --- a/rpc/eth_api.go +++ b/rpc/eth_api.go @@ -533,7 +533,7 @@ func convertTransactionsToRPC(cliCtx context.CLIContext, txs []tmtypes.Tx, block for i, tx := range txs { ethTx, err := bytesToEthTx(cliCtx, tx) if err != nil { - // continue to + // continue to next transaction in case it's not a MsgEthereumTx continue } // TODO: Remove gas usage calculation if saving gasUsed per block diff --git a/rpc/filter_api.go b/rpc/filter_api.go index 8e0d1b877..c0c447c23 100644 --- a/rpc/filter_api.go +++ b/rpc/filter_api.go @@ -374,32 +374,30 @@ func (api *PublicFilterAPI) NewFilter(criteria filters.FilterCriteria) (rpc.ID, // GetLogs returns logs matching the given argument that are stored within the state. // // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getlogs -func (api *PublicFilterAPI) GetLogs(criteria filters.FilterCriteria) ([]*ethtypes.Log, error) { - filter := NewFilter(api.backend, filters.LogsSubscription, criteria) - return filter.getFilterLogs() - // var filter *Filter - // if crit.BlockHash != nil { - // // Block filter requested, construct a single-shot filter - // filter = NewBlockFilter(api.backend, *crit.BlockHash, crit.Addresses, crit.Topics) - // } else { - // // Convert the RPC block numbers into internal representations - // begin := rpc.LatestBlockNumber.Int64() - // if crit.FromBlock != nil { - // begin = crit.FromBlock.Int64() - // } - // end := rpc.LatestBlockNumber.Int64() - // if crit.ToBlock != nil { - // end = crit.ToBlock.Int64() - // } - // // Construct the range filter - // filter = NewRangeFilter(api.backend, begin, end, crit.Addresses, crit.Topics) - // } - // // Run the filter and return all the logs - // logs, err := filter.Logs(ctx) - // if err != nil { - // return nil, err - // } - // return returnLogs(logs), err +func (api *PublicFilterAPI) GetLogs(ctx context.Context, crit filters.FilterCriteria) ([]*ethtypes.Log, error) { + var filter *Filter + if crit.BlockHash != nil { + // Block filter requested, construct a single-shot filter + filter = NewBlockFilter(api.backend, *crit.BlockHash, crit.Addresses, crit.Topics) + } else { + // Convert the RPC block numbers into internal representations + begin := rpc.LatestBlockNumber.Int64() + if crit.FromBlock != nil { + begin = crit.FromBlock.Int64() + } + end := rpc.LatestBlockNumber.Int64() + if crit.ToBlock != nil { + end = crit.ToBlock.Int64() + } + // Construct the range filter + filter = NewRangeFilter(api.backend, begin, end, crit.Addresses, crit.Topics) + } + // Run the filter and return all the logs + logs, err := filter.Logs(ctx) + if err != nil { + return nil, err + } + return returnLogs(logs), err } // UninstallFilter removes the filter with the given filter id. @@ -424,7 +422,7 @@ func (api *PublicFilterAPI) UninstallFilter(id rpc.ID) bool { // If the filter could not be found an empty array of logs is returned. // // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getfilterlogs -func (api *PublicFilterAPI) GetFilterLogs(id rpc.ID) ([]*ethtypes.Log, error) { +func (api *PublicFilterAPI) GetFilterLogs(ctx context.Context, id rpc.ID) ([]*ethtypes.Log, error) { api.filtersMu.Lock() f, found := api.filters[id] api.filtersMu.Unlock() @@ -437,7 +435,7 @@ func (api *PublicFilterAPI) GetFilterLogs(id rpc.ID) ([]*ethtypes.Log, error) { return nil, fmt.Errorf("filter %s doesn't have a LogsSubscription type", id) } - return api.filters[id].Logs() + return api.filters[id].Logs(ctx) } // GetFilterChanges returns the logs for the filter with the given id since diff --git a/rpc/filters.go b/rpc/filters.go index e83c01f21..59b9b3f6a 100644 --- a/rpc/filters.go +++ b/rpc/filters.go @@ -1,6 +1,7 @@ package rpc import ( + "context" "fmt" "math/big" "time" @@ -40,20 +41,24 @@ func NewFilter(backend Backend, filterType filters.Type, criteria filters.Filter } } +// TODO: func (f *Filter) Unsubscribe() { if !f.subscription { return } - // switch f.typ { - // case: - // } + switch f.typ { + case filters.LogsSubscription: + + case filters.BlocksSubscription: + + } } // Logs searches the blockchain for matching log entries, returning all from the // first block that contains matches, updating the start of the filter accordingly. -func (f *Filter) Logs() ([]*ethtypes.Log, error) { +func (f *Filter) Logs(_ context.Context) ([]*ethtypes.Log, error) { logs := []*ethtypes.Log{} var err error // If we're doing singleton block filtering, execute and return @@ -114,9 +119,25 @@ func (f *Filter) blockLogs(header *ethtypes.Header) ([]*ethtypes.Log, error) { return []*ethtypes.Log{}, nil } - return f.checkMatches(header) + logsList, err := f.backend.GetLogs(header.Hash()) + if err != nil { + return []*ethtypes.Log{}, err + } + + var unfiltered []*ethtypes.Log + for _, logs := range logsList { + unfiltered = append(unfiltered, logs...) + } + logs := filterLogs(unfiltered, nil, nil, f.criteria.Addresses, f.criteria.Topics) + if len(logs) == 0 { + return []*ethtypes.Log{}, nil + } + return logs, nil } +// checkMatches checks if the logs from the a list of transactions transaction +// contain any log events that match the filter criteria. This function is +// called when the bloom filter signals a potential match. func (f *Filter) checkMatches(transactions []common.Hash) ([]*ethtypes.Log, error) { unfiltered := []*ethtypes.Log{} for _, tx := range transactions { From bc4ddf35a55715f652b72105b540900827b678d1 Mon Sep 17 00:00:00 2001 From: Federico Kunze Date: Thu, 4 Jun 2020 19:40:48 +0200 Subject: [PATCH 10/36] update Tendermint event system --- rpc/backend.go | 2 +- rpc/filter_api.go | 86 ++++++++----- rpc/filter_system.go | 281 ++++++++++++++++++++++++++++++++++++++----- rpc/filters.go | 6 + 4 files changed, 310 insertions(+), 65 deletions(-) diff --git a/rpc/backend.go b/rpc/backend.go index 9eb6f94f0..61b27b0ae 100644 --- a/rpc/backend.go +++ b/rpc/backend.go @@ -113,7 +113,7 @@ func (e *EthermintBackend) getBlockHeader(height int64) (*ethtypes.Header, error return nil, err } - res, _, err := e.cliCtx.Query(fmt.Sprintf("custom/%s/%s/%s", types.ModuleName, evm.QueryLogsBloom, strconv.FormatInt(height, 10))) + res, _, err := e.cliCtx.Query(fmt.Sprintf("custom/%s/%s/%s", types.ModuleName, evm.QueryBloom, strconv.FormatInt(height, 10))) if err != nil { return nil, err } diff --git a/rpc/filter_api.go b/rpc/filter_api.go index c0c447c23..a559509fb 100644 --- a/rpc/filter_api.go +++ b/rpc/filter_api.go @@ -24,6 +24,17 @@ var ( deadline = 5 * time.Minute // consider a filter inactive if it has not been polled for within deadline ) +// filter is a helper struct that holds meta information over the filter type +// and associated subscription in the event system. +type filter struct { + typ filters.Type + deadline *time.Timer // filter is inactiv when deadline triggers + hashes []common.Hash + crit filters.FilterCriteria + logs []*types.Log + s *Subscription // associated subscription in event system +} + // PublicFilterAPI offers support to create and manage filters. This will allow external clients to retrieve various // information related to the Ethereum protocol such as blocks, transactions and logs. type PublicFilterAPI struct { @@ -37,15 +48,14 @@ type PublicFilterAPI struct { } // NewPublicFilterAPI returns a new PublicFilterAPI instance. -func NewPublicFilterAPI(cliCtx clientcontext.CLIContext, backend Backend, timeoutSec int64) *PublicFilterAPI { +func NewPublicFilterAPI(cliCtx clientcontext.CLIContext, backend Backend) *PublicFilterAPI { api := &PublicFilterAPI{ cliCtx: cliCtx, backend: backend, filters: make(map[rpc.ID]*Filter), events: &TendermintEvents{ - ctx: context.Background(), - client: cliCtx.Client, - timeout: time.Duration(timeoutSec) * time.Second, + ctx: context.Background(), + client: cliCtx.Client, }, } @@ -57,7 +67,7 @@ func NewPublicFilterAPI(cliCtx clientcontext.CLIContext, backend Backend, timeou // timeoutLoop runs every 5 minutes and deletes filters that have not been recently used. // Tt is started when the api is created. func (api *PublicFilterAPI) timeoutLoop() { - ticker := time.NewTicker(5 * time.Minute) + ticker := time.NewTicker(deadline) defer ticker.Stop() for { <-ticker.C @@ -162,32 +172,31 @@ func (api *PublicFilterAPI) timeoutLoop() { // // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newblockfilter func (api *PublicFilterAPI) NewBlockFilter() rpc.ID { - subscriberID := rpc.NewID() - eventCh, err := api.events.SubscribeNewHeads(subscriberID) + headerSub, err := api.events.SubscribeNewHeads() if err != nil { // return an empty id return rpc.ID("") } - ctx, cancelFn := context.WithTimeout(context.Background(), api.events.GetTimeout()) + ctx, cancelFn := context.WithTimeout(context.Background(), deadline) defer cancelFn() api.events = api.events.WithContext(ctx) api.filtersMu.Lock() - api.filters[subscriberID] = NewFilter(api.backend, filters.BlocksSubscription, filters.FilterCriteria{}) + api.filters[headerSub.ID()] = NewFilter(api.backend, filters.BlocksSubscription, filters.FilterCriteria{}) api.filtersMu.Unlock() go func() { for { select { - case event := <-eventCh: + case event := <-headerSub.eventChannel: evHeader, ok := event.Data.(tmtypes.EventDataNewBlockHeader) if !ok { return } api.filtersMu.Lock() - if f, found := api.filters[subscriberID]; found { + if f, found := api.filters[headerSub.ID()]; found { f.hashes = append(f.hashes, common.BytesToHash(evHeader.Header.Hash())) } api.filtersMu.Unlock() @@ -195,7 +204,7 @@ func (api *PublicFilterAPI) NewBlockFilter() rpc.ID { } }() - return subscriberID + return headerSub.ID() } // NewHeads send a notification each time a new (header) block is appended to the chain. @@ -205,22 +214,21 @@ func (api *PublicFilterAPI) NewHeads(ctx context.Context) (*rpc.Subscription, er return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported } - ctx, cancelFn := context.WithTimeout(ctx, api.events.GetTimeout()) + ctx, cancelFn := context.WithTimeout(ctx, deadline) defer cancelFn() api.events = api.events.WithContext(ctx) rpcSub := notifier.CreateSubscription() - var err error - go func() { - eventCh, err := api.events.SubscribeNewHeads(rpcSub.ID) - if err != nil { - return - } + headersSub, err := api.events.SubscribeNewHeads() + if err != nil { + return nil, err + } + go func() { for { select { - case event := <-eventCh: + case event := <-headersSub.eventChannel: evHeader, ok := event.Data.(tmtypes.EventDataNewBlockHeader) if !ok { err = fmt.Errorf("invalid event data %T, expected %s", event.Data, tmtypes.EventNewBlockHeader) @@ -228,10 +236,10 @@ func (api *PublicFilterAPI) NewHeads(ctx context.Context) (*rpc.Subscription, er } notifier.Notify(rpcSub.ID, evHeader.Header) case <-rpcSub.Err(): - err = api.events.UnsubscribeHeads(rpcSub.ID) + err = headersSub.Unsubscribe(ctx, api.cliCtx.Client) return case <-notifier.Closed(): - err = api.events.UnsubscribeHeads(rpcSub.ID) + err = headersSub.Unsubscribe(ctx, api.cliCtx.Client) return } } @@ -247,7 +255,7 @@ func (api *PublicFilterAPI) Logs(ctx context.Context, crit filters.FilterCriteri return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported } - ctx, cancelFn := context.WithTimeout(context.Background(), api.events.GetTimeout()) + ctx, cancelFn := context.WithTimeout(context.Background(), deadline) defer cancelFn() api.events = api.events.WithContext(ctx) @@ -256,7 +264,7 @@ func (api *PublicFilterAPI) Logs(ctx context.Context, crit filters.FilterCriteri rpcSub = notifier.CreateSubscription() ) - eventCh, err := api.events.SubscribeLogs(rpcSub.ID) + logsSub, err := api.events.SubscribeLogs(crit) if err != nil { return nil, err } @@ -264,7 +272,7 @@ func (api *PublicFilterAPI) Logs(ctx context.Context, crit filters.FilterCriteri go func() { for { select { - case event := <-eventCh: + case event := <-logsSub.eventChannel: // filter only events from EVM module txs _, isMsgEthermint := event.Events[evmtypes.TypeMsgEthermint] _, isMsgEthereumTx := event.Events[evmtypes.TypeMsgEthereumTx] @@ -292,10 +300,10 @@ func (api *PublicFilterAPI) Logs(ctx context.Context, crit filters.FilterCriteri notifier.Notify(rpcSub.ID, &log) } case <-rpcSub.Err(): // client send an unsubscribe request - err = api.events.UnsubscribeLogs(rpcSub.ID) + err = logsSub.Unsubscribe(ctx, api.cliCtx.Client) return case <-notifier.Closed(): // connection dropped - err = api.events.UnsubscribeLogs(rpcSub.ID) + err = logsSub.Unsubscribe(ctx, api.cliCtx.Client) return } } @@ -319,12 +327,12 @@ func (api *PublicFilterAPI) Logs(ctx context.Context, crit filters.FilterCriteri // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newfilter func (api *PublicFilterAPI) NewFilter(criteria filters.FilterCriteria) (rpc.ID, error) { subscriberID := rpc.NewID() - eventCh, err := api.events.SubscribeLogs(subscriberID) + logsSub, err := api.events.SubscribeLogs(criteria) if err != nil { return rpc.ID(""), err } - ctx, cancelFn := context.WithTimeout(context.Background(), api.events.GetTimeout()) + ctx, cancelFn := context.WithTimeout(context.Background(), deadline) defer cancelFn() api.events = api.events.WithContext(ctx) @@ -336,7 +344,7 @@ func (api *PublicFilterAPI) NewFilter(criteria filters.FilterCriteria) (rpc.ID, go func() { for { select { - case event := <-eventCh: + case event := <-logsSub.eventChannel: // filter only events from EVM module txs _, isMsgEthermint := event.Events[evmtypes.TypeMsgEthermint] _, isMsgEthereumTx := event.Events[evmtypes.TypeMsgEthereumTx] @@ -428,11 +436,11 @@ func (api *PublicFilterAPI) GetFilterLogs(ctx context.Context, id rpc.ID) ([]*et api.filtersMu.Unlock() if !found { - return nil, fmt.Errorf("filter %s not found", id) + return returnLogs(nil), fmt.Errorf("filter %s not found", id) } if f.typ != filters.LogsSubscription { - return nil, fmt.Errorf("filter %s doesn't have a LogsSubscription type", id) + return returnLogs(nil), fmt.Errorf("filter %s doesn't have a LogsSubscription type: got %d", id, f.typ) } return api.filters[id].Logs(ctx) @@ -454,6 +462,14 @@ func (api *PublicFilterAPI) GetFilterChanges(id rpc.ID) (interface{}, error) { return nil, fmt.Errorf("filter %s not found", id) } + if !f.deadline.Stop() { + // timer expired but filter is not yet removed in timeout loop + // receive timer value and reset timer + <-f.deadline.C + } + f.deadline.Reset(deadline) + + var err error switch f.typ { case filters.PendingTransactionsSubscription, filters.BlocksSubscription: hashes := f.hashes @@ -461,6 +477,12 @@ func (api *PublicFilterAPI) GetFilterChanges(id rpc.ID) (interface{}, error) { return returnHashes(hashes), nil case filters.LogsSubscription, filters.MinedAndPendingLogsSubscription: logs := f.logs + if len(logs) == 0 { + logs, err = f.Logs(context.Background()) + if err != nil { + return returnLogs(nil), err + } + } f.logs = nil return returnLogs(logs), nil default: diff --git a/rpc/filter_system.go b/rpc/filter_system.go index 04d38f169..fe59adac2 100644 --- a/rpc/filter_system.go +++ b/rpc/filter_system.go @@ -2,8 +2,10 @@ package rpc import ( "context" + "fmt" "time" + evmtypes "github.com/cosmos/ethermint/x/evm/types" rpcclient "github.com/tendermint/tendermint/rpc/client" coretypes "github.com/tendermint/tendermint/rpc/core/types" tmtypes "github.com/tendermint/tendermint/types" @@ -17,21 +19,70 @@ import ( // subscription which match the subscription criteria. type EventSystem interface { WithContext(ctx context.Context) EventSystem - GetTimeout() time.Duration - SubscribeLogs(subscriberID rpc.ID) (eventCh <-chan coretypes.ResultEvent, err error) - UnsubscribeLogs(subscriberID rpc.ID) (err error) - SubscribeNewHeads(subscriberID rpc.ID) (eventCh <-chan coretypes.ResultEvent, err error) - UnsubscribeHeads(subscriberID rpc.ID) (err error) + SubscribeLogs(crit filters.FilterCriteria) (*Subscription, error) + SubscribeNewHeads() (*Subscription, error) // SubscribePendingTxs(hashes chan []common.Hash) *filters.Subscription } +type subscription struct { + id rpc.ID + typ filters.Type + created time.Time + logsCrit filters.FilterCriteria + installed chan struct{} // closed when the filter is installed + err chan error // closed when the filter is uninstalled +} + +func (s subscription) event() string { + switch s.typ { + case filters.LogsSubscription, filters.PendingTransactionsSubscription, filters.MinedAndPendingLogsSubscription, filters.PendingLogsSubscription: + return tmtypes.EventTx + case filters.BlocksSubscription: + return tmtypes.EventNewBlockHeader + default: + return "" + } +} + var _ EventSystem = &TendermintEvents{} // TendermintEvents implements the EventSystem using Tendermint's RPC client. type TendermintEvents struct { - ctx context.Context - client rpcclient.Client - timeout time.Duration + ctx context.Context + client rpcclient.Client + + // Subscriptions + // txsSub *Subscription // Subscription for new transaction event + logsSub *Subscription // Subscription for new log event + // rmLogsSub *Subscription // Subscription for removed log event + // pendingLogsSub *Subscription // Subscription for pending log event + chainSub *Subscription // Subscription for new chain event + + // Channels + install chan *subscription // install filter for event notification + uninstall chan *subscription // remove filter for event notification + eventsChannel <-chan coretypes.ResultEvent // channel to receive tendermint event results +} + +// NewTendermintEvents creates a new manager that listens for event on the given mux, +// parses and filters them. It uses the all map to retrieve filter changes. The +// work loop holds its own index that is used to forward events to filters. +// +// The returned manager has a loop that needs to be stopped with the Stop function +// or by stopping the given mux. +func NewTendermintEvents(ctx context.Context, client rpcclient.Client) *TendermintEvents { + te := &TendermintEvents{ + ctx: ctx, + client: client, + } + + // Subscribe events + // te.txsSub = te.SubscribeNewTxsEvent(m.txsCh) + te.logsSub, _ = te.SubscribeLogs(filters.FilterCriteria{}) + te.chainSub, _ = te.SubscribeNewHeads() + + go te.eventLoop() + return te } // WithContext sets the a given context to the @@ -40,40 +91,206 @@ func (te *TendermintEvents) WithContext(ctx context.Context) EventSystem { return te } -// GetTimeout returns the default timeout in seconds -func (te TendermintEvents) GetTimeout() time.Duration { - return te.timeout +func (te TendermintEvents) subscribe(sub *subscription) (*Subscription, error) { + var err error + subscription := &Subscription{ + subscription: sub, + } + subscription.eventChannel, err = te.client.Subscribe(te.ctx, string(sub.id), tmtypes.QueryForEvent(sub.event()).String()) + return subscription, err } -// SubscribeLogs subscribes to new incoming MsgEthereumTx or MsgEthermint transactions -func (te TendermintEvents) SubscribeLogs(subscriberID rpc.ID) (eventCh <-chan coretypes.ResultEvent, err error) { - return te.client.Subscribe( - te.ctx, string(subscriberID), - tmtypes.QueryForEvent(tmtypes.EventTx).String(), - ) +// SubscribeLogs creates a subscription that will write all logs matching the +// given criteria to the given logs channel. Default value for the from and to +// block is "latest". If the fromBlock > toBlock an error is returned. +func (te *TendermintEvents) SubscribeLogs(crit filters.FilterCriteria) (*Subscription, error) { + var from, to rpc.BlockNumber + if crit.FromBlock == nil { + from = rpc.LatestBlockNumber + } else { + from = rpc.BlockNumber(crit.FromBlock.Int64()) + } + if crit.ToBlock == nil { + to = rpc.LatestBlockNumber + } else { + to = rpc.BlockNumber(crit.ToBlock.Int64()) + } + + switch { + // // only interested in pending logs + // case from == rpc.PendingBlockNumber && to == rpc.PendingBlockNumber: + // return te.subscribePendingLogs(crit, logs) + + // only interested in new mined logs, mined logs within a specific block range, or + // logs from a specific block number to new mined blocks + case (from == rpc.LatestBlockNumber && to == rpc.LatestBlockNumber), + (from >= 0 && to >= 0 && to >= from): + return te.subscribeLogs(crit) + + // // interested in mined logs from a specific block number, new logs and pending logs + // case from >= rpc.LatestBlockNumber && to == rpc.PendingBlockNumber: + // return te.subscribeMinedPendingLogs(crit, logs) + + default: + return nil, fmt.Errorf("invalid from and to block combination: from > to (%d > %d)", from, to) + } } -func (te TendermintEvents) UnsubscribeLogs(subscriberID rpc.ID) (err error) { - return te.client.Unsubscribe( - te.ctx, string(subscriberID), - tmtypes.QueryForEvent(tmtypes.EventTx).String(), - ) +// subscribeMinedPendingLogs creates a subscription that returned mined and +// pending logs that match the given criteria. +func (te *TendermintEvents) subscribeMinedPendingLogs(crit filters.FilterCriteria) (*Subscription, error) { + sub := &subscription{ + id: rpc.NewID(), + typ: filters.MinedAndPendingLogsSubscription, + logsCrit: crit, + created: time.Now(), + installed: make(chan struct{}), + err: make(chan error), + } + return te.subscribe(sub) } -func (te TendermintEvents) SubscribeNewHeads(subscriberID rpc.ID) (eventCh <-chan coretypes.ResultEvent, err error) { - return te.client.Subscribe( - te.ctx, string(subscriberID), - tmtypes.QueryForEvent(tmtypes.EventNewBlockHeader).String(), - ) +// subscribeLogs creates a subscription that will write all logs matching the +// given criteria to the given logs channel. +func (te *TendermintEvents) subscribeLogs(crit filters.FilterCriteria) (*Subscription, error) { + sub := &subscription{ + id: rpc.NewID(), + typ: filters.LogsSubscription, + logsCrit: crit, + created: time.Now(), + installed: make(chan struct{}), + err: make(chan error), + } + return te.subscribe(sub) } -func (te TendermintEvents) UnsubscribeHeads(subscriberID rpc.ID) (err error) { - return te.client.Unsubscribe( - te.ctx, string(subscriberID), - tmtypes.QueryForEvent(tmtypes.EventNewBlockHeader).String(), - ) +// subscribePendingLogs creates a subscription that writes transaction hashes for +// transactions that enter the transaction pool. +func (te *TendermintEvents) subscribePendingLogs(crit filters.FilterCriteria) (*Subscription, error) { + sub := &subscription{ + id: rpc.NewID(), + typ: filters.PendingLogsSubscription, + logsCrit: crit, + created: time.Now(), + installed: make(chan struct{}), + err: make(chan error), + } + return te.subscribe(sub) +} + +func (te TendermintEvents) SubscribeNewHeads() (*Subscription, error) { + sub := &subscription{ + id: rpc.NewID(), + typ: filters.BlocksSubscription, + created: time.Now(), + installed: make(chan struct{}), + err: make(chan error), + } + return te.subscribe(sub) } func (te TendermintEvents) SubscribePendingTxs(hashes chan []common.Hash) *filters.Subscription { return &filters.Subscription{} } + +type filterIndex map[filters.Type]map[rpc.ID]*subscription + +func (te *TendermintEvents) handleLogs(filterIdx filterIndex, ev coretypes.ResultEvent) { + // filter only events from EVM module txs + _, isMsgEthermint := ev.Events[evmtypes.TypeMsgEthermint] + _, isMsgEthereumTx := ev.Events[evmtypes.TypeMsgEthereumTx] + + if !(isMsgEthermint || isMsgEthereumTx) { + // ignore transaction + return + } + + data, _ := ev.Data.(tmtypes.EventDataTx) + resultData, err := evmtypes.DecodeResultData(data.TxResult.Result.Data) + if err != nil { + return + } + + if len(resultData.Logs) == 0 { + return + } + for _, f := range filterIdx[filters.LogsSubscription] { + matchedLogs := filterLogs(resultData.Logs, f.logsCrit.FromBlock, f.logsCrit.ToBlock, f.logsCrit.Addresses, f.logsCrit.Topics) + if len(matchedLogs) > 0 { + f.logs = matchedLogs + } + } +} + +func (te *TendermintEvents) handleChainEvent(filterIdx filterIndex, ev coretypes.ResultEvent) { + data, _ := ev.Data.(tmtypes.EventDataNewBlockHeader) + for _, f := range filterIdx[filters.BlocksSubscription] { + f.headers = data.Header + } + // TODO: light client +} + +// eventLoop (un)installs filters and processes mux events. +func (te *TendermintEvents) eventLoop() { + // Ensure all subscriptions get cleaned up + defer func() { + // te.txsSub.Unsubscribe(te.ctx, te.client) + te.logsSub.Unsubscribe(te.ctx, te.client) + // te.rmLogsSub.Unsubscribe(te.ctx, te.client) + // te.pendingLogsSub.Unsubscribe(te.ctx, te.client) + te.chainSub.Unsubscribe(te.ctx, te.client) + }() + + index := make(filterIndex) + for i := filters.UnknownSubscription; i < filters.LastIndexSubscription; i++ { + index[i] = make(map[rpc.ID]*subscription) + } + + for { + select { + case ev := <-te.eventsChannel: + switch ev.Data.(type) { + case tmtypes.EventDataTx: + te.handleLogs(index, ev) + case tmtypes.EventDataNewBlockHeader: + te.handleChainEvent(index, ev) + } + + case f := <-te.install: + if f.typ == filters.MinedAndPendingLogsSubscription { + // the type are logs and pending logs subscriptions + index[filters.LogsSubscription][f.id] = f + index[filters.PendingLogsSubscription][f.id] = f + } else { + index[f.typ][f.id] = f + } + close(f.installed) + + case f := <-te.uninstall: + if f.typ == filters.MinedAndPendingLogsSubscription { + // the type are logs and pending logs subscriptions + delete(index[filters.LogsSubscription], f.id) + delete(index[filters.PendingLogsSubscription], f.id) + } else { + delete(index[f.typ], f.id) + } + close(f.err) + } + } +} + +type Subscription struct { + subscription *subscription + eventChannel <-chan coretypes.ResultEvent +} + +func (s Subscription) ID() rpc.ID { + return s.subscription.id +} + +func (s Subscription) Unsubscribe(ctx context.Context, client rpcclient.Client) error { + return client.Unsubscribe( + ctx, string(s.ID()), + tmtypes.QueryForEvent(s.subscription.event()).String(), + ) +} diff --git a/rpc/filters.go b/rpc/filters.go index 59b9b3f6a..3180b6eef 100644 --- a/rpc/filters.go +++ b/rpc/filters.go @@ -28,6 +28,8 @@ type Filter struct { matcher *bloombits.Matcher + logs []*ethtypes.Log // stored logs + subscription bool // associated subscription in event system } @@ -49,9 +51,13 @@ func (f *Filter) Unsubscribe() { switch f.typ { case filters.LogsSubscription: + case filters.BlocksSubscription: + + case filters.U + } } From 6c164522883936eee2d9bcb5a6bf9780957b5b62 Mon Sep 17 00:00:00 2001 From: Federico Kunze Date: Thu, 4 Jun 2020 20:54:58 +0200 Subject: [PATCH 11/36] update Filter --- rpc/apis.go | 2 +- rpc/backend.go | 8 ++++- rpc/filter_api.go | 62 ++++++++++++++++++++-------------- rpc/filter_system.go | 4 +-- rpc/filters.go | 79 ++++++++++++++++++++++++-------------------- 5 files changed, 91 insertions(+), 64 deletions(-) diff --git a/rpc/apis.go b/rpc/apis.go index c8d0adb6d..e7d05adb8 100644 --- a/rpc/apis.go +++ b/rpc/apis.go @@ -44,7 +44,7 @@ func GetRPCAPIs(cliCtx context.CLIContext, key emintcrypto.PrivKeySecp256k1) []r { Namespace: EthNamespace, Version: apiVersion, - Service: NewPublicFilterAPI(cliCtx, backend, 5), + Service: NewPublicFilterAPI(cliCtx, backend), Public: true, }, { diff --git a/rpc/backend.go b/rpc/backend.go index 61b27b0ae..8b9c3048e 100644 --- a/rpc/backend.go +++ b/rpc/backend.go @@ -34,7 +34,7 @@ type Backend interface { // Used by log filter GetTransactionLogs(txHash common.Hash) ([]*ethtypes.Log, error) - // TODO: Bloom methods + BloomStatus() (uint64, uint64) } var _ Backend = (*EthermintBackend)(nil) @@ -301,3 +301,9 @@ func (e *EthermintBackend) GetLogs(blockHash common.Hash) ([][]*ethtypes.Log, er return blockLogs, nil } + +// BloomStatus returns the BloomBitsBlocks and the number of processed sections maintained +// by the chain indexer. +func (e *EthermintBackend) BloomStatus() (uint64, uint64) { + return 4096, 0 +} diff --git a/rpc/filter_api.go b/rpc/filter_api.go index a559509fb..085e6ffcb 100644 --- a/rpc/filter_api.go +++ b/rpc/filter_api.go @@ -20,9 +20,8 @@ import ( evmtypes "github.com/cosmos/ethermint/x/evm/types" ) -var ( - deadline = 5 * time.Minute // consider a filter inactive if it has not been polled for within deadline -) +// consider a filter inactive if it has not been polled for within deadline +var deadline = 5 * time.Minute // filter is a helper struct that holds meta information over the filter type // and associated subscription in the event system. @@ -44,7 +43,7 @@ type PublicFilterAPI struct { quit chan struct{} events EventSystem filtersMu sync.Mutex - filters map[rpc.ID]*Filter + filters map[rpc.ID]*filter } // NewPublicFilterAPI returns a new PublicFilterAPI instance. @@ -52,11 +51,8 @@ func NewPublicFilterAPI(cliCtx clientcontext.CLIContext, backend Backend) *Publi api := &PublicFilterAPI{ cliCtx: cliCtx, backend: backend, - filters: make(map[rpc.ID]*Filter), - events: &TendermintEvents{ - ctx: context.Background(), - client: cliCtx.Client, - }, + filters: make(map[rpc.ID]*filter), + events: NewTendermintEvents(cliCtx.Client), } go api.timeoutLoop() @@ -75,7 +71,7 @@ func (api *PublicFilterAPI) timeoutLoop() { for id, f := range api.filters { select { case <-f.deadline.C: - f.Unsubscribe() + f.s.Unsubscribe(context.Background(), api.cliCtx.Client) delete(api.filters, id) default: continue @@ -184,7 +180,7 @@ func (api *PublicFilterAPI) NewBlockFilter() rpc.ID { api.events = api.events.WithContext(ctx) api.filtersMu.Lock() - api.filters[headerSub.ID()] = NewFilter(api.backend, filters.BlocksSubscription, filters.FilterCriteria{}) + api.filters[headerSub.ID()] = &filter{typ: filters.BlocksSubscription, deadline: time.NewTimer(deadline), hashes: make([]common.Hash, 0), s: headerSub} api.filtersMu.Unlock() go func() { @@ -326,7 +322,6 @@ func (api *PublicFilterAPI) Logs(ctx context.Context, crit filters.FilterCriteri // // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newfilter func (api *PublicFilterAPI) NewFilter(criteria filters.FilterCriteria) (rpc.ID, error) { - subscriberID := rpc.NewID() logsSub, err := api.events.SubscribeLogs(criteria) if err != nil { return rpc.ID(""), err @@ -338,7 +333,7 @@ func (api *PublicFilterAPI) NewFilter(criteria filters.FilterCriteria) (rpc.ID, api.events = api.events.WithContext(ctx) api.filtersMu.Lock() - api.filters[subscriberID] = NewFilter(api.backend, filters.LogsSubscription, criteria) + api.filters[logsSub.ID()] = &filter{typ: filters.LogsSubscription, crit: criteria, deadline: time.NewTimer(deadline), logs: make([]*types.Log, 0), s: logsSub} api.filtersMu.Unlock() go func() { @@ -368,7 +363,7 @@ func (api *PublicFilterAPI) NewFilter(criteria filters.FilterCriteria) (rpc.ID, logs := filterLogs(resultData.Logs, criteria.FromBlock, criteria.ToBlock, criteria.Addresses, criteria.Topics) api.filtersMu.Lock() - if f, found := api.filters[subscriberID]; found { + if f, found := api.filters[logsSub.ID()]; found { f.logs = append(f.logs, logs...) } api.filtersMu.Unlock() @@ -376,7 +371,7 @@ func (api *PublicFilterAPI) NewFilter(criteria filters.FilterCriteria) (rpc.ID, } }() - return subscriberID, nil + return logsSub.ID(), nil } // GetLogs returns logs matching the given argument that are stored within the state. @@ -386,7 +381,7 @@ func (api *PublicFilterAPI) GetLogs(ctx context.Context, crit filters.FilterCrit var filter *Filter if crit.BlockHash != nil { // Block filter requested, construct a single-shot filter - filter = NewBlockFilter(api.backend, *crit.BlockHash, crit.Addresses, crit.Topics) + filter = NewBlockFilter(api.backend, crit) } else { // Convert the RPC block numbers into internal representations begin := rpc.LatestBlockNumber.Int64() @@ -422,7 +417,7 @@ func (api *PublicFilterAPI) UninstallFilter(id rpc.ID) bool { return false } - f.Unsubscribe() + f.s.Unsubscribe(context.Background(), api.cliCtx.Client) return true } @@ -443,7 +438,31 @@ func (api *PublicFilterAPI) GetFilterLogs(ctx context.Context, id rpc.ID) ([]*et return returnLogs(nil), fmt.Errorf("filter %s doesn't have a LogsSubscription type: got %d", id, f.typ) } - return api.filters[id].Logs(ctx) + var filter *Filter + if f.crit.BlockHash != nil { + // Block filter requested, construct a single-shot filter + filter = NewBlockFilter(api.backend, f.crit) + } else { + // Convert the RPC block numbers into internal representations + begin := rpc.LatestBlockNumber.Int64() + if f.crit.FromBlock != nil { + begin = f.crit.FromBlock.Int64() + } + end := rpc.LatestBlockNumber.Int64() + if f.crit.ToBlock != nil { + end = f.crit.ToBlock.Int64() + } + // Construct the range filter + filter = NewRangeFilter(api.backend, begin, end, f.crit.Addresses, f.crit.Topics) + } + // Run the filter and return all the logs + logs, err := filter.Logs(ctx) + if err != nil { + return nil, err + } + return returnLogs(logs), nil + + // return api.filters[id].Logs(ctx) } // GetFilterChanges returns the logs for the filter with the given id since @@ -469,7 +488,6 @@ func (api *PublicFilterAPI) GetFilterChanges(id rpc.ID) (interface{}, error) { } f.deadline.Reset(deadline) - var err error switch f.typ { case filters.PendingTransactionsSubscription, filters.BlocksSubscription: hashes := f.hashes @@ -477,12 +495,6 @@ func (api *PublicFilterAPI) GetFilterChanges(id rpc.ID) (interface{}, error) { return returnHashes(hashes), nil case filters.LogsSubscription, filters.MinedAndPendingLogsSubscription: logs := f.logs - if len(logs) == 0 { - logs, err = f.Logs(context.Background()) - if err != nil { - return returnLogs(nil), err - } - } f.logs = nil return returnLogs(logs), nil default: diff --git a/rpc/filter_system.go b/rpc/filter_system.go index fe59adac2..1ec043ad6 100644 --- a/rpc/filter_system.go +++ b/rpc/filter_system.go @@ -70,9 +70,9 @@ type TendermintEvents struct { // // The returned manager has a loop that needs to be stopped with the Stop function // or by stopping the given mux. -func NewTendermintEvents(ctx context.Context, client rpcclient.Client) *TendermintEvents { +func NewTendermintEvents(client rpcclient.Client) *TendermintEvents { te := &TendermintEvents{ - ctx: ctx, + ctx: context.Background(), client: client, } diff --git a/rpc/filters.go b/rpc/filters.go index 3180b6eef..e7442db93 100644 --- a/rpc/filters.go +++ b/rpc/filters.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "math/big" - "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/bloombits" @@ -13,53 +12,63 @@ import ( "github.com/ethereum/go-ethereum/rpc" ) -/* - - Filter functions derived from go-ethereum - Used to set the criteria passed in from RPC params -*/ - +// Filter can be used to retrieve and filter logs. type Filter struct { - backend Backend - - typ filters.Type // filter type - deadline *time.Timer // filter is inactiv when deadline triggers - hashes []common.Hash // filtered block or transaction hashes + backend Backend criteria filters.FilterCriteria + matcher *bloombits.Matcher +} - matcher *bloombits.Matcher - - logs []*ethtypes.Log // stored logs - - subscription bool // associated subscription in event system +// NewBlockFilter creates a new filter which directly inspects the contents of +// a block to figure out whether it is interesting or not. +func NewBlockFilter(backend Backend, criteria filters.FilterCriteria) *Filter { + // Create a generic filter and convert it into a block filter + return newFilter(backend, criteria, nil) } -// NewFilter returns a new Filter -func NewFilter(backend Backend, filterType filters.Type, criteria filters.FilterCriteria) *Filter { - return &Filter{ - backend: backend, - typ: filterType, - deadline: time.NewTimer(deadline), - criteria: criteria, +// NewRangeFilter creates a new filter which uses a bloom filter on blocks to +// figure out whether a particular block is interesting or not. +func NewRangeFilter(backend Backend, begin, end int64, addresses []common.Address, topics [][]common.Hash) *Filter { + // Flatten the address and topic filter clauses into a single bloombits filter + // system. Since the bloombits are not positional, nil topics are permitted, + // which get flattened into a nil byte slice. + var filtersBz [][][]byte + if len(addresses) > 0 { + filter := make([][]byte, len(addresses)) + for i, address := range addresses { + filter[i] = address.Bytes() + } + filtersBz = append(filtersBz, filter) } -} -// TODO: -func (f *Filter) Unsubscribe() { - if !f.subscription { - return + for _, topicList := range topics { + filter := make([][]byte, len(topicList)) + for i, topic := range topicList { + filter[i] = topic.Bytes() + } + filtersBz = append(filtersBz, filter) } - switch f.typ { - case filters.LogsSubscription: - + size, _ := backend.BloomStatus() - case filters.BlocksSubscription: + // Create a generic filter and convert it into a range filter + criteria := filters.FilterCriteria{ + FromBlock: big.NewInt(begin), + ToBlock: big.NewInt(end), + Addresses: addresses, + Topics: topics, + } - - case filters.U + return newFilter(backend, criteria, bloombits.NewMatcher(size, filtersBz)) +} +// newFilter returns a new Filter +func newFilter(backend Backend, criteria filters.FilterCriteria, matcher *bloombits.Matcher) *Filter { + return &Filter{ + backend: backend, + criteria: criteria, + matcher: matcher, } - } // Logs searches the blockchain for matching log entries, returning all from the From b804b3ec0fa789036bf0f9d62f9fdc2f48accd38 Mon Sep 17 00:00:00 2001 From: Federico Kunze Date: Thu, 4 Jun 2020 23:43:54 +0200 Subject: [PATCH 12/36] update EventSystem --- rpc/backend.go | 42 ++++++----- rpc/filter_api.go | 28 ++++---- rpc/filter_system.go | 166 +++++++++++++++++++++++++------------------ 3 files changed, 133 insertions(+), 103 deletions(-) diff --git a/rpc/backend.go b/rpc/backend.go index 8b9c3048e..b1dc7c454 100644 --- a/rpc/backend.go +++ b/rpc/backend.go @@ -5,6 +5,8 @@ import ( "math/big" "strconv" + tmtypes "github.com/tendermint/tendermint/types" + "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/ethermint/x/evm" "github.com/cosmos/ethermint/x/evm/types" @@ -121,25 +123,8 @@ func (e *EthermintBackend) getBlockHeader(height int64) (*ethtypes.Header, error var bloomRes types.QueryBloomFilter e.cliCtx.Codec.MustUnmarshalJSON(res, &bloomRes) - header := block.Block.Header - ethHeader := ðtypes.Header{ - ParentHash: common.BytesToHash(header.LastBlockID.Hash.Bytes()), - UncleHash: common.Hash{}, - Coinbase: common.Address{}, - Root: common.BytesToHash(header.AppHash), - TxHash: common.BytesToHash(header.DataHash), - ReceiptHash: common.Hash{}, - Bloom: bloomRes.Bloom, - Difficulty: nil, - Number: big.NewInt(height), - // TODO: block gas limit - // GasLimit: uint64(gasLimit), - // GasUsed: uint64(gasUsed.Int64()), - Time: uint64(header.Time.Unix()), - Extra: nil, - MixDigest: common.Hash{}, - Nonce: ethtypes.BlockNonce{}, - } + ethHeader := EthHeaderFromTendermint(block.Block.Header) + ethHeader.Bloom = bloomRes.Bloom return ethHeader, nil } @@ -307,3 +292,22 @@ func (e *EthermintBackend) GetLogs(blockHash common.Hash) ([][]*ethtypes.Log, er func (e *EthermintBackend) BloomStatus() (uint64, uint64) { return 4096, 0 } + +// EthHeaderFromTendermint is an util function that returns an Ethereum Header +// from a tendermint Header. +func EthHeaderFromTendermint(header tmtypes.Header) *ethtypes.Header { + return ðtypes.Header{ + ParentHash: common.BytesToHash(header.LastBlockID.Hash.Bytes()), + UncleHash: common.Hash{}, + Coinbase: common.Address{}, + Root: common.BytesToHash(header.AppHash), + TxHash: common.BytesToHash(header.DataHash), + ReceiptHash: common.Hash{}, + Difficulty: nil, + Number: big.NewInt(header.Height), + Time: uint64(header.Time.Unix()), + Extra: nil, + MixDigest: common.Hash{}, + Nonce: ethtypes.BlockNonce{}, + } +} diff --git a/rpc/filter_api.go b/rpc/filter_api.go index 085e6ffcb..e838671d2 100644 --- a/rpc/filter_api.go +++ b/rpc/filter_api.go @@ -41,7 +41,7 @@ type PublicFilterAPI struct { backend Backend mux *event.TypeMux quit chan struct{} - events EventSystem + events *EventSystem filtersMu sync.Mutex filters map[rpc.ID]*filter } @@ -52,7 +52,7 @@ func NewPublicFilterAPI(cliCtx clientcontext.CLIContext, backend Backend) *Publi cliCtx: cliCtx, backend: backend, filters: make(map[rpc.ID]*filter), - events: NewTendermintEvents(cliCtx.Client), + events: NewEventSystem(cliCtx.Client), } go api.timeoutLoop() @@ -71,7 +71,7 @@ func (api *PublicFilterAPI) timeoutLoop() { for id, f := range api.filters { select { case <-f.deadline.C: - f.s.Unsubscribe(context.Background(), api.cliCtx.Client) + f.s.Unsubscribe(api.events) delete(api.filters, id) default: continue @@ -97,7 +97,7 @@ func (api *PublicFilterAPI) timeoutLoop() { // ctx, cancelFn := context.WithTimeout(context.Background(), api.events.GetTimeout()) // defer cancelFn() -// api.events = api.events.WithContext(ctx) +// api.events.WithContext(ctx) // api.filtersMu.Lock() // api.filters[pendingTxSub.ID] = NewFilter(api.backend, &filters.FilterCriteria{}, filters.PendingTransactionsSubscription) @@ -135,7 +135,7 @@ func (api *PublicFilterAPI) timeoutLoop() { // ctx, cancelFn := context.WithTimeout(context.Background(), api.events.GetTimeout()) // defer cancelFn() -// api.events = api.events.WithContext(ctx) +// api.events.WithContext(ctx) // rpcSub := notifier.CreateSubscription() // go func() { @@ -177,7 +177,7 @@ func (api *PublicFilterAPI) NewBlockFilter() rpc.ID { ctx, cancelFn := context.WithTimeout(context.Background(), deadline) defer cancelFn() - api.events = api.events.WithContext(ctx) + api.events.WithContext(ctx) api.filtersMu.Lock() api.filters[headerSub.ID()] = &filter{typ: filters.BlocksSubscription, deadline: time.NewTimer(deadline), hashes: make([]common.Hash, 0), s: headerSub} @@ -213,7 +213,7 @@ func (api *PublicFilterAPI) NewHeads(ctx context.Context) (*rpc.Subscription, er ctx, cancelFn := context.WithTimeout(ctx, deadline) defer cancelFn() - api.events = api.events.WithContext(ctx) + api.events.WithContext(ctx) rpcSub := notifier.CreateSubscription() headersSub, err := api.events.SubscribeNewHeads() @@ -232,10 +232,10 @@ func (api *PublicFilterAPI) NewHeads(ctx context.Context) (*rpc.Subscription, er } notifier.Notify(rpcSub.ID, evHeader.Header) case <-rpcSub.Err(): - err = headersSub.Unsubscribe(ctx, api.cliCtx.Client) + err = headersSub.Unsubscribe(api.events) return case <-notifier.Closed(): - err = headersSub.Unsubscribe(ctx, api.cliCtx.Client) + err = headersSub.Unsubscribe(api.events) return } } @@ -254,7 +254,7 @@ func (api *PublicFilterAPI) Logs(ctx context.Context, crit filters.FilterCriteri ctx, cancelFn := context.WithTimeout(context.Background(), deadline) defer cancelFn() - api.events = api.events.WithContext(ctx) + api.events.WithContext(ctx) var ( rpcSub = notifier.CreateSubscription() @@ -296,10 +296,10 @@ func (api *PublicFilterAPI) Logs(ctx context.Context, crit filters.FilterCriteri notifier.Notify(rpcSub.ID, &log) } case <-rpcSub.Err(): // client send an unsubscribe request - err = logsSub.Unsubscribe(ctx, api.cliCtx.Client) + err = logsSub.Unsubscribe(api.events) return case <-notifier.Closed(): // connection dropped - err = logsSub.Unsubscribe(ctx, api.cliCtx.Client) + err = logsSub.Unsubscribe(api.events) return } } @@ -330,7 +330,7 @@ func (api *PublicFilterAPI) NewFilter(criteria filters.FilterCriteria) (rpc.ID, ctx, cancelFn := context.WithTimeout(context.Background(), deadline) defer cancelFn() - api.events = api.events.WithContext(ctx) + api.events.WithContext(ctx) api.filtersMu.Lock() api.filters[logsSub.ID()] = &filter{typ: filters.LogsSubscription, crit: criteria, deadline: time.NewTimer(deadline), logs: make([]*types.Log, 0), s: logsSub} @@ -417,7 +417,7 @@ func (api *PublicFilterAPI) UninstallFilter(id rpc.ID) bool { return false } - f.s.Unsubscribe(context.Background(), api.cliCtx.Client) + f.s.Unsubscribe(api.events) return true } diff --git a/rpc/filter_system.go b/rpc/filter_system.go index 1ec043ad6..c54288678 100644 --- a/rpc/filter_system.go +++ b/rpc/filter_system.go @@ -3,6 +3,7 @@ package rpc import ( "context" "fmt" + "sync" "time" evmtypes "github.com/cosmos/ethermint/x/evm/types" @@ -11,26 +12,20 @@ import ( tmtypes "github.com/tendermint/tendermint/types" "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth/filters" "github.com/ethereum/go-ethereum/rpc" ) -// EventSystem creates subscriptions, processes events and broadcasts them to the -// subscription which match the subscription criteria. -type EventSystem interface { - WithContext(ctx context.Context) EventSystem - SubscribeLogs(crit filters.FilterCriteria) (*Subscription, error) - SubscribeNewHeads() (*Subscription, error) - // SubscribePendingTxs(hashes chan []common.Hash) *filters.Subscription -} - type subscription struct { - id rpc.ID - typ filters.Type - created time.Time - logsCrit filters.FilterCriteria + id rpc.ID + typ filters.Type + created time.Time + logsCrit filters.FilterCriteria + logs chan []*ethtypes.Log + // hashes chan []common.Hash + headers chan *ethtypes.Header installed chan struct{} // closed when the filter is installed - err chan error // closed when the filter is uninstalled } func (s subscription) event() string { @@ -44,10 +39,9 @@ func (s subscription) event() string { } } -var _ EventSystem = &TendermintEvents{} - -// TendermintEvents implements the EventSystem using Tendermint's RPC client. -type TendermintEvents struct { +// EventSystem creates subscriptions, processes events and broadcasts them to the +// subscription which match the subscription criteria using the Tendermint's RPC client. +type EventSystem struct { ctx context.Context client rpcclient.Client @@ -64,46 +58,48 @@ type TendermintEvents struct { eventsChannel <-chan coretypes.ResultEvent // channel to receive tendermint event results } -// NewTendermintEvents creates a new manager that listens for event on the given mux, +// NewEventSystem creates a new manager that listens for event on the given mux, // parses and filters them. It uses the all map to retrieve filter changes. The // work loop holds its own index that is used to forward events to filters. // // The returned manager has a loop that needs to be stopped with the Stop function // or by stopping the given mux. -func NewTendermintEvents(client rpcclient.Client) *TendermintEvents { - te := &TendermintEvents{ +func NewEventSystem(client rpcclient.Client) *EventSystem { + es := &EventSystem{ ctx: context.Background(), client: client, } // Subscribe events - // te.txsSub = te.SubscribeNewTxsEvent(m.txsCh) - te.logsSub, _ = te.SubscribeLogs(filters.FilterCriteria{}) - te.chainSub, _ = te.SubscribeNewHeads() + // es.txsSub = es.SubscribeNewTxsEvent(m.txsCh) + es.logsSub, _ = es.SubscribeLogs(filters.FilterCriteria{}) + es.chainSub, _ = es.SubscribeNewHeads() - go te.eventLoop() - return te + go es.eventLoop() + return es } // WithContext sets the a given context to the -func (te *TendermintEvents) WithContext(ctx context.Context) EventSystem { - te.ctx = ctx - return te +func (es *EventSystem) WithContext(ctx context.Context) { + es.ctx = ctx } -func (te TendermintEvents) subscribe(sub *subscription) (*Subscription, error) { +func (es EventSystem) subscribe(sub *subscription) (*Subscription, error) { + es.install <- sub + <-sub.installed + var err error subscription := &Subscription{ subscription: sub, } - subscription.eventChannel, err = te.client.Subscribe(te.ctx, string(sub.id), tmtypes.QueryForEvent(sub.event()).String()) + subscription.eventChannel, err = es.client.Subscribe(es.ctx, string(sub.id), tmtypes.QueryForEvent(sub.event()).String()) return subscription, err } // SubscribeLogs creates a subscription that will write all logs matching the // given criteria to the given logs channel. Default value for the from and to // block is "latest". If the fromBlock > toBlock an error is returned. -func (te *TendermintEvents) SubscribeLogs(crit filters.FilterCriteria) (*Subscription, error) { +func (es *EventSystem) SubscribeLogs(crit filters.FilterCriteria) (*Subscription, error) { var from, to rpc.BlockNumber if crit.FromBlock == nil { from = rpc.LatestBlockNumber @@ -119,17 +115,17 @@ func (te *TendermintEvents) SubscribeLogs(crit filters.FilterCriteria) (*Subscri switch { // // only interested in pending logs // case from == rpc.PendingBlockNumber && to == rpc.PendingBlockNumber: - // return te.subscribePendingLogs(crit, logs) + // return es.subscribePendingLogs(crit, logs) // only interested in new mined logs, mined logs within a specific block range, or // logs from a specific block number to new mined blocks case (from == rpc.LatestBlockNumber && to == rpc.LatestBlockNumber), (from >= 0 && to >= 0 && to >= from): - return te.subscribeLogs(crit) + return es.subscribeLogs(crit) // // interested in mined logs from a specific block number, new logs and pending logs // case from >= rpc.LatestBlockNumber && to == rpc.PendingBlockNumber: - // return te.subscribeMinedPendingLogs(crit, logs) + // return es.subscribeMinedPendingLogs(crit, logs) default: return nil, fmt.Errorf("invalid from and to block combination: from > to (%d > %d)", from, to) @@ -138,64 +134,72 @@ func (te *TendermintEvents) SubscribeLogs(crit filters.FilterCriteria) (*Subscri // subscribeMinedPendingLogs creates a subscription that returned mined and // pending logs that match the given criteria. -func (te *TendermintEvents) subscribeMinedPendingLogs(crit filters.FilterCriteria) (*Subscription, error) { +func (es *EventSystem) subscribeMinedPendingLogs(crit filters.FilterCriteria) (*Subscription, error) { sub := &subscription{ id: rpc.NewID(), typ: filters.MinedAndPendingLogsSubscription, logsCrit: crit, created: time.Now(), + logs: make(chan []*ethtypes.Log), installed: make(chan struct{}), - err: make(chan error), } - return te.subscribe(sub) + return es.subscribe(sub) } // subscribeLogs creates a subscription that will write all logs matching the // given criteria to the given logs channel. -func (te *TendermintEvents) subscribeLogs(crit filters.FilterCriteria) (*Subscription, error) { +func (es *EventSystem) subscribeLogs(crit filters.FilterCriteria) (*Subscription, error) { sub := &subscription{ id: rpc.NewID(), typ: filters.LogsSubscription, logsCrit: crit, created: time.Now(), + logs: make(chan []*ethtypes.Log), installed: make(chan struct{}), - err: make(chan error), } - return te.subscribe(sub) + return es.subscribe(sub) } // subscribePendingLogs creates a subscription that writes transaction hashes for // transactions that enter the transaction pool. -func (te *TendermintEvents) subscribePendingLogs(crit filters.FilterCriteria) (*Subscription, error) { +func (es *EventSystem) subscribePendingLogs(crit filters.FilterCriteria) (*Subscription, error) { sub := &subscription{ id: rpc.NewID(), typ: filters.PendingLogsSubscription, logsCrit: crit, created: time.Now(), + logs: make(chan []*ethtypes.Log), installed: make(chan struct{}), - err: make(chan error), } - return te.subscribe(sub) + return es.subscribe(sub) } -func (te TendermintEvents) SubscribeNewHeads() (*Subscription, error) { +// SubscribeNewHeads subscribes to new block headers events. +func (es EventSystem) SubscribeNewHeads() (*Subscription, error) { sub := &subscription{ id: rpc.NewID(), typ: filters.BlocksSubscription, created: time.Now(), + headers: make(chan *ethtypes.Header), installed: make(chan struct{}), - err: make(chan error), } - return te.subscribe(sub) + return es.subscribe(sub) } -func (te TendermintEvents) SubscribePendingTxs(hashes chan []common.Hash) *filters.Subscription { - return &filters.Subscription{} +// SubscribePendingTxs subscribes to new pending transactions events from the mempool. +func (es EventSystem) SubscribePendingTxs(hashes chan []common.Hash) (*Subscription, error) { + sub := &subscription{ + id: rpc.NewID(), + typ: filters.PendingTransactionsSubscription, + created: time.Now(), + installed: make(chan struct{}), + } + return es.subscribe(sub) } type filterIndex map[filters.Type]map[rpc.ID]*subscription -func (te *TendermintEvents) handleLogs(filterIdx filterIndex, ev coretypes.ResultEvent) { +func (es *EventSystem) handleLogs(filterIdx filterIndex, ev coretypes.ResultEvent) { // filter only events from EVM module txs _, isMsgEthermint := ev.Events[evmtypes.TypeMsgEthermint] _, isMsgEthereumTx := ev.Events[evmtypes.TypeMsgEthereumTx] @@ -217,28 +221,28 @@ func (te *TendermintEvents) handleLogs(filterIdx filterIndex, ev coretypes.Resul for _, f := range filterIdx[filters.LogsSubscription] { matchedLogs := filterLogs(resultData.Logs, f.logsCrit.FromBlock, f.logsCrit.ToBlock, f.logsCrit.Addresses, f.logsCrit.Topics) if len(matchedLogs) > 0 { - f.logs = matchedLogs + f.logs <- matchedLogs } } } -func (te *TendermintEvents) handleChainEvent(filterIdx filterIndex, ev coretypes.ResultEvent) { +func (es *EventSystem) handleChainEvent(filterIdx filterIndex, ev coretypes.ResultEvent) { data, _ := ev.Data.(tmtypes.EventDataNewBlockHeader) for _, f := range filterIdx[filters.BlocksSubscription] { - f.headers = data.Header + f.headers <- EthHeaderFromTendermint(data.Header) } // TODO: light client } // eventLoop (un)installs filters and processes mux events. -func (te *TendermintEvents) eventLoop() { +func (es *EventSystem) eventLoop() { // Ensure all subscriptions get cleaned up defer func() { - // te.txsSub.Unsubscribe(te.ctx, te.client) - te.logsSub.Unsubscribe(te.ctx, te.client) - // te.rmLogsSub.Unsubscribe(te.ctx, te.client) - // te.pendingLogsSub.Unsubscribe(te.ctx, te.client) - te.chainSub.Unsubscribe(te.ctx, te.client) + // es.txsSub.Unsubscribe(es) + es.logsSub.Unsubscribe(es) + // es.rmLogsSub.Unsubscribe(es) + // es.pendingLogsSub.Unsubscribe(es) + es.chainSub.Unsubscribe(es) }() index := make(filterIndex) @@ -248,15 +252,15 @@ func (te *TendermintEvents) eventLoop() { for { select { - case ev := <-te.eventsChannel: + case ev := <-es.eventsChannel: switch ev.Data.(type) { case tmtypes.EventDataTx: - te.handleLogs(index, ev) + es.handleLogs(index, ev) case tmtypes.EventDataNewBlockHeader: - te.handleChainEvent(index, ev) + es.handleChainEvent(index, ev) } - case f := <-te.install: + case f := <-es.install: if f.typ == filters.MinedAndPendingLogsSubscription { // the type are logs and pending logs subscriptions index[filters.LogsSubscription][f.id] = f @@ -266,7 +270,7 @@ func (te *TendermintEvents) eventLoop() { } close(f.installed) - case f := <-te.uninstall: + case f := <-es.uninstall: if f.typ == filters.MinedAndPendingLogsSubscription { // the type are logs and pending logs subscriptions delete(index[filters.LogsSubscription], f.id) @@ -274,23 +278,45 @@ func (te *TendermintEvents) eventLoop() { } else { delete(index[f.typ], f.id) } - close(f.err) } } } +// Subscription defines a wrapper for the private subscription type Subscription struct { subscription *subscription eventChannel <-chan coretypes.ResultEvent + unsubOnce sync.Once } +// ID returns the underlying subscription RPC identifier. func (s Subscription) ID() rpc.ID { return s.subscription.id } -func (s Subscription) Unsubscribe(ctx context.Context, client rpcclient.Client) error { - return client.Unsubscribe( - ctx, string(s.ID()), - tmtypes.QueryForEvent(s.subscription.event()).String(), - ) +// Unsubscribe the current subscription from Tendermint Websocket. +func (s Subscription) Unsubscribe(es *EventSystem) (err error) { + s.unsubOnce.Do(func() { + uninstallLoop: + for { + // write uninstall request and consume logs/hashes. This prevents + // the eventLoop broadcast method to deadlock when writing to the + // filter event channel while the subscription loop is waiting for + // this method to return (and thus not reading these events). + select { + case es.uninstall <- s.subscription: + break uninstallLoop + case <-s.subscription.logs: + // case <-s.subscription.hashes: + case <-s.subscription.headers: + } + } + + err = es.client.Unsubscribe( + es.ctx, string(s.ID()), + tmtypes.QueryForEvent(s.subscription.event()).String(), + ) + }) + + return } From 050bd0ea2a60c140a942d2d3a1b83909c9f1882a Mon Sep 17 00:00:00 2001 From: Federico Kunze Date: Fri, 5 Jun 2020 16:59:06 +0200 Subject: [PATCH 13/36] fix lint issues --- go.mod | 1 + go.sum | 149 +++++++++++++++++++++++++++++++++++++++++++ rpc/filter_api.go | 21 +++--- rpc/filter_system.go | 62 +++++++++--------- rpc/filters.go | 6 +- 5 files changed, 197 insertions(+), 42 deletions(-) diff --git a/go.mod b/go.mod index 00bf0d1ed..268c0e99d 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( github.com/ethereum/go-ethereum v1.9.14 github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 // indirect github.com/gogo/protobuf v1.3.1 + github.com/golangci/golangci-lint v1.23.8 // indirect github.com/gorilla/mux v1.7.4 github.com/mattn/go-colorable v0.1.4 // indirect github.com/onsi/ginkgo v1.11.0 // indirect diff --git a/go.sum b/go.sum index cad0d7031..64c3fa979 100644 --- a/go.sum +++ b/go.sum @@ -33,6 +33,8 @@ github.com/ChainSafe/go-schnorrkel v0.0.0-20200102211924-4bcbc698314f h1:4O1om+U github.com/ChainSafe/go-schnorrkel v0.0.0-20200102211924-4bcbc698314f/go.mod h1:URdX5+vg25ts3aCh8H5IFZybJYKWhJHYMTnf+ULtoC4= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/OpenPeeDeeP/depguard v1.0.1 h1:VlW4R6jmBIv3/u1JNlawEvJMM4J+dPORPaZasQee8Us= +github.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmUx/1V+TNhjQvM= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/sarama v1.26.1/go.mod h1:NbSGBSSndYaIhRcBtY9V0U7AyH+x71bG668AuWys/yU= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= @@ -75,6 +77,8 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/bombsimon/wsl/v2 v2.0.0 h1:+Vjcn+/T5lSrO8Bjzhk4v14Un/2UyCA1E3V5j9nwTkQ= +github.com/bombsimon/wsl/v2 v2.0.0/go.mod h1:mf25kr/SqFEPhhcxW1+7pxzGlW+hIl/hYTKY95VwV8U= github.com/btcsuite/btcd v0.0.0-20171128150713-2e60448ffcc6/go.mod h1:Dmm/EzmjnCiweXmzRIAiUWCInVmPgjkzgv5k4tVyXiQ= github.com/btcsuite/btcd v0.0.0-20190115013929-ed77733ec07d/go.mod h1:d3C0AkH6BRcvO8T0UEPu53cnw4IbV63x1bEjildYhO0= github.com/btcsuite/btcd v0.20.1-beta h1:Ik4hyJqN8Jfyv3S4AGBOmyouMsYE3EdYODkMbQjwPGw= @@ -107,6 +111,7 @@ github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= @@ -121,6 +126,7 @@ github.com/cosmos/ledger-cosmos-go v0.11.1 h1:9JIYsGnXP613pb2vPjFeMMjBI5lEDsEaF6 github.com/cosmos/ledger-cosmos-go v0.11.1/go.mod h1:J8//BsAGTo3OC/vDLjMRFLW6q0WAaXvHnVc7ZmE8iUY= github.com/cosmos/ledger-go v0.9.2 h1:Nnao/dLwaVTk1Q5U9THldpUMMXU94BOTWPddSmVB6pI= github.com/cosmos/ledger-go v0.9.2/go.mod h1:oZJ2hHAZROdlHiwTg4t7kP+GKIIkBT+o6c9QWFanOyI= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= @@ -166,6 +172,7 @@ github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqL github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 h1:E2s37DuLxFhQDg5gKsWoLBOB0n+ZW8s599zru8FJ2/Y= github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0= github.com/fatih/color v1.3.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fjl/memsize v0.0.0-20180418122429-ca190fb6ffbc/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c= @@ -183,11 +190,15 @@ github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gibson042/canonicaljson-go v1.0.3 h1:EAyF8L74AWabkyUmrvEFHEt/AGFQeD6RfwbAuf0j1bI= github.com/gibson042/canonicaljson-go v1.0.3/go.mod h1:DsLpJTThXyGNO+KZlI85C1/KDcImpP67k/RKVjcaEqo= +github.com/go-critic/go-critic v0.4.1 h1:4DTQfT1wWwLg/hzxwD9bkdhDQrdJtxe6DUTadPlrIeE= +github.com/go-critic/go-critic v0.4.1/go.mod h1:7/14rZGnZbY6E38VEGk2kVhoq6itzc1E68facVDK23g= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.10.0 h1:dXFJfIHVvUcpSgDOV+Ne6t7jXri8Tfv2uOLHUZ2XNuo= github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= +github.com/go-lintpack/lintpack v0.5.2 h1:DI5mA3+eKdWeJ40nU4d6Wc26qmdG8RCi/btYq0TuRN0= +github.com/go-lintpack/lintpack v0.5.2/go.mod h1:NwZuYi2nUHho8XEIZ6SIxihrnPoqBTDqfpXvXAN0sXM= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4= @@ -197,8 +208,32 @@ github.com/go-sourcemap/sourcemap v2.1.2+incompatible/go.mod h1:F8jJfvm2KbVjc5Nq github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-toolsmith/astcast v1.0.0 h1:JojxlmI6STnFVG9yOImLeGREv8W2ocNUM+iOhR6jE7g= +github.com/go-toolsmith/astcast v1.0.0/go.mod h1:mt2OdQTeAQcY4DQgPSArJjHCcOwlX+Wl/kwN+LbLGQ4= +github.com/go-toolsmith/astcopy v1.0.0 h1:OMgl1b1MEpjFQ1m5ztEO06rz5CUd3oBv9RF7+DyvdG8= +github.com/go-toolsmith/astcopy v1.0.0/go.mod h1:vrgyG+5Bxrnz4MZWPF+pI4R8h3qKRjjyvV/DSez4WVQ= +github.com/go-toolsmith/astequal v0.0.0-20180903214952-dcb477bfacd6/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY= +github.com/go-toolsmith/astequal v1.0.0 h1:4zxD8j3JRFNyLN46lodQuqz3xdKSrur7U/sr0SDS/gQ= +github.com/go-toolsmith/astequal v1.0.0/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY= +github.com/go-toolsmith/astfmt v0.0.0-20180903215011-8f8ee99c3086/go.mod h1:mP93XdblcopXwlyN4X4uodxXQhldPGZbcEJIimQHrkg= +github.com/go-toolsmith/astfmt v1.0.0 h1:A0vDDXt+vsvLEdbMFJAUBI/uTbRw1ffOPnxsILnFL6k= +github.com/go-toolsmith/astfmt v1.0.0/go.mod h1:cnWmsOAuq4jJY6Ct5YWlVLmcmLMn1JUPuQIHCY7CJDw= +github.com/go-toolsmith/astinfo v0.0.0-20180906194353-9809ff7efb21/go.mod h1:dDStQCHtmZpYOmjRP/8gHHnCCch3Zz3oEgCdZVdtweU= +github.com/go-toolsmith/astp v0.0.0-20180903215135-0af7e3c24f30/go.mod h1:SV2ur98SGypH1UjcPpCatrV5hPazG6+IfNHbkDXBRrk= +github.com/go-toolsmith/astp v1.0.0 h1:alXE75TXgcmupDsMK1fRAy0YUzLzqPVvBKoyWV+KPXg= +github.com/go-toolsmith/astp v1.0.0/go.mod h1:RSyrtpVlfTFGDYRbrjyWP1pYu//tSFcvdYrA8meBmLI= +github.com/go-toolsmith/pkgload v0.0.0-20181119091011-e9e65178eee8/go.mod h1:WoMrjiy4zvdS+Bg6z9jZH82QXwkcgCBX6nOfnmdaHks= +github.com/go-toolsmith/pkgload v1.0.0/go.mod h1:5eFArkbO80v7Z0kdngIxsRXRMTaX4Ilcwuh3clNrQJc= +github.com/go-toolsmith/strparse v1.0.0 h1:Vcw78DnpCAKlM20kSbAyO4mPfJn/lyYA4BJUDxe2Jb4= +github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8= +github.com/go-toolsmith/typep v1.0.0 h1:zKymWyA1TRYvqYrYDrfEMZULyrhcnGY3x7LDKU2XQaA= +github.com/go-toolsmith/typep v1.0.0/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU= +github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= +github.com/gofrs/flock v0.0.0-20190320160742-5135e617513b h1:ekuhfTjngPhisSjOJ0QWKpPQE8/rbknHaes6WVJj5Hw= +github.com/gofrs/flock v0.0.0-20190320160742-5135e617513b/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -229,6 +264,36 @@ github.com/golang/protobuf v1.4.0-rc.4/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9c github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2 h1:23T5iq8rbUYlhpt5DB4XJkc6BU31uODLD1o1gKvZmD0= +github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2/go.mod h1:k9Qvh+8juN+UKMCS/3jFtGICgW8O96FVaZsaxdzDkR4= +github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a h1:w8hkcTqaFpzKqonE9uMCefW1WDie15eSP/4MssdenaM= +github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= +github.com/golangci/errcheck v0.0.0-20181223084120-ef45e06d44b6 h1:YYWNAGTKWhKpcLLt7aSj/odlKrSrelQwlovBpDuf19w= +github.com/golangci/errcheck v0.0.0-20181223084120-ef45e06d44b6/go.mod h1:DbHgvLiFKX1Sh2T1w8Q/h4NAI8MHIpzCdnBUDTXU3I0= +github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613 h1:9kfjN3AdxcbsZBf8NjltjWihK2QfBBBZuv91cMFfDHw= +github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613/go.mod h1:SyvUF2NxV+sN8upjjeVYr5W7tyxaT1JVtvhKhOn2ii8= +github.com/golangci/goconst v0.0.0-20180610141641-041c5f2b40f3 h1:pe9JHs3cHHDQgOFXJJdYkK6fLz2PWyYtP4hthoCMvs8= +github.com/golangci/goconst v0.0.0-20180610141641-041c5f2b40f3/go.mod h1:JXrF4TWy4tXYn62/9x8Wm/K/dm06p8tCKwFRDPZG/1o= +github.com/golangci/gocyclo v0.0.0-20180528134321-2becd97e67ee h1:J2XAy40+7yz70uaOiMbNnluTg7gyQhtGqLQncQh+4J8= +github.com/golangci/gocyclo v0.0.0-20180528134321-2becd97e67ee/go.mod h1:ozx7R9SIwqmqf5pRP90DhR2Oay2UIjGuKheCBCNwAYU= +github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a h1:iR3fYXUjHCR97qWS8ch1y9zPNsgXThGwjKPrYfqMPks= +github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a/go.mod h1:9qCChq59u/eW8im404Q2WWTrnBUQKjpNYKMbU4M7EFU= +github.com/golangci/golangci-lint v1.23.8 h1:NlD+Ld2TKH8qVmADy4iEvPxVmXaqPIeQu3d1cGQP4jc= +github.com/golangci/golangci-lint v1.23.8/go.mod h1:g/38bxfhp4rI7zeWSxcdIeHTQGS58TCak8FYcyCmavQ= +github.com/golangci/ineffassign v0.0.0-20190609212857-42439a7714cc h1:gLLhTLMk2/SutryVJ6D4VZCU3CUqr8YloG7FPIBWFpI= +github.com/golangci/ineffassign v0.0.0-20190609212857-42439a7714cc/go.mod h1:e5tpTHCfVze+7EpLEozzMB3eafxo2KT5veNg1k6byQU= +github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0 h1:MfyDlzVjl1hoaPzPD4Gpb/QgoRfSBR0jdhwGyAWwMSA= +github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg= +github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca h1:kNY3/svz5T29MYHubXix4aDDuE3RWHkPvopM/EDv/MA= +github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca/go.mod h1:tvlJhZqDe4LMs4ZHD0oMUlt9G2LWuDGoisJTBzLMV9o= +github.com/golangci/misspell v0.0.0-20180809174111-950f5d19e770 h1:EL/O5HGrF7Jaq0yNhBLucz9hTuRzj2LdwGBOaENgxIk= +github.com/golangci/misspell v0.0.0-20180809174111-950f5d19e770/go.mod h1:dEbvlSfYbMQDtrpRMQU675gSDLDNa8sCPPChZ7PhiVA= +github.com/golangci/prealloc v0.0.0-20180630174525-215b22d4de21 h1:leSNB7iYzLYSSx3J/s5sVf4Drkc68W2wm4Ixh/mr0us= +github.com/golangci/prealloc v0.0.0-20180630174525-215b22d4de21/go.mod h1:tf5+bzsHdTM0bsB7+8mt0GUMvjCgwLpTapNZHU8AajI= +github.com/golangci/revgrep v0.0.0-20180526074752-d9c87f5ffaf0 h1:HVfrLniijszjS1aiNg8JbBMO2+E1WIQ+j/gL4SQqGPg= +github.com/golangci/revgrep v0.0.0-20180526074752-d9c87f5ffaf0/go.mod h1:qOQCunEYvmd/TLamH+7LlVccLvUH5kZNhbCgTHoBbp4= +github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 h1:zwtduBRr5SSWhqsYNgcuWO2kFlpdOZbP0+yRjmvPGys= +github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4/go.mod h1:Izgrg8RkN3rCIMLGE9CyYmU9pY2Jer6DgANEnZ/L/cQ= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -263,6 +328,8 @@ github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989/go.mod h1:E7qH github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3 h1:JVnpOZS+qxli+rgVl98ILOXVNbW+kb5wcxeGx8ShUIw= +github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE= github.com/graph-gophers/graphql-go v0.0.0-20191115155744-f33e81362277/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= @@ -316,9 +383,14 @@ github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458 h1:6OvNmYgJye github.com/jackpal/go-nat-pmp v1.0.2-0.20160603034137-1fa385a6f458/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jingyugao/rowserrcheck v0.0.0-20191204022205-72ab7603b68a h1:GmsqmapfzSJkm28dhRoHz2tLRbJmqhU86IPgBtN3mmk= +github.com/jingyugao/rowserrcheck v0.0.0-20191204022205-72ab7603b68a/go.mod h1:xRskid8CManxVta/ALEhJha/pweKBaVG6fWgc0yH25s= +github.com/jirfag/go-printf-func-name v0.0.0-20191110105641-45db9963cdd3 h1:jNYPNLe3d8smommaoQlK7LOA5ESyUJJ+Wf79ZtA7Vp4= +github.com/jirfag/go-printf-func-name v0.0.0-20191110105641-45db9963cdd3/go.mod h1:HEWGJkRDzjJY2sqdDwxccsGicWEf9BQOZsq2tV+xzM0= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmhodges/levigo v1.0.0 h1:q5EC36kV79HWeTBWsod3mG11EgStG3qArTKcvlksN1U= github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ= +github.com/jmoiron/sqlx v1.2.1-0.20190826204134-d7d95172beb5/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= @@ -336,29 +408,41 @@ github.com/keybase/go-keychain v0.0.0-20190712205309-48d3d31d256d h1:Z+RDyXzjKE0 github.com/keybase/go-keychain v0.0.0-20190712205309-48d3d31d256d/go.mod h1:JJNrCn9otv/2QP4D7SMJBgaleKpOf66PnW6F5WGNRIc= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.9.8/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.10.1/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/reedsolomon v1.9.3/go.mod h1:CwCi+NUr9pqSVktrkN+Ondf06rkhYZ/pcNv7fu+8Un4= +github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/libp2p/go-buffer-pool v0.0.2 h1:QNK2iAFa8gjAe1SPz6mHSMuCcjs+X1wlHzeOSqcmlfs= github.com/libp2p/go-buffer-pool v0.0.2/go.mod h1:MvaB6xw5vOrDl8rYZGLFdKAuk/hRoRZd1Vi32+RXyFM= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= +github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/matoous/godox v0.0.0-20190911065817-5d6d842e92eb h1:RHba4YImhrUVQDHUCe2BNSOz4tVy2yGyXhvYDvxGgeE= +github.com/matoous/godox v0.0.0-20190911065817-5d6d842e92eb/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.0/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= @@ -375,6 +459,8 @@ github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzp github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= @@ -384,6 +470,7 @@ github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceT github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-ps v0.0.0-20190716172923-621e5597135b/go.mod h1:r1VsdOzOPt1ZSrGZWFoNhsAedKnEd6r9Np1+5blZCWk= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= @@ -394,6 +481,7 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mozilla/tls-observatory v0.0.0-20190404164649-a3c1b6cfecfd/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0= github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= @@ -404,6 +492,8 @@ github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzE github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d h1:AREM5mwr4u1ORQBMvzfzBgpsctsbQikCVpvC+tX285E= +github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU= github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= @@ -493,6 +583,7 @@ github.com/prometheus/tsdb v0.6.2-0.20190402121629-4f204dcbc150/go.mod h1:qhTCs0 github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/prometheus/tsdb v0.9.1 h1:IWaAmWkYlgG7/S4iw4IpAQt5Y35QaZM6/GsZ7GsjAuk= github.com/prometheus/tsdb v0.9.1/go.mod h1:oi49uRhEe9dPUTlS3JRZOwJuVi6tmh10QSgwXEyGCt4= +github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI= github.com/rakyll/statik v0.1.7 h1:OF3QCZUuyPxuGEP7B4ypUa7sB/iHtqOTDYZXGM8KOdQ= github.com/rakyll/statik v0.1.7/go.mod h1:AlZONWzMtEnMs7W4e/1LURLiI49pIMmp6V9Unghqrcc= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= @@ -509,13 +600,21 @@ github.com/rs/cors v0.0.0-20160617231935-a62a804a8a00/go.mod h1:gFx+x8UowdsKA9Ac github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521/go.mod h1:RvLn4FgxWubrpZHtQLnOf6EwhN2hEMusxZOhcW9H3UQ= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/securego/gosec v0.0.0-20200103095621-79fbf3af8d83 h1:AtnWoOvTioyDXFvu96MWEeE8qj4COSQnJogzLy/u41A= +github.com/securego/gosec v0.0.0-20200103095621-79fbf3af8d83/go.mod h1:vvbZ2Ae7AzSq3/kywjUDxSNq2SJ27RxCz2un0H3ePqE= +github.com/shirou/gopsutil v0.0.0-20190901111213-e4ec7b275ada/go.mod h1:WWnYX4lzhCH5h/3YBfyVA3VbLYjlMZZAQcW9ojMexNc= +github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc= +github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= +github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= @@ -524,6 +623,8 @@ github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9 github.com/snikch/goodman v0.0.0-20171125024755-10e37e294daa/go.mod h1:oJyF+mSPHbB5mVY2iO9KV3pTt/QbIkGaO8gQ2WrDbP4= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= +github.com/sourcegraph/go-diff v0.5.1 h1:gO6i5zugwzo1RVTvgvfwCOSVegNuvnNi6bAD1QCmkHs= +github.com/sourcegraph/go-diff v0.5.1/go.mod h1:j2dHj3m8aZgQO8lMTcTnBcXkRRRqi34cd2MNlA9u1mE= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.1/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= @@ -532,6 +633,7 @@ github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTd github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/cobra v0.0.6/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v0.0.7 h1:FfTH+vuMXOas8jmfb5/M7dzEYx7LpcLb7a0LPe34uOU= github.com/spf13/cobra v0.0.7/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= @@ -542,7 +644,9 @@ github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/spf13/viper v1.6.1/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k= github.com/spf13/viper v1.6.2/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k= github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= @@ -591,14 +695,29 @@ github.com/tendermint/tm-db v0.4.1/go.mod h1:JsJ6qzYkCGiGwm5GHl/H5GLI9XLb6qZX7PR github.com/tendermint/tm-db v0.5.0/go.mod h1:lSq7q5WRR/njf1LnhiZ/lIJHk2S8Y1Zyq5oP/3o9C2U= github.com/tendermint/tm-db v0.5.1 h1:H9HDq8UEA7Eeg13kdYckkgwwkQLBnJGgX4PgLJRhieY= github.com/tendermint/tm-db v0.5.1/go.mod h1:g92zWjHpCYlEvQXvy9M168Su8V1IBEeawpXVVBaK4f4= +github.com/timakin/bodyclose v0.0.0-20190930140734-f7f2e9bca95e h1:RumXZ56IrCj4CL+g1b9OL/oH0QnsF976bC8xQFYUD5Q= +github.com/timakin/bodyclose v0.0.0-20190930140734-f7f2e9bca95e/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk= github.com/tjfoc/gmsm v1.3.0/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tommy-muehle/go-mnd v1.1.1 h1:4D0wuPKjOTiK2garzuPGGvm4zZ/wLYDOH8TJSABC7KU= +github.com/tommy-muehle/go-mnd v1.1.1/go.mod h1:dSUh0FtTP8VhvkL1S+gUR1OKd9ZnSaozuI6r3m6wOig= github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef h1:wHSqTBrZW24CsNJDfeh9Ex6Pm0Rcpc7qrgKBiL44vF4= github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/ultraware/funlen v0.0.2 h1:Av96YVBwwNSe4MLR7iI/BIa3VyI7/djnto/pK3Uxbdo= +github.com/ultraware/funlen v0.0.2/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA= +github.com/ultraware/whitespace v0.0.4 h1:If7Va4cM03mpgrNH9k49/VOicWpGoG70XPBFFODYDsg= +github.com/ultraware/whitespace v0.0.4/go.mod h1:aVMh/gQve5Maj9hQ/hg+F75lr/X5A89uZnzAmWSineA= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/uudashr/gocognit v1.0.1 h1:MoG2fZ0b/Eo7NXoIwCVFLG5JED3qgQz5/NEE+rOsjPs= +github.com/uudashr/gocognit v1.0.1/go.mod h1:j44Ayx2KW4+oB6SWMv8KsmHzZrOInQav7D3cQMJ5JUM= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.2.0/go.mod h1:4vX61m6KN+xDduDNwXrhIAVZaZaZiQ1luJk8LWSxF3s= +github.com/valyala/quicktemplate v1.2.0/go.mod h1:EH+4AkTd43SvgIbQHYu59/cJyxDoOVRUAfrukLPuGJ4= +github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208 h1:1cngl9mPEoITZG8s8cVcUy5CeIBYhEESkOB7m6Gmkrk= github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208/go.mod h1:IotVbo4F+mw0EzQ08zFqg7pK3FebNXpaMsRy2RT+Ees= github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= @@ -629,6 +748,7 @@ go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -658,10 +778,12 @@ golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee h1:WG0RUwxtNT4qqaXX3DPA8zHFNm/D9xaBpxzHt1WcA/E= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -680,6 +802,7 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0 h1:Jcxah/M+oLZ/R4/z5RzfPzGbPXnVDPkEDtf2JnuxN+U= @@ -702,6 +825,7 @@ golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -714,6 +838,7 @@ golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -730,28 +855,42 @@ golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181117154741-2ddaf7f79a09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190110163146-51295c7ec13a/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190221204921-83362c3779f5/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190311215038-5c2858a9cfe5/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190322203728-c1a832b0ad89/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190521203540-521d6ed310dd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190719005602-e377ae9d6386/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113232020-e2727e816f5a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200102140908-9497f49d5709/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204192400-7124308813f3/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200221224223-e1da425f72fd h1:hHkvGJK23seRCflePJnVa9IMv8fsuavSCWKd11kDQFs= golang.org/x/tools v0.0.0-20200221224223-e1da425f72fd/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -835,6 +974,7 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -846,9 +986,18 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed h1:WX1yoOaKQfddO/mLzdV4wptyWgoH/6hwLs7QHTixo0I= +mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc= +mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b h1:DxJ5nJdkhDlLok9K6qO+5290kphDJbHOQO1DFFFTeBo= +mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4= +mvdan.cc/unparam v0.0.0-20190720180237-d51796306d8f h1:Cq7MalBHYACRd6EesksG1Q8EoIAKOsiZviGKbOLIej4= +mvdan.cc/unparam v0.0.0-20190720180237-d51796306d8f/go.mod h1:4G1h5nDURzA3bwVMZIVpwbkw+04kSxk3rAtzlimaUJw= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= +sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4 h1:JPJh2pk3+X4lXAkZIk2RuE/7/FoK9maXw+TNPJhVS/c= +sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= diff --git a/rpc/filter_api.go b/rpc/filter_api.go index e838671d2..d382ad29b 100644 --- a/rpc/filter_api.go +++ b/rpc/filter_api.go @@ -12,7 +12,6 @@ import ( "github.com/ethereum/go-ethereum/core/types" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth/filters" - "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/rpc" clientcontext "github.com/cosmos/cosmos-sdk/client/context" @@ -39,8 +38,6 @@ type filter struct { type PublicFilterAPI struct { cliCtx clientcontext.CLIContext backend Backend - mux *event.TypeMux - quit chan struct{} events *EventSystem filtersMu sync.Mutex filters map[rpc.ID]*filter @@ -71,7 +68,7 @@ func (api *PublicFilterAPI) timeoutLoop() { for id, f := range api.filters { select { case <-f.deadline.C: - f.s.Unsubscribe(api.events) + _ = f.s.Unsubscribe(api.events) delete(api.filters, id) default: continue @@ -184,6 +181,7 @@ func (api *PublicFilterAPI) NewBlockFilter() rpc.ID { api.filtersMu.Unlock() go func() { + // nolint: gosimple for { select { case event := <-headerSub.eventChannel: @@ -230,7 +228,10 @@ func (api *PublicFilterAPI) NewHeads(ctx context.Context) (*rpc.Subscription, er err = fmt.Errorf("invalid event data %T, expected %s", event.Data, tmtypes.EventNewBlockHeader) return } - notifier.Notify(rpcSub.ID, evHeader.Header) + err = notifier.Notify(rpcSub.ID, evHeader.Header) + if err != nil { + return + } case <-rpcSub.Err(): err = headersSub.Unsubscribe(api.events) return @@ -293,7 +294,10 @@ func (api *PublicFilterAPI) Logs(ctx context.Context, crit filters.FilterCriteri logs := filterLogs(resultData.Logs, crit.FromBlock, crit.ToBlock, crit.Addresses, crit.Topics) for _, log := range logs { - notifier.Notify(rpcSub.ID, &log) + err = notifier.Notify(rpcSub.ID, log) + if err != nil { + return + } } case <-rpcSub.Err(): // client send an unsubscribe request err = logsSub.Unsubscribe(api.events) @@ -337,6 +341,7 @@ func (api *PublicFilterAPI) NewFilter(criteria filters.FilterCriteria) (rpc.ID, api.filtersMu.Unlock() go func() { + // nolint: gosimple for { select { case event := <-logsSub.eventChannel: @@ -417,8 +422,8 @@ func (api *PublicFilterAPI) UninstallFilter(id rpc.ID) bool { return false } - f.s.Unsubscribe(api.events) - return true + err := f.s.Unsubscribe(api.events) + return err != nil } // GetFilterLogs returns the logs for the filter with the given id. diff --git a/rpc/filter_system.go b/rpc/filter_system.go index c54288678..a4af6e004 100644 --- a/rpc/filter_system.go +++ b/rpc/filter_system.go @@ -132,19 +132,19 @@ func (es *EventSystem) SubscribeLogs(crit filters.FilterCriteria) (*Subscription } } -// subscribeMinedPendingLogs creates a subscription that returned mined and -// pending logs that match the given criteria. -func (es *EventSystem) subscribeMinedPendingLogs(crit filters.FilterCriteria) (*Subscription, error) { - sub := &subscription{ - id: rpc.NewID(), - typ: filters.MinedAndPendingLogsSubscription, - logsCrit: crit, - created: time.Now(), - logs: make(chan []*ethtypes.Log), - installed: make(chan struct{}), - } - return es.subscribe(sub) -} +// // subscribeMinedPendingLogs creates a subscription that returned mined and +// // pending logs that match the given criteria. +// func (es *EventSystem) subscribeMinedPendingLogs(crit filters.FilterCriteria) (*Subscription, error) { +// sub := &subscription{ +// id: rpc.NewID(), +// typ: filters.MinedAndPendingLogsSubscription, +// logsCrit: crit, +// created: time.Now(), +// logs: make(chan []*ethtypes.Log), +// installed: make(chan struct{}), +// } +// return es.subscribe(sub) +// } // subscribeLogs creates a subscription that will write all logs matching the // given criteria to the given logs channel. @@ -160,19 +160,19 @@ func (es *EventSystem) subscribeLogs(crit filters.FilterCriteria) (*Subscription return es.subscribe(sub) } -// subscribePendingLogs creates a subscription that writes transaction hashes for -// transactions that enter the transaction pool. -func (es *EventSystem) subscribePendingLogs(crit filters.FilterCriteria) (*Subscription, error) { - sub := &subscription{ - id: rpc.NewID(), - typ: filters.PendingLogsSubscription, - logsCrit: crit, - created: time.Now(), - logs: make(chan []*ethtypes.Log), - installed: make(chan struct{}), - } - return es.subscribe(sub) -} +// // subscribePendingLogs creates a subscription that writes transaction hashes for +// // transactions that enter the transaction pool. +// func (es *EventSystem) subscribePendingLogs(crit filters.FilterCriteria) (*Subscription, error) { +// sub := &subscription{ +// id: rpc.NewID(), +// typ: filters.PendingLogsSubscription, +// logsCrit: crit, +// created: time.Now(), +// logs: make(chan []*ethtypes.Log), +// installed: make(chan struct{}), +// } +// return es.subscribe(sub) +// } // SubscribeNewHeads subscribes to new block headers events. func (es EventSystem) SubscribeNewHeads() (*Subscription, error) { @@ -238,11 +238,11 @@ func (es *EventSystem) handleChainEvent(filterIdx filterIndex, ev coretypes.Resu func (es *EventSystem) eventLoop() { // Ensure all subscriptions get cleaned up defer func() { - // es.txsSub.Unsubscribe(es) - es.logsSub.Unsubscribe(es) - // es.rmLogsSub.Unsubscribe(es) - // es.pendingLogsSub.Unsubscribe(es) - es.chainSub.Unsubscribe(es) + // _ = es.txsSub.Unsubscribe(es) + _ = es.logsSub.Unsubscribe(es) + // _ = es.rmLogsSub.Unsubscribe(es) + // _ = es.pendingLogsSub.Unsubscribe(es) + _ = es.chainSub.Unsubscribe(es) }() index := make(filterIndex) diff --git a/rpc/filters.go b/rpc/filters.go index e7442db93..63ac47031 100644 --- a/rpc/filters.go +++ b/rpc/filters.go @@ -32,7 +32,7 @@ func NewRangeFilter(backend Backend, begin, end int64, addresses []common.Addres // Flatten the address and topic filter clauses into a single bloombits filter // system. Since the bloombits are not positional, nil topics are permitted, // which get flattened into a nil byte slice. - var filtersBz [][][]byte + var filtersBz [][][]byte // nolint: prealloc if len(addresses) > 0 { filter := make([][]byte, len(addresses)) for i, address := range addresses { @@ -139,7 +139,7 @@ func (f *Filter) blockLogs(header *ethtypes.Header) ([]*ethtypes.Log, error) { return []*ethtypes.Log{}, err } - var unfiltered []*ethtypes.Log + var unfiltered []*ethtypes.Log // nolint: prealloc for _, logs := range logsList { unfiltered = append(unfiltered, logs...) } @@ -190,7 +190,7 @@ Logs: } // If the to filtered topics is greater than the amount of topics in logs, skip. if len(topics) > len(log.Topics) { - continue Logs + continue } for i, sub := range topics { match := len(sub) == 0 // empty rule set == wildcard From 1b888f02d78116a4903b2e1dd639d68bdddb0773 Mon Sep 17 00:00:00 2001 From: Federico Kunze Date: Mon, 8 Jun 2020 10:58:53 +0200 Subject: [PATCH 14/36] update rpc filters --- rpc/filter_api.go | 310 ++++++++++++++++++++++--------------------- rpc/filter_system.go | 281 +++++++++++++++++++++------------------ rpc/filters.go | 23 +++- 3 files changed, 325 insertions(+), 289 deletions(-) diff --git a/rpc/filter_api.go b/rpc/filter_api.go index d382ad29b..6dd4f3423 100644 --- a/rpc/filter_api.go +++ b/rpc/filter_api.go @@ -19,6 +19,19 @@ import ( evmtypes "github.com/cosmos/ethermint/x/evm/types" ) +// FiltersBackend defines the methods requided by the PublicFilterAPI backend +type FiltersBackend interface { + GetBlockByNumber(blockNum rpc.BlockNumber, fullTx bool) (map[string]interface{}, error) + HeaderByNumber(blockNr rpc.BlockNumber) (*ethtypes.Header, error) + HeaderByHash(blockHash common.Hash) (*ethtypes.Header, error) + // GetReceipts(blockHash common.Hash) (ethtypes.Receipts, error) + GetLogs(blockHash common.Hash) ([][]*ethtypes.Log, error) + + GetTransactionLogs(txHash common.Hash) ([]*ethtypes.Log, error) + BloomStatus() (uint64, uint64) + // ServiceFilter(session *bloombits.MatcherSession) +} + // consider a filter inactive if it has not been polled for within deadline var deadline = 5 * time.Minute @@ -37,14 +50,14 @@ type filter struct { // information related to the Ethereum protocol such as blocks, transactions and logs. type PublicFilterAPI struct { cliCtx clientcontext.CLIContext - backend Backend + backend FiltersBackend events *EventSystem filtersMu sync.Mutex filters map[rpc.ID]*filter } // NewPublicFilterAPI returns a new PublicFilterAPI instance. -func NewPublicFilterAPI(cliCtx clientcontext.CLIContext, backend Backend) *PublicFilterAPI { +func NewPublicFilterAPI(cliCtx clientcontext.CLIContext, backend FiltersBackend) *PublicFilterAPI { api := &PublicFilterAPI{ cliCtx: cliCtx, backend: backend, @@ -52,7 +65,7 @@ func NewPublicFilterAPI(cliCtx clientcontext.CLIContext, backend Backend) *Publi events: NewEventSystem(cliCtx.Client), } - go api.timeoutLoop() + // go api.timeoutLoop() return api } @@ -78,127 +91,127 @@ func (api *PublicFilterAPI) timeoutLoop() { } } -// // NewPendingTransactionFilter creates a filter that fetches pending transaction hashes -// // as transactions enter the pending state. -// // -// // It is part of the filter package because this filter can be used through the -// // `eth_getFilterChanges` polling method that is also used for log filters. -// // -// // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newpendingtransactionfilter -// func (api *PublicFilterAPI) NewPendingTransactionFilter() rpc.ID { -// var ( -// pendingTxs = make(chan []common.Hash) -// pendingTxSub = api.events.SubscribePendingTxs(pendingTxs) -// ) - -// ctx, cancelFn := context.WithTimeout(context.Background(), api.events.GetTimeout()) -// defer cancelFn() - -// api.events.WithContext(ctx) - -// api.filtersMu.Lock() -// api.filters[pendingTxSub.ID] = NewFilter(api.backend, &filters.FilterCriteria{}, filters.PendingTransactionsSubscription) -// api.filtersMu.Unlock() - -// go func() { -// for { -// select { -// case ph := <-pendingTxs: -// api.filtersMu.Lock() -// if f, found := api.filters[pendingTxSub.ID]; found { -// f.hashes = append(f.hashes, ph...) -// } -// api.filtersMu.Unlock() -// case <-pendingTxSub.Err(): -// api.filtersMu.Lock() -// delete(api.filters, pendingTxSub.ID) -// api.filtersMu.Unlock() -// return -// } -// } -// }() - -// return pendingTxSub.ID -// } - -// // NewPendingTransactions creates a subscription that is triggered each time a transaction -// // enters the transaction pool and was signed from one of the transactions this nodes manages. -// func (api *PublicFilterAPI) NewPendingTransactions(ctx context.Context) (*rpc.Subscription, error) { -// notifier, supported := rpc.NotifierFromContext(ctx) -// if !supported { -// return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported -// } - -// ctx, cancelFn := context.WithTimeout(context.Background(), api.events.GetTimeout()) -// defer cancelFn() - -// api.events.WithContext(ctx) -// rpcSub := notifier.CreateSubscription() - -// go func() { -// txHashes := make(chan []common.Hash, 128) -// pendingTxSub := api.events.SubscribePendingTxs(txHashes) - -// for { -// select { -// case hashes := <-txHashes: -// // To keep the original behaviour, send a single tx hash in one notification. -// // TODO(rjl493456442) Send a batch of tx hashes in one notification -// for _, h := range hashes { -// notifier.Notify(rpcSub.ID, h) -// } -// case <-rpcSub.Err(): -// pendingTxSub.Unsubscribe() -// return -// case <-notifier.Closed(): -// pendingTxSub.Unsubscribe() -// return -// } -// } -// }() - -// return rpcSub, nil -// } - -// NewBlockFilter creates a filter that fetches blocks that are imported into the chain. -// It is part of the filter package since polling goes with eth_getFilterChanges. +// NewPendingTransactionFilter creates a filter that fetches pending transaction hashes +// as transactions enter the pending state. // -// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newblockfilter -func (api *PublicFilterAPI) NewBlockFilter() rpc.ID { - headerSub, err := api.events.SubscribeNewHeads() +// It is part of the filter package because this filter can be used through the +// `eth_getFilterChanges` polling method that is also used for log filters. +// +// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newpendingtransactionfilter +func (api *PublicFilterAPI) NewPendingTransactionFilter() rpc.ID { + pendingTxSub, cancelSubs, err := api.events.SubscribePendingTxs() if err != nil { // return an empty id return rpc.ID("") } + defer cancelSubs() + + api.filtersMu.Lock() + api.filters[pendingTxSub.ID()] = &filter{typ: filters.PendingTransactionsSubscription, deadline: time.NewTimer(deadline), hashes: make([]common.Hash, 0), s: pendingTxSub} + api.filtersMu.Unlock() + + // go func() { + // for { + // select { + // case ph := <-pendingTxs: + // api.filtersMu.Lock() + // if f, found := api.filters[pendingTxSub.ID()]; found { + // f.hashes = append(f.hashes, ph...) + // } + // api.filtersMu.Unlock() + // } + // } + // }() + + return pendingTxSub.ID() +} + +// NewPendingTransactions creates a subscription that is triggered each time a transaction +// enters the transaction pool and was signed from one of the transactions this nodes manages. +func (api *PublicFilterAPI) NewPendingTransactions(ctx context.Context) (*rpc.Subscription, error) { + notifier, supported := rpc.NotifierFromContext(ctx) + if !supported { + return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported + } + ctx, cancelFn := context.WithTimeout(context.Background(), deadline) defer cancelFn() api.events.WithContext(ctx) + rpcSub := notifier.CreateSubscription() + + _, cancelSubs, err := api.events.SubscribeNewHeads() + if err != nil { + return nil, err + } + + defer cancelSubs() + + // go func() { + // for { + // select { + // case hashes := <-pendingTxSub.eventChannel: + + // // To keep the original behaviour, send a single tx hash in one notification. + // // TODO(rjl493456442) Send a batch of tx hashes in one notification + // for _, h := range hashes { + // err = notifier.Notify(rpcSub.ID, h) + // if err != nil { + // return + // } + // } + // case <-rpcSub.Err(): + // err = pendingTxSub.Unsubscribe(api.events) + // return + // case <-notifier.Closed(): + // err = pendingTxSub.Unsubscribe(api.events) + // return + // } + // } + // }() + + return rpcSub, nil +} + +// NewBlockFilter creates a filter that fetches blocks that are imported into the chain. +// It is part of the filter package since polling goes with eth_getFilterChanges. +// +// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newblockfilter +func (api *PublicFilterAPI) NewBlockFilter() rpc.ID { + headerSub, cancelSubs, err := api.events.SubscribeNewHeads() + if err != nil { + // return an empty id + return rpc.ID("1") + } + + defer cancelSubs() api.filtersMu.Lock() api.filters[headerSub.ID()] = &filter{typ: filters.BlocksSubscription, deadline: time.NewTimer(deadline), hashes: make([]common.Hash, 0), s: headerSub} api.filtersMu.Unlock() - go func() { - // nolint: gosimple - for { - select { - case event := <-headerSub.eventChannel: - evHeader, ok := event.Data.(tmtypes.EventDataNewBlockHeader) - if !ok { - return - } - api.filtersMu.Lock() - if f, found := api.filters[headerSub.ID()]; found { - f.hashes = append(f.hashes, common.BytesToHash(evHeader.Header.Hash())) - } - api.filtersMu.Unlock() - } - } - }() - return headerSub.ID() + + // go func() { + // // nolint: gosimple + // for { + // select { + // case event := <-headerSub.eventChannel: + // evHeader, ok := event.Data.(tmtypes.EventDataNewBlockHeader) + // if !ok { + // return + // } + // api.filtersMu.Lock() + // if f, found := api.filters[headerSub.ID()]; found { + // f.hashes = append(f.hashes, common.BytesToHash(evHeader.Header.Hash())) + // } + // api.filtersMu.Unlock() + // } + // } + // }() + + // return headerSub.ID() } // NewHeads send a notification each time a new (header) block is appended to the chain. @@ -208,21 +221,21 @@ func (api *PublicFilterAPI) NewHeads(ctx context.Context) (*rpc.Subscription, er return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported } - ctx, cancelFn := context.WithTimeout(ctx, deadline) - defer cancelFn() - api.events.WithContext(ctx) rpcSub := notifier.CreateSubscription() - headersSub, err := api.events.SubscribeNewHeads() - if err != nil { - return nil, err - } - + var err error go func() { + headersSub, cancelSubs, err := api.events.SubscribeNewHeads() + if err != nil { + return + } + + defer cancelSubs() + for { select { - case event := <-headersSub.eventChannel: + case event := <-api.events.chainCh: evHeader, ok := event.Data.(tmtypes.EventDataNewBlockHeader) if !ok { err = fmt.Errorf("invalid event data %T, expected %s", event.Data, tmtypes.EventNewBlockHeader) @@ -252,24 +265,21 @@ func (api *PublicFilterAPI) Logs(ctx context.Context, crit filters.FilterCriteri return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported } - ctx, cancelFn := context.WithTimeout(context.Background(), deadline) - defer cancelFn() - api.events.WithContext(ctx) + rpcSub := notifier.CreateSubscription() - var ( - rpcSub = notifier.CreateSubscription() - ) + var err error + go func() { + logsSub, cancelSubs, err := api.events.SubscribeLogs(crit) + if err != nil { + return + } - logsSub, err := api.events.SubscribeLogs(crit) - if err != nil { - return nil, err - } + defer cancelSubs() - go func() { for { select { - case event := <-logsSub.eventChannel: + case event := <-api.events.logsCh: // filter only events from EVM module txs _, isMsgEthermint := event.Events[evmtypes.TypeMsgEthermint] _, isMsgEthereumTx := event.Events[evmtypes.TypeMsgEthereumTx] @@ -326,37 +336,27 @@ func (api *PublicFilterAPI) Logs(ctx context.Context, crit filters.FilterCriteri // // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newfilter func (api *PublicFilterAPI) NewFilter(criteria filters.FilterCriteria) (rpc.ID, error) { - logsSub, err := api.events.SubscribeLogs(criteria) - if err != nil { - return rpc.ID(""), err - } + var ( + filterID = rpc.ID("") + err error + ) - ctx, cancelFn := context.WithTimeout(context.Background(), deadline) - defer cancelFn() + go func() { + logsSub, cancelSubs, err := api.events.SubscribeLogs(criteria) + if err != nil { + return + } - api.events.WithContext(ctx) + defer cancelSubs() - api.filtersMu.Lock() - api.filters[logsSub.ID()] = &filter{typ: filters.LogsSubscription, crit: criteria, deadline: time.NewTimer(deadline), logs: make([]*types.Log, 0), s: logsSub} - api.filtersMu.Unlock() + filterID = logsSub.ID() - go func() { - // nolint: gosimple for { select { - case event := <-logsSub.eventChannel: - // filter only events from EVM module txs - _, isMsgEthermint := event.Events[evmtypes.TypeMsgEthermint] - _, isMsgEthereumTx := event.Events[evmtypes.TypeMsgEthereumTx] - - if !(isMsgEthermint || isMsgEthereumTx) { - // ignore transaction - return - } - + case event := <-api.events.logsCh: dataTx, ok := event.Data.(tmtypes.EventDataTx) if !ok { - err = fmt.Errorf("invalid event data %T, expected %s", event.Data, tmtypes.EventTx) + err = fmt.Errorf("invalid event data %T, expected EventDataTx", event.Data) return } @@ -376,7 +376,11 @@ func (api *PublicFilterAPI) NewFilter(criteria filters.FilterCriteria) (rpc.ID, } }() - return logsSub.ID(), nil + if err != nil { + return rpc.ID(""), err + } + + return filterID, nil } // GetLogs returns logs matching the given argument that are stored within the state. diff --git a/rpc/filter_system.go b/rpc/filter_system.go index a4af6e004..34857816c 100644 --- a/rpc/filter_system.go +++ b/rpc/filter_system.go @@ -3,10 +3,8 @@ package rpc import ( "context" "fmt" - "sync" "time" - evmtypes "github.com/cosmos/ethermint/x/evm/types" rpcclient "github.com/tendermint/tendermint/rpc/client" coretypes "github.com/tendermint/tendermint/rpc/core/types" tmtypes "github.com/tendermint/tendermint/types" @@ -15,29 +13,17 @@ import ( ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth/filters" "github.com/ethereum/go-ethereum/rpc" -) -type subscription struct { - id rpc.ID - typ filters.Type - created time.Time - logsCrit filters.FilterCriteria - logs chan []*ethtypes.Log - // hashes chan []common.Hash - headers chan *ethtypes.Header - installed chan struct{} // closed when the filter is installed -} + sdk "github.com/cosmos/cosmos-sdk/types" -func (s subscription) event() string { - switch s.typ { - case filters.LogsSubscription, filters.PendingTransactionsSubscription, filters.MinedAndPendingLogsSubscription, filters.PendingLogsSubscription: - return tmtypes.EventTx - case filters.BlocksSubscription: - return tmtypes.EventNewBlockHeader - default: - return "" - } -} + evmtypes "github.com/cosmos/ethermint/x/evm/types" +) + +var ( + txEvents = fmt.Sprintf("%s='%s'", tmtypes.EventTypeKey, tmtypes.EventTx) + evmEvents = fmt.Sprintf("%s='%s' AND %s.%s='%s'", tmtypes.EventTypeKey, tmtypes.EventTx, sdk.EventTypeMessage, sdk.AttributeKeyModule, evmtypes.ModuleName) + headerEvents = fmt.Sprintf("%s='%s'", tmtypes.EventTypeKey, tmtypes.EventNewBlockHeader) +) // EventSystem creates subscriptions, processes events and broadcasts them to the // subscription which match the subscription criteria using the Tendermint's RPC client. @@ -45,17 +31,24 @@ type EventSystem struct { ctx context.Context client rpcclient.Client + // light client mode + lightMode bool + // Subscriptions - // txsSub *Subscription // Subscription for new transaction event + txsSub *Subscription // Subscription for new transaction event logsSub *Subscription // Subscription for new log event // rmLogsSub *Subscription // Subscription for removed log event - // pendingLogsSub *Subscription // Subscription for pending log event - chainSub *Subscription // Subscription for new chain event + pendingLogsSub *Subscription // Subscription for pending log event + chainSub *Subscription // Subscription for new chain event // Channels - install chan *subscription // install filter for event notification - uninstall chan *subscription // remove filter for event notification - eventsChannel <-chan coretypes.ResultEvent // channel to receive tendermint event results + install chan *Subscription // install filter for event notification + uninstall chan *Subscription // remove filter for event notification + txsCh <-chan coretypes.ResultEvent // Channel to receive new pending transactions event + logsCh <-chan coretypes.ResultEvent // Channel to receive new log event + pendingLogsCh <-chan coretypes.ResultEvent // Channel to receive new log event + rmLogsCh <-chan coretypes.ResultEvent // Channel to receive removed log event + chainCh <-chan coretypes.ResultEvent // Channel to receive new chain event } // NewEventSystem creates a new manager that listens for event on the given mux, @@ -66,15 +59,11 @@ type EventSystem struct { // or by stopping the given mux. func NewEventSystem(client rpcclient.Client) *EventSystem { es := &EventSystem{ - ctx: context.Background(), - client: client, + ctx: context.Background(), + client: client, + lightMode: false, } - // Subscribe events - // es.txsSub = es.SubscribeNewTxsEvent(m.txsCh) - es.logsSub, _ = es.SubscribeLogs(filters.FilterCriteria{}) - es.chainSub, _ = es.SubscribeNewHeads() - go es.eventLoop() return es } @@ -84,22 +73,33 @@ func (es *EventSystem) WithContext(ctx context.Context) { es.ctx = ctx } -func (es EventSystem) subscribe(sub *subscription) (*Subscription, error) { - es.install <- sub - <-sub.installed - - var err error - subscription := &Subscription{ - subscription: sub, +func (es *EventSystem) subscribe(sub *Subscription) (*Subscription, context.CancelFunc, error) { + var ( + err error + cancelFn context.CancelFunc + ) + + es.ctx, cancelFn = context.WithTimeout(context.Background(), deadline) + + switch sub.typ { + case filters.PendingTransactionsSubscription: + es.txsCh, err = es.client.Subscribe(es.ctx, string(sub.id), sub.event, 1000) + case filters.PendingLogsSubscription, filters.MinedAndPendingLogsSubscription: + es.pendingLogsCh, err = es.client.Subscribe(es.ctx, string(sub.id), sub.event, 1000) + case filters.LogsSubscription: + es.logsCh, err = es.client.Subscribe(es.ctx, string(sub.id), sub.event, 1000) + case filters.BlocksSubscription: + es.chainCh, err = es.client.Subscribe(es.ctx, string(sub.id), sub.event, 1000) + default: + err = fmt.Errorf("invalid filter subscription type %d", sub.typ) } - subscription.eventChannel, err = es.client.Subscribe(es.ctx, string(sub.id), tmtypes.QueryForEvent(sub.event()).String()) - return subscription, err + return sub, cancelFn, err } // SubscribeLogs creates a subscription that will write all logs matching the // given criteria to the given logs channel. Default value for the from and to // block is "latest". If the fromBlock > toBlock an error is returned. -func (es *EventSystem) SubscribeLogs(crit filters.FilterCriteria) (*Subscription, error) { +func (es *EventSystem) SubscribeLogs(crit filters.FilterCriteria) (*Subscription, context.CancelFunc, error) { var from, to rpc.BlockNumber if crit.FromBlock == nil { from = rpc.LatestBlockNumber @@ -113,9 +113,9 @@ func (es *EventSystem) SubscribeLogs(crit filters.FilterCriteria) (*Subscription } switch { - // // only interested in pending logs - // case from == rpc.PendingBlockNumber && to == rpc.PendingBlockNumber: - // return es.subscribePendingLogs(crit, logs) + // only interested in pending logs + case from == rpc.PendingBlockNumber && to == rpc.PendingBlockNumber: + return es.subscribePendingLogs(crit) // only interested in new mined logs, mined logs within a specific block range, or // logs from a specific block number to new mined blocks @@ -123,35 +123,37 @@ func (es *EventSystem) SubscribeLogs(crit filters.FilterCriteria) (*Subscription (from >= 0 && to >= 0 && to >= from): return es.subscribeLogs(crit) - // // interested in mined logs from a specific block number, new logs and pending logs - // case from >= rpc.LatestBlockNumber && to == rpc.PendingBlockNumber: - // return es.subscribeMinedPendingLogs(crit, logs) + // interested in mined logs from a specific block number, new logs and pending logs + case from >= rpc.LatestBlockNumber && to == rpc.PendingBlockNumber: + return es.subscribeMinedPendingLogs(crit) default: - return nil, fmt.Errorf("invalid from and to block combination: from > to (%d > %d)", from, to) + return nil, nil, fmt.Errorf("invalid from and to block combination: from > to (%d > %d)", from, to) } } -// // subscribeMinedPendingLogs creates a subscription that returned mined and -// // pending logs that match the given criteria. -// func (es *EventSystem) subscribeMinedPendingLogs(crit filters.FilterCriteria) (*Subscription, error) { -// sub := &subscription{ -// id: rpc.NewID(), -// typ: filters.MinedAndPendingLogsSubscription, -// logsCrit: crit, -// created: time.Now(), -// logs: make(chan []*ethtypes.Log), -// installed: make(chan struct{}), -// } -// return es.subscribe(sub) -// } +// subscribeMinedPendingLogs creates a subscription that returned mined and +// pending logs that match the given criteria. +func (es *EventSystem) subscribeMinedPendingLogs(crit filters.FilterCriteria) (*Subscription, context.CancelFunc, error) { + sub := &Subscription{ + id: rpc.NewID(), + typ: filters.MinedAndPendingLogsSubscription, + event: evmEvents, + logsCrit: crit, + created: time.Now(), + logs: make(chan []*ethtypes.Log), + installed: make(chan struct{}), + } + return es.subscribe(sub) +} // subscribeLogs creates a subscription that will write all logs matching the // given criteria to the given logs channel. -func (es *EventSystem) subscribeLogs(crit filters.FilterCriteria) (*Subscription, error) { - sub := &subscription{ +func (es *EventSystem) subscribeLogs(crit filters.FilterCriteria) (*Subscription, context.CancelFunc, error) { + sub := &Subscription{ id: rpc.NewID(), typ: filters.LogsSubscription, + event: evmEvents, logsCrit: crit, created: time.Now(), logs: make(chan []*ethtypes.Log), @@ -160,25 +162,27 @@ func (es *EventSystem) subscribeLogs(crit filters.FilterCriteria) (*Subscription return es.subscribe(sub) } -// // subscribePendingLogs creates a subscription that writes transaction hashes for -// // transactions that enter the transaction pool. -// func (es *EventSystem) subscribePendingLogs(crit filters.FilterCriteria) (*Subscription, error) { -// sub := &subscription{ -// id: rpc.NewID(), -// typ: filters.PendingLogsSubscription, -// logsCrit: crit, -// created: time.Now(), -// logs: make(chan []*ethtypes.Log), -// installed: make(chan struct{}), -// } -// return es.subscribe(sub) -// } +// subscribePendingLogs creates a subscription that writes transaction hashes for +// transactions that enter the transaction pool. +func (es *EventSystem) subscribePendingLogs(crit filters.FilterCriteria) (*Subscription, context.CancelFunc, error) { + sub := &Subscription{ + id: rpc.NewID(), + typ: filters.PendingLogsSubscription, + event: evmEvents, + logsCrit: crit, + created: time.Now(), + logs: make(chan []*ethtypes.Log), + installed: make(chan struct{}), + } + return es.subscribe(sub) +} // SubscribeNewHeads subscribes to new block headers events. -func (es EventSystem) SubscribeNewHeads() (*Subscription, error) { - sub := &subscription{ +func (es EventSystem) SubscribeNewHeads() (*Subscription, context.CancelFunc, error) { + sub := &Subscription{ id: rpc.NewID(), typ: filters.BlocksSubscription, + event: tmtypes.EventNewBlockHeader, created: time.Now(), headers: make(chan *ethtypes.Header), installed: make(chan struct{}), @@ -187,28 +191,21 @@ func (es EventSystem) SubscribeNewHeads() (*Subscription, error) { } // SubscribePendingTxs subscribes to new pending transactions events from the mempool. -func (es EventSystem) SubscribePendingTxs(hashes chan []common.Hash) (*Subscription, error) { - sub := &subscription{ +func (es EventSystem) SubscribePendingTxs() (*Subscription, context.CancelFunc, error) { + sub := &Subscription{ id: rpc.NewID(), typ: filters.PendingTransactionsSubscription, + event: txEvents, created: time.Now(), + hashes: make(chan []common.Hash), installed: make(chan struct{}), } return es.subscribe(sub) } -type filterIndex map[filters.Type]map[rpc.ID]*subscription +type filterIndex map[filters.Type]map[rpc.ID]*Subscription func (es *EventSystem) handleLogs(filterIdx filterIndex, ev coretypes.ResultEvent) { - // filter only events from EVM module txs - _, isMsgEthermint := ev.Events[evmtypes.TypeMsgEthermint] - _, isMsgEthereumTx := ev.Events[evmtypes.TypeMsgEthereumTx] - - if !(isMsgEthermint || isMsgEthereumTx) { - // ignore transaction - return - } - data, _ := ev.Data.(tmtypes.EventDataTx) resultData, err := evmtypes.DecodeResultData(data.TxResult.Result.Data) if err != nil { @@ -226,6 +223,13 @@ func (es *EventSystem) handleLogs(filterIdx filterIndex, ev coretypes.ResultEven } } +func (es *EventSystem) handleTxsEvent(filterIdx filterIndex, ev coretypes.ResultEvent) { + data, _ := ev.Data.(tmtypes.EventDataTx) + for _, f := range filterIdx[filters.PendingTransactionsSubscription] { + f.hashes <- []common.Hash{common.BytesToHash(data.Tx.Hash())} + } +} + func (es *EventSystem) handleChainEvent(filterIdx filterIndex, ev coretypes.ResultEvent) { data, _ := ev.Data.(tmtypes.EventDataNewBlockHeader) for _, f := range filterIdx[filters.BlocksSubscription] { @@ -236,29 +240,55 @@ func (es *EventSystem) handleChainEvent(filterIdx filterIndex, ev coretypes.Resu // eventLoop (un)installs filters and processes mux events. func (es *EventSystem) eventLoop() { + var ( + err error + cancelPendingTxsSubs, cancelLogsSubs, cancelHeaderSubs context.CancelFunc + ) + + // Subscribe events + es.txsSub, cancelPendingTxsSubs, err = es.SubscribePendingTxs() + if err != nil { + panic(err) + } + + defer cancelPendingTxsSubs() + + es.logsSub, cancelLogsSubs, err = es.SubscribeLogs(filters.FilterCriteria{}) + if err != nil { + panic(err) + } + + defer cancelLogsSubs() + + es.chainSub, cancelHeaderSubs, err = es.SubscribeNewHeads() + if err != nil { + panic(err) + } + + defer cancelHeaderSubs() + // Ensure all subscriptions get cleaned up defer func() { - // _ = es.txsSub.Unsubscribe(es) + _ = es.txsSub.Unsubscribe(es) _ = es.logsSub.Unsubscribe(es) // _ = es.rmLogsSub.Unsubscribe(es) - // _ = es.pendingLogsSub.Unsubscribe(es) + _ = es.pendingLogsSub.Unsubscribe(es) _ = es.chainSub.Unsubscribe(es) }() index := make(filterIndex) for i := filters.UnknownSubscription; i < filters.LastIndexSubscription; i++ { - index[i] = make(map[rpc.ID]*subscription) + index[i] = make(map[rpc.ID]*Subscription) } for { select { - case ev := <-es.eventsChannel: - switch ev.Data.(type) { - case tmtypes.EventDataTx: - es.handleLogs(index, ev) - case tmtypes.EventDataNewBlockHeader: - es.handleChainEvent(index, ev) - } + case txEvent := <-es.txsCh: + es.handleTxsEvent(index, txEvent) + case headerEv := <-es.chainCh: + es.handleChainEvent(index, headerEv) + case logsEv := <-es.logsCh: + es.handleLogs(index, logsEv) case f := <-es.install: if f.typ == filters.MinedAndPendingLogsSubscription { @@ -284,39 +314,26 @@ func (es *EventSystem) eventLoop() { // Subscription defines a wrapper for the private subscription type Subscription struct { - subscription *subscription - eventChannel <-chan coretypes.ResultEvent - unsubOnce sync.Once + id rpc.ID + typ filters.Type + event string + created time.Time + logsCrit filters.FilterCriteria + logs chan []*ethtypes.Log + hashes chan []common.Hash + headers chan *ethtypes.Header + installed chan struct{} // closed when the filter is installed } // ID returns the underlying subscription RPC identifier. func (s Subscription) ID() rpc.ID { - return s.subscription.id + return s.id } // Unsubscribe the current subscription from Tendermint Websocket. func (s Subscription) Unsubscribe(es *EventSystem) (err error) { - s.unsubOnce.Do(func() { - uninstallLoop: - for { - // write uninstall request and consume logs/hashes. This prevents - // the eventLoop broadcast method to deadlock when writing to the - // filter event channel while the subscription loop is waiting for - // this method to return (and thus not reading these events). - select { - case es.uninstall <- s.subscription: - break uninstallLoop - case <-s.subscription.logs: - // case <-s.subscription.hashes: - case <-s.subscription.headers: - } - } - - err = es.client.Unsubscribe( - es.ctx, string(s.ID()), - tmtypes.QueryForEvent(s.subscription.event()).String(), - ) - }) - - return + return es.client.Unsubscribe( + es.ctx, string(s.ID()), + tmtypes.QueryForEvent(s.event).String(), + ) } diff --git a/rpc/filters.go b/rpc/filters.go index 63ac47031..31ccf5194 100644 --- a/rpc/filters.go +++ b/rpc/filters.go @@ -14,21 +14,21 @@ import ( // Filter can be used to retrieve and filter logs. type Filter struct { - backend Backend + backend FiltersBackend criteria filters.FilterCriteria matcher *bloombits.Matcher } // NewBlockFilter creates a new filter which directly inspects the contents of // a block to figure out whether it is interesting or not. -func NewBlockFilter(backend Backend, criteria filters.FilterCriteria) *Filter { +func NewBlockFilter(backend FiltersBackend, criteria filters.FilterCriteria) *Filter { // Create a generic filter and convert it into a block filter return newFilter(backend, criteria, nil) } // NewRangeFilter creates a new filter which uses a bloom filter on blocks to // figure out whether a particular block is interesting or not. -func NewRangeFilter(backend Backend, begin, end int64, addresses []common.Address, topics [][]common.Hash) *Filter { +func NewRangeFilter(backend FiltersBackend, begin, end int64, addresses []common.Address, topics [][]common.Hash) *Filter { // Flatten the address and topic filter clauses into a single bloombits filter // system. Since the bloombits are not positional, nil topics are permitted, // which get flattened into a nil byte slice. @@ -63,7 +63,7 @@ func NewRangeFilter(backend Backend, begin, end int64, addresses []common.Addres } // newFilter returns a new Filter -func newFilter(backend Backend, criteria filters.FilterCriteria, matcher *bloombits.Matcher) *Filter { +func newFilter(backend FiltersBackend, criteria filters.FilterCriteria, matcher *bloombits.Matcher) *Filter { return &Filter{ backend: backend, criteria: criteria, @@ -244,3 +244,18 @@ func bloomFilter(bloom ethtypes.Bloom, addresses []common.Address, topics [][]co } return included } + +// func (f *Filter) pollForTransactions(hashCh chan []common.Hash, errCh chan error) { +// for { +// txs, err := f.backend.PendingTransactions() +// if err != nil { +// errCh <- err +// } + +// for _, tx := range txs { +// hashCh <- tx.Hash +// } + +// <-time.After(6 * time.Second) +// } +// } From 820e3f201d6c78b45a9ce9b67e7a0a62c0a71e3d Mon Sep 17 00:00:00 2001 From: Federico Kunze Date: Mon, 22 Jun 2020 13:47:58 +0200 Subject: [PATCH 15/36] upgrade to tendermint v0.33.4 --- .gitignore | 2 ++ go.mod | 10 +++++----- go.sum | 46 +++++++++++++++++++++++++++++++--------------- rpc/eth_api.go | 2 +- 4 files changed, 39 insertions(+), 21 deletions(-) diff --git a/.gitignore b/.gitignore index 735a84989..f3b29d76f 100644 --- a/.gitignore +++ b/.gitignore @@ -14,9 +14,11 @@ .glide/ vendor/ build/ +docs/node_modules # Goland .idea/ start.sh +.vscode bin/ diff --git a/go.mod b/go.mod index 366bc4629..42940c48e 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,6 @@ go 1.14 require ( github.com/allegro/bigcache v1.2.1 // indirect github.com/aristanetworks/goarista v0.0.0-20200331225509-2cc472e8fbd6 // indirect - github.com/btcsuite/btcd v0.20.1-beta // indirect github.com/cespare/cp v1.1.1 // indirect github.com/cosmos/cosmos-sdk v0.34.4-0.20200403200637-7f78e61b93a5 github.com/deckarep/golang-set v1.7.1 // indirect @@ -22,15 +21,16 @@ require ( github.com/regen-network/cosmos-proto v0.1.1-0.20200213154359-02baa11ea7c2 github.com/rjeczalik/notify v0.9.2 // indirect github.com/spf13/afero v1.2.2 // indirect - github.com/spf13/cobra v0.0.7 + github.com/spf13/cobra v1.0.0 github.com/spf13/viper v1.7.0 github.com/status-im/keycard-go v0.0.0-20190424133014-d95853db0f48 // indirect github.com/stretchr/testify v1.6.1 github.com/tendermint/go-amino v0.15.1 - github.com/tendermint/tendermint v0.33.3 + github.com/tendermint/tendermint v0.33.4 github.com/tendermint/tm-db v0.5.1 - golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4 + golang.org/x/crypto v0.0.0-20200406173513-056763e48d71 gopkg.in/yaml.v2 v2.3.0 ) -replace github.com/cosmos/cosmos-sdk => github.com/cosmos/cosmos-sdk v0.34.4-0.20200403200637-7f78e61b93a5 +// forked SDK to avoid breaking changes +replace github.com/cosmos/cosmos-sdk => github.com/Chainsafe/cosmos-sdk v0.34.4-0.20200622114457-35ea97f29c5f diff --git a/go.sum b/go.sum index b7e6b2c39..fddf754ab 100644 --- a/go.sum +++ b/go.sum @@ -31,6 +31,10 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/ChainSafe/go-schnorrkel v0.0.0-20200102211924-4bcbc698314f h1:4O1om+UVU+Hfcihr1timk8YNXHxzZWgCo7ofnrZRApw= github.com/ChainSafe/go-schnorrkel v0.0.0-20200102211924-4bcbc698314f/go.mod h1:URdX5+vg25ts3aCh8H5IFZybJYKWhJHYMTnf+ULtoC4= +github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d h1:nalkkPQcITbvhmL4+C4cKA87NW0tfm3Kl9VXRoPywFg= +github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d/go.mod h1:URdX5+vg25ts3aCh8H5IFZybJYKWhJHYMTnf+ULtoC4= +github.com/Chainsafe/cosmos-sdk v0.34.4-0.20200622114457-35ea97f29c5f h1:hLvatKcr7PZPWlwBb08oSxdfd7bN5JT0d3MKIwm3zEk= +github.com/Chainsafe/cosmos-sdk v0.34.4-0.20200622114457-35ea97f29c5f/go.mod h1:brXC4wuGawcC5pQebaWER22hzunmXFLgN8vajUh+xhE= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= @@ -41,6 +45,7 @@ github.com/VictoriaMetrics/fastcache v1.5.7 h1:4y6y0G8PRzszQUYIQHHssv/jgPHAb5qQu github.com/VictoriaMetrics/fastcache v1.5.7/go.mod h1:ptDBkNMQI4RtmVo8VS/XwRY6RoTu1dAWCbrk+6WsEM8= github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/Workiva/go-datastructures v1.0.52 h1:PLSK6pwn8mYdaoaCZEMsXBpBotr4HHn9abU0yMQt0NI= github.com/Workiva/go-datastructures v1.0.52/go.mod h1:Z+F2Rca0qCsVYDS8z7bAGm8f3UkzuWYS/oBZz5a7VVA= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= @@ -83,6 +88,8 @@ github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufo github.com/btcsuite/btcutil v0.0.0-20180706230648-ab6388e0c60a/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d h1:yJzD/yFppdVCf6ApMkVy8cUxV0XrxdP9rVf6D87/Mng= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/btcutil v1.0.2 h1:9iZ1Terx9fMIOtq1VrwdqfsATL9MC2l8ZrUY6YZ2uts= +github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= @@ -113,8 +120,6 @@ github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7 github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cosmos/cosmos-sdk v0.34.4-0.20200403200637-7f78e61b93a5 h1:Up28KmvitVSSms5m+JZUrfYjVF27LvXZVfTb+408HaM= -github.com/cosmos/cosmos-sdk v0.34.4-0.20200403200637-7f78e61b93a5/go.mod h1:J2RTB23kBgFKwtKd7J/gk4WwG363cA/xM0GU1Gfztw4= github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d h1:49RLWk1j44Xu4fjHb6JFYmeUnDORVwHNkDxaQ0ctCVU= github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y= github.com/cosmos/ledger-cosmos-go v0.11.1 h1:9JIYsGnXP613pb2vPjFeMMjBI5lEDsEaF6oYorTy6J4= @@ -224,8 +229,9 @@ github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaW github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4 h1:+EOh4OY6tjM6ZueeUKinl1f0U2820HzQOuf1iqMnsks= -github.com/golang/protobuf v1.4.0-rc.4/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0 h1:oOuy+ugB+P/kBdUnG5QaMXSIyJ1q38wWSojYCb3z5VQ= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -273,6 +279,8 @@ github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8 github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0= github.com/gtank/merlin v0.1.1-0.20191105220539-8318aed1a79f h1:8N8XWLZelZNibkhM1FuF+3Ad3YIbgirjdMiVA0eUkaM= github.com/gtank/merlin v0.1.1-0.20191105220539-8318aed1a79f/go.mod h1:T86dnYJhcGOh5BjZFCJWTDeTK7XW8uE+E21Cy/bIQ+s= +github.com/gtank/merlin v0.1.1 h1:eQ90iG7K9pOhtereWsmyRJ6RAwcP4tHTDBHXNg+u5is= +github.com/gtank/merlin v0.1.1/go.mod h1:T86dnYJhcGOh5BjZFCJWTDeTK7XW8uE+E21Cy/bIQ+s= github.com/gtank/ristretto255 v0.1.2 h1:JEqUCPA1NvLq5DwYtuzigd7ss8fwbYay9fi4/5uMzcc= github.com/gtank/ristretto255 v0.1.2/go.mod h1:Ph5OpO6c7xKUGROZfWVLiJf9icMDwUeIvY4OmlYW69o= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= @@ -468,6 +476,8 @@ github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeD github.com/prometheus/client_golang v1.4.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_golang v1.5.0 h1:Ctq0iGpCmr3jeP77kbF2UxgvRwzWWz+4Bh9/vJTyg1A= github.com/prometheus/client_golang v1.5.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= +github.com/prometheus/client_golang v1.5.1 h1:bdHYieyGlH+6OLEk2YQha8THib30KP0/yD0YH9m6xcA= +github.com/prometheus/client_golang v1.5.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -498,6 +508,8 @@ github.com/rakyll/statik v0.1.7/go.mod h1:AlZONWzMtEnMs7W4e/1LURLiI49pIMmp6V9Ung github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563 h1:dY6ETXrvDG7Sa4vE8ZQG4yqWg6UnOcbqTAahkV813vQ= github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 h1:MkV+77GLUNo5oJ0jf870itWm3D0Sjh7+Za9gazKc5LQ= +github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/regen-network/cosmos-proto v0.1.1-0.20200213154359-02baa11ea7c2 h1:jQK1YoH972Aptd22YKgtNu5jM2X2xMGkyIENOAc71to= github.com/regen-network/cosmos-proto v0.1.1-0.20200213154359-02baa11ea7c2/go.mod h1:+r7jN10xXCypD4yBgzKOa+vgLz0riqYMHeDcKekxPvA= github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRrjvIXnJho= @@ -533,8 +545,8 @@ github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.6/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= -github.com/spf13/cobra v0.0.7 h1:FfTH+vuMXOas8jmfb5/M7dzEYx7LpcLb7a0LPe34uOU= -github.com/spf13/cobra v0.0.7/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= +github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8= +github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= @@ -544,6 +556,7 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.6.2/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k= +github.com/spf13/viper v1.6.3/go.mod h1:jUMtyi0/lB5yZH/FjyGAoH7IMNrIhlBf6pXZmbMDvzw= github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/status-im/keycard-go v0.0.0-20190316090335-8537d3370df4/go.mod h1:RZLeN1LMWmRsyYjvAu+I6Dm9QmlDaIIt+Y+4Kd7Tp+Q= @@ -565,8 +578,6 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.0 h1:jlIyCplCJFULU/01vCkhKuTyc3OorI3bJFuw6obfgho= -github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= @@ -584,13 +595,12 @@ github.com/tendermint/crypto v0.0.0-20191022145703-50d29ede1e15/go.mod h1:z4YtwM github.com/tendermint/go-amino v0.14.1/go.mod h1:i/UKE5Uocn+argJJBb12qTZsCDBcAYMbR92AaJVmKso= github.com/tendermint/go-amino v0.15.1 h1:D2uk35eT4iTsvJd9jWIetzthE5C0/k2QmMFkCN+4JgQ= github.com/tendermint/go-amino v0.15.1/go.mod h1:TQU0M1i/ImAo+tYpZi73AU3V/dKeCoMC9Sphe2ZwGME= -github.com/tendermint/iavl v0.13.2 h1:O1m08/Ciy53l9IYmf75uIRVvrNsfjEbre8u/yCu/oqk= -github.com/tendermint/iavl v0.13.2/go.mod h1:vE1u0XAGXYjHykd4BLp8p/yivrw2PF1TuoljBcsQoGA= +github.com/tendermint/iavl v0.13.3 h1:expgBDY1MX+6/3sqrIxGChbTNf9N9aTJ67SH4bPchCs= +github.com/tendermint/iavl v0.13.3/go.mod h1:2lE7GiWdSvc7kvT78ncIKmkOjCnp6JEnSb2O7B9htLw= github.com/tendermint/tendermint v0.33.2/go.mod h1:25DqB7YvV1tN3tHsjWoc2vFtlwICfrub9XO6UBO+4xk= -github.com/tendermint/tendermint v0.33.3 h1:6lMqjEoCGejCzAghbvfQgmw87snGSqEhDTo/jw+W8CI= -github.com/tendermint/tendermint v0.33.3/go.mod h1:25DqB7YvV1tN3tHsjWoc2vFtlwICfrub9XO6UBO+4xk= +github.com/tendermint/tendermint v0.33.4 h1:NM3G9618yC5PaaxGrcAySc5ylc1PAANeIx42u2Re/jo= +github.com/tendermint/tendermint v0.33.4/go.mod h1:6NW9DVkvsvqmCY6wbRsOo66qGDhMXglRL70aXajvBEA= github.com/tendermint/tm-db v0.4.1/go.mod h1:JsJ6qzYkCGiGwm5GHl/H5GLI9XLb6qZX7PRe425dHAY= -github.com/tendermint/tm-db v0.5.0/go.mod h1:lSq7q5WRR/njf1LnhiZ/lIJHk2S8Y1Zyq5oP/3o9C2U= github.com/tendermint/tm-db v0.5.1 h1:H9HDq8UEA7Eeg13kdYckkgwwkQLBnJGgX4PgLJRhieY= github.com/tendermint/tm-db v0.5.1/go.mod h1:g92zWjHpCYlEvQXvy9M168Su8V1IBEeawpXVVBaK4f4= github.com/tjfoc/gmsm v1.3.0/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w= @@ -638,10 +648,13 @@ golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20191219195013-becbf705a915/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4 h1:QmwruyY+bKbDDL0BaglrbZABEali68eoMFhTZpCjYVA= golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200406173513-056763e48d71 h1:DOmugCavvUtnUD114C1Wh+UgTgQZ4pMLzXxi1pSt+/Y= +golang.org/x/crypto v0.0.0-20200406173513-056763e48d71/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -684,6 +697,7 @@ golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0 h1:Jcxah/M+oLZ/R4/z5RzfPzGbPXnVDPkEDtf2JnuxN+U= golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -798,12 +812,14 @@ google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8 google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.28.0 h1:bO/TA4OxCOummhSf10siHuG7vJOiwh7SpRpFZDkOgl4= google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.28.1 h1:C1QC6KzgSiLyBabDi87BbjaGreoRgGUF5nOyvfrAZ1k= +google.golang.org/grpc v1.28.1/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.20.1 h1:ESRXHgpUBG5D2I5mmsQIyYxB/tQIZfSZ8wLyFDf/N/U= -google.golang.org/protobuf v1.20.1/go.mod h1:KqelGeouBkcbcuB3HCk4/YH2tmNLk6YSWA5LIWeI/lY= +google.golang.org/protobuf v1.21.0 h1:qdOKuR/EIArgaWNjetjgTzgVTAZ+S/WXVrq9HW9zimw= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/bsm/ratelimit.v1 v1.0.0-20160220154919-db14e161995a/go.mod h1:KF9sEfUPAXdG8Oev9e99iLGnl2uJMjc5B+4y3O7x610= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/rpc/eth_api.go b/rpc/eth_api.go index d0411cae1..f488ecffc 100644 --- a/rpc/eth_api.go +++ b/rpc/eth_api.go @@ -266,7 +266,7 @@ func (e *PublicEthAPI) GetTransactionLogs(txHash common.Hash) ([]*ethtypes.Log, // ExportAccount exports an account's balance, code, and storage at the given block number // TODO: deprecate this once the export genesis command works -func (e *PublicEthAPI) ExportAccount(address common.Address, blockNumber BlockNumber) (string, error) { +func (e *PublicEthAPI) ExportAccount(address common.Address, blockNumber rpc.BlockNumber) (string, error) { ctx := e.cliCtx.WithHeight(blockNumber.Int64()) res, _, err := ctx.QueryWithData(fmt.Sprintf("custom/%s/%s/%s", types.ModuleName, evm.QueryExportAccount, address.Hex()), nil) From 638fe367062d82c9f53f8edd99f2d51cd9f6a505 Mon Sep 17 00:00:00 2001 From: Federico Kunze Date: Mon, 22 Jun 2020 15:10:07 +0200 Subject: [PATCH 16/36] update filters --- rpc/filter_api.go | 152 ++++++++++++++++++++++--------------------- rpc/filter_system.go | 11 ++-- 2 files changed, 84 insertions(+), 79 deletions(-) diff --git a/rpc/filter_api.go b/rpc/filter_api.go index 6dd4f3423..dbf61e3fc 100644 --- a/rpc/filter_api.go +++ b/rpc/filter_api.go @@ -39,7 +39,7 @@ var deadline = 5 * time.Minute // and associated subscription in the event system. type filter struct { typ filters.Type - deadline *time.Timer // filter is inactiv when deadline triggers + deadline *time.Timer // filter is inactive when deadline triggers hashes []common.Hash crit filters.FilterCriteria logs []*types.Log @@ -65,7 +65,13 @@ func NewPublicFilterAPI(cliCtx clientcontext.CLIContext, backend FiltersBackend) events: NewEventSystem(cliCtx.Client), } - // go api.timeoutLoop() + // start the client to subscribe to Tendermint events + err := api.cliCtx.Client.Start() + if err != nil { + panic(err) + } + + go api.timeoutLoop() return api } @@ -97,12 +103,12 @@ func (api *PublicFilterAPI) timeoutLoop() { // It is part of the filter package because this filter can be used through the // `eth_getFilterChanges` polling method that is also used for log filters. // -// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newpendingtransactionfilter +// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newPendingTransactionFilter func (api *PublicFilterAPI) NewPendingTransactionFilter() rpc.ID { pendingTxSub, cancelSubs, err := api.events.SubscribePendingTxs() if err != nil { - // return an empty id - return rpc.ID("") + // wrap error on the ID + return rpc.ID(fmt.Sprintf("error creating pending tx filter: %s", err.Error())) } defer cancelSubs() @@ -111,18 +117,21 @@ func (api *PublicFilterAPI) NewPendingTransactionFilter() rpc.ID { api.filters[pendingTxSub.ID()] = &filter{typ: filters.PendingTransactionsSubscription, deadline: time.NewTimer(deadline), hashes: make([]common.Hash, 0), s: pendingTxSub} api.filtersMu.Unlock() - // go func() { - // for { - // select { - // case ph := <-pendingTxs: - // api.filtersMu.Lock() - // if f, found := api.filters[pendingTxSub.ID()]; found { - // f.hashes = append(f.hashes, ph...) - // } - // api.filtersMu.Unlock() - // } - // } - // }() + go func() { + for { + select { + case ev := <-api.events.txsCh: + data, _ := ev.Data.(tmtypes.EventDataTx) + txHash := common.BytesToHash(data.Tx.Hash()) + + api.filtersMu.Lock() + if f, found := api.filters[pendingTxSub.ID()]; found { + f.hashes = append(f.hashes, txHash) + } + api.filtersMu.Unlock() + } + } + }() return pendingTxSub.ID() } @@ -141,37 +150,37 @@ func (api *PublicFilterAPI) NewPendingTransactions(ctx context.Context) (*rpc.Su api.events.WithContext(ctx) rpcSub := notifier.CreateSubscription() - _, cancelSubs, err := api.events.SubscribeNewHeads() + pendingTxSub, cancelSubs, err := api.events.SubscribePendingTxs() if err != nil { return nil, err } defer cancelSubs() - // go func() { - // for { - // select { - // case hashes := <-pendingTxSub.eventChannel: - - // // To keep the original behaviour, send a single tx hash in one notification. - // // TODO(rjl493456442) Send a batch of tx hashes in one notification - // for _, h := range hashes { - // err = notifier.Notify(rpcSub.ID, h) - // if err != nil { - // return - // } - // } - // case <-rpcSub.Err(): - // err = pendingTxSub.Unsubscribe(api.events) - // return - // case <-notifier.Closed(): - // err = pendingTxSub.Unsubscribe(api.events) - // return - // } - // } - // }() - - return rpcSub, nil + go func() { + for { + select { + case ev := <-api.events.txsCh: + data, _ := ev.Data.(tmtypes.EventDataTx) + txHash := common.BytesToHash(data.Tx.Hash()) + + // To keep the original behaviour, send a single tx hash in one notification. + // TODO(rjl493456442) Send a batch of tx hashes in one notification + err = notifier.Notify(rpcSub.ID, txHash) + if err != nil { + return + } + case <-rpcSub.Err(): + err = pendingTxSub.Unsubscribe(api.events) + return + case <-notifier.Closed(): + err = pendingTxSub.Unsubscribe(api.events) + return + } + } + }() + + return rpcSub, err } // NewBlockFilter creates a filter that fetches blocks that are imported into the chain. @@ -181,8 +190,8 @@ func (api *PublicFilterAPI) NewPendingTransactions(ctx context.Context) (*rpc.Su func (api *PublicFilterAPI) NewBlockFilter() rpc.ID { headerSub, cancelSubs, err := api.events.SubscribeNewHeads() if err != nil { - // return an empty id - return rpc.ID("1") + // wrap error on the ID + return rpc.ID(fmt.Sprintf("error creating block filter: %s", err.Error())) } defer cancelSubs() @@ -191,27 +200,24 @@ func (api *PublicFilterAPI) NewBlockFilter() rpc.ID { api.filters[headerSub.ID()] = &filter{typ: filters.BlocksSubscription, deadline: time.NewTimer(deadline), hashes: make([]common.Hash, 0), s: headerSub} api.filtersMu.Unlock() - return headerSub.ID() + go func() { + // nolint: gosimple + for { + select { + case ev := <-api.events.chainCh: + data, _ := ev.Data.(tmtypes.EventDataNewBlockHeader) + header := EthHeaderFromTendermint(data.Header) - // go func() { - // // nolint: gosimple - // for { - // select { - // case event := <-headerSub.eventChannel: - // evHeader, ok := event.Data.(tmtypes.EventDataNewBlockHeader) - // if !ok { - // return - // } - // api.filtersMu.Lock() - // if f, found := api.filters[headerSub.ID()]; found { - // f.hashes = append(f.hashes, common.BytesToHash(evHeader.Header.Hash())) - // } - // api.filtersMu.Unlock() - // } - // } - // }() - - // return headerSub.ID() + api.filtersMu.Lock() + if f, found := api.filters[headerSub.ID()]; found { + f.hashes = append(f.hashes, header.Hash()) + } + api.filtersMu.Unlock() + } + } + }() + + return headerSub.ID() } // NewHeads send a notification each time a new (header) block is appended to the chain. @@ -235,13 +241,15 @@ func (api *PublicFilterAPI) NewHeads(ctx context.Context) (*rpc.Subscription, er for { select { - case event := <-api.events.chainCh: - evHeader, ok := event.Data.(tmtypes.EventDataNewBlockHeader) + case ev := <-api.events.chainCh: + data, ok := ev.Data.(tmtypes.EventDataNewBlockHeader) if !ok { - err = fmt.Errorf("invalid event data %T, expected %s", event.Data, tmtypes.EventNewBlockHeader) + err = fmt.Errorf("invalid event data %T, expected %s", ev.Data, tmtypes.EventNewBlockHeader) return } - err = notifier.Notify(rpcSub.ID, evHeader.Header) + + header := EthHeaderFromTendermint(data.Header) + err = notifier.Notify(rpcSub.ID, header) if err != nil { return } @@ -376,11 +384,7 @@ func (api *PublicFilterAPI) NewFilter(criteria filters.FilterCriteria) (rpc.ID, } }() - if err != nil { - return rpc.ID(""), err - } - - return filterID, nil + return filterID, err } // GetLogs returns logs matching the given argument that are stored within the state. @@ -500,7 +504,7 @@ func (api *PublicFilterAPI) GetFilterChanges(id rpc.ID) (interface{}, error) { switch f.typ { case filters.PendingTransactionsSubscription, filters.BlocksSubscription: hashes := f.hashes - f.hashes = nil + // f.hashes = nil return returnHashes(hashes), nil case filters.LogsSubscription, filters.MinedAndPendingLogsSubscription: logs := f.logs diff --git a/rpc/filter_system.go b/rpc/filter_system.go index 34857816c..18f2f3f91 100644 --- a/rpc/filter_system.go +++ b/rpc/filter_system.go @@ -64,7 +64,8 @@ func NewEventSystem(client rpcclient.Client) *EventSystem { lightMode: false, } - go es.eventLoop() + // TODO: register on initialization + // go es.eventLoop() return es } @@ -248,21 +249,21 @@ func (es *EventSystem) eventLoop() { // Subscribe events es.txsSub, cancelPendingTxsSubs, err = es.SubscribePendingTxs() if err != nil { - panic(err) + panic(fmt.Errorf("failed to subscribe pending txs: %w", err)) } defer cancelPendingTxsSubs() es.logsSub, cancelLogsSubs, err = es.SubscribeLogs(filters.FilterCriteria{}) if err != nil { - panic(err) + panic(fmt.Errorf("failed to subscribe logs: %w", err)) } defer cancelLogsSubs() es.chainSub, cancelHeaderSubs, err = es.SubscribeNewHeads() if err != nil { - panic(err) + panic(fmt.Errorf("failed to subscribe headers: %w", err)) } defer cancelHeaderSubs() @@ -272,7 +273,7 @@ func (es *EventSystem) eventLoop() { _ = es.txsSub.Unsubscribe(es) _ = es.logsSub.Unsubscribe(es) // _ = es.rmLogsSub.Unsubscribe(es) - _ = es.pendingLogsSub.Unsubscribe(es) + // _ = es.pendingLogsSub.Unsubscribe(es) _ = es.chainSub.Unsubscribe(es) }() From 0efc30ad624c303eeffa9a02d9c217b11831d11f Mon Sep 17 00:00:00 2001 From: Federico Kunze Date: Thu, 25 Jun 2020 16:32:46 +0200 Subject: [PATCH 17/36] fix unsubscription --- rpc/filter_api.go | 35 +++++++++++----- rpc/filter_system.go | 97 +++++++++++++++++++++++--------------------- 2 files changed, 74 insertions(+), 58 deletions(-) diff --git a/rpc/filter_api.go b/rpc/filter_api.go index dbf61e3fc..ffa3024fb 100644 --- a/rpc/filter_api.go +++ b/rpc/filter_api.go @@ -3,6 +3,7 @@ package rpc import ( "context" "fmt" + "log" "sync" "time" @@ -58,6 +59,12 @@ type PublicFilterAPI struct { // NewPublicFilterAPI returns a new PublicFilterAPI instance. func NewPublicFilterAPI(cliCtx clientcontext.CLIContext, backend FiltersBackend) *PublicFilterAPI { + // start the client to subscribe to Tendermint events + err := cliCtx.Client.Start() + if err != nil { + panic(err) + } + api := &PublicFilterAPI{ cliCtx: cliCtx, backend: backend, @@ -65,12 +72,6 @@ func NewPublicFilterAPI(cliCtx clientcontext.CLIContext, backend FiltersBackend) events: NewEventSystem(cliCtx.Client), } - // start the client to subscribe to Tendermint events - err := api.cliCtx.Client.Start() - if err != nil { - panic(err) - } - go api.timeoutLoop() return api @@ -81,13 +82,18 @@ func NewPublicFilterAPI(cliCtx clientcontext.CLIContext, backend FiltersBackend) func (api *PublicFilterAPI) timeoutLoop() { ticker := time.NewTicker(deadline) defer ticker.Stop() + + var err error for { <-ticker.C api.filtersMu.Lock() for id, f := range api.filters { select { case <-f.deadline.C: - _ = f.s.Unsubscribe(api.events) + err = f.s.Unsubscribe(api.events) + if err != nil { + log.Println("error unsubscribing", err) + } delete(api.filters, id) default: continue @@ -194,6 +200,8 @@ func (api *PublicFilterAPI) NewBlockFilter() rpc.ID { return rpc.ID(fmt.Sprintf("error creating block filter: %s", err.Error())) } + log.Println("new block filter") + defer cancelSubs() api.filtersMu.Lock() @@ -205,6 +213,7 @@ func (api *PublicFilterAPI) NewBlockFilter() rpc.ID { for { select { case ev := <-api.events.chainCh: + log.Println("event", ev) data, _ := ev.Data.(tmtypes.EventDataNewBlockHeader) header := EthHeaderFromTendermint(data.Header) @@ -423,15 +432,21 @@ func (api *PublicFilterAPI) UninstallFilter(id rpc.ID) bool { api.filtersMu.Lock() f, found := api.filters[id] if found { + log.Println("deleting filter", id) delete(api.filters, id) } api.filtersMu.Unlock() + if !found { + log.Println("filter not found", id) return false } err := f.s.Unsubscribe(api.events) - return err != nil + if err != nil { + log.Println("error unsubscribing", err) + } + return err == nil } // GetFilterLogs returns the logs for the filter with the given id. @@ -474,8 +489,6 @@ func (api *PublicFilterAPI) GetFilterLogs(ctx context.Context, id rpc.ID) ([]*et return nil, err } return returnLogs(logs), nil - - // return api.filters[id].Logs(ctx) } // GetFilterChanges returns the logs for the filter with the given id since @@ -504,7 +517,7 @@ func (api *PublicFilterAPI) GetFilterChanges(id rpc.ID) (interface{}, error) { switch f.typ { case filters.PendingTransactionsSubscription, filters.BlocksSubscription: hashes := f.hashes - // f.hashes = nil + f.hashes = nil return returnHashes(hashes), nil case filters.LogsSubscription, filters.MinedAndPendingLogsSubscription: logs := f.logs diff --git a/rpc/filter_system.go b/rpc/filter_system.go index 18f2f3f91..1d58e7b76 100644 --- a/rpc/filter_system.go +++ b/rpc/filter_system.go @@ -3,6 +3,7 @@ package rpc import ( "context" "fmt" + "log" "time" rpcclient "github.com/tendermint/tendermint/rpc/client" @@ -20,9 +21,9 @@ import ( ) var ( - txEvents = fmt.Sprintf("%s='%s'", tmtypes.EventTypeKey, tmtypes.EventTx) - evmEvents = fmt.Sprintf("%s='%s' AND %s.%s='%s'", tmtypes.EventTypeKey, tmtypes.EventTx, sdk.EventTypeMessage, sdk.AttributeKeyModule, evmtypes.ModuleName) - headerEvents = fmt.Sprintf("%s='%s'", tmtypes.EventTypeKey, tmtypes.EventNewBlockHeader) + txEvents = fmt.Sprintf("%s = '%s'", tmtypes.EventTypeKey, tmtypes.EventTx) + evmEvents = fmt.Sprintf("%s = '%s' AND %s.%s = '%s'", tmtypes.EventTypeKey, tmtypes.EventTx, sdk.EventTypeMessage, sdk.AttributeKeyModule, evmtypes.ModuleName) + headerEvents = fmt.Sprintf("%s = '%s'", tmtypes.EventTypeKey, tmtypes.EventNewBlockHeader) ) // EventSystem creates subscriptions, processes events and broadcasts them to the @@ -64,8 +65,7 @@ func NewEventSystem(client rpcclient.Client) *EventSystem { lightMode: false, } - // TODO: register on initialization - // go es.eventLoop() + go es.eventLoop() return es } @@ -85,12 +85,16 @@ func (es *EventSystem) subscribe(sub *Subscription) (*Subscription, context.Canc switch sub.typ { case filters.PendingTransactionsSubscription: es.txsCh, err = es.client.Subscribe(es.ctx, string(sub.id), sub.event, 1000) + log.Println("subscribed to pending txs") case filters.PendingLogsSubscription, filters.MinedAndPendingLogsSubscription: es.pendingLogsCh, err = es.client.Subscribe(es.ctx, string(sub.id), sub.event, 1000) + log.Println("subscribed to pending logs") case filters.LogsSubscription: es.logsCh, err = es.client.Subscribe(es.ctx, string(sub.id), sub.event, 1000) + log.Println("subscribed to logs") case filters.BlocksSubscription: es.chainCh, err = es.client.Subscribe(es.ctx, string(sub.id), sub.event, 1000) + log.Println("subscribed to headers") default: err = fmt.Errorf("invalid filter subscription type %d", sub.typ) } @@ -142,8 +146,8 @@ func (es *EventSystem) subscribeMinedPendingLogs(crit filters.FilterCriteria) (* event: evmEvents, logsCrit: crit, created: time.Now(), - logs: make(chan []*ethtypes.Log), - installed: make(chan struct{}), + logs: make(chan []*ethtypes.Log, 1000), + installed: make(chan struct{}, 1), } return es.subscribe(sub) } @@ -157,8 +161,8 @@ func (es *EventSystem) subscribeLogs(crit filters.FilterCriteria) (*Subscription event: evmEvents, logsCrit: crit, created: time.Now(), - logs: make(chan []*ethtypes.Log), - installed: make(chan struct{}), + logs: make(chan []*ethtypes.Log, 1000), + installed: make(chan struct{}, 1), } return es.subscribe(sub) } @@ -172,8 +176,8 @@ func (es *EventSystem) subscribePendingLogs(crit filters.FilterCriteria) (*Subsc event: evmEvents, logsCrit: crit, created: time.Now(), - logs: make(chan []*ethtypes.Log), - installed: make(chan struct{}), + logs: make(chan []*ethtypes.Log, 1000), + installed: make(chan struct{}, 1), } return es.subscribe(sub) } @@ -185,8 +189,8 @@ func (es EventSystem) SubscribeNewHeads() (*Subscription, context.CancelFunc, er typ: filters.BlocksSubscription, event: tmtypes.EventNewBlockHeader, created: time.Now(), - headers: make(chan *ethtypes.Header), - installed: make(chan struct{}), + headers: make(chan *ethtypes.Header, 1000), + installed: make(chan struct{}, 1), } return es.subscribe(sub) } @@ -198,8 +202,8 @@ func (es EventSystem) SubscribePendingTxs() (*Subscription, context.CancelFunc, typ: filters.PendingTransactionsSubscription, event: txEvents, created: time.Now(), - hashes: make(chan []common.Hash), - installed: make(chan struct{}), + hashes: make(chan []common.Hash, 1000), + installed: make(chan struct{}, 1), } return es.subscribe(sub) } @@ -282,35 +286,37 @@ func (es *EventSystem) eventLoop() { index[i] = make(map[rpc.ID]*Subscription) } - for { - select { - case txEvent := <-es.txsCh: - es.handleTxsEvent(index, txEvent) - case headerEv := <-es.chainCh: - es.handleChainEvent(index, headerEv) - case logsEv := <-es.logsCh: - es.handleLogs(index, logsEv) - - case f := <-es.install: - if f.typ == filters.MinedAndPendingLogsSubscription { - // the type are logs and pending logs subscriptions - index[filters.LogsSubscription][f.id] = f - index[filters.PendingLogsSubscription][f.id] = f - } else { - index[f.typ][f.id] = f - } - close(f.installed) - - case f := <-es.uninstall: - if f.typ == filters.MinedAndPendingLogsSubscription { - // the type are logs and pending logs subscriptions - delete(index[filters.LogsSubscription], f.id) - delete(index[filters.PendingLogsSubscription], f.id) - } else { - delete(index[f.typ], f.id) + go func() { + for { + select { + case txEvent := <-es.txsCh: + es.handleTxsEvent(index, txEvent) + case headerEv := <-es.chainCh: + es.handleChainEvent(index, headerEv) + case logsEv := <-es.logsCh: + es.handleLogs(index, logsEv) + + case f := <-es.install: + if f.typ == filters.MinedAndPendingLogsSubscription { + // the type are logs and pending logs subscriptions + index[filters.LogsSubscription][f.id] = f + index[filters.PendingLogsSubscription][f.id] = f + } else { + index[f.typ][f.id] = f + } + close(f.installed) + + case f := <-es.uninstall: + if f.typ == filters.MinedAndPendingLogsSubscription { + // the type are logs and pending logs subscriptions + delete(index[filters.LogsSubscription], f.id) + delete(index[filters.PendingLogsSubscription], f.id) + } else { + delete(index[f.typ], f.id) + } } } - } + }() } // Subscription defines a wrapper for the private subscription @@ -332,9 +338,6 @@ func (s Subscription) ID() rpc.ID { } // Unsubscribe the current subscription from Tendermint Websocket. -func (s Subscription) Unsubscribe(es *EventSystem) (err error) { - return es.client.Unsubscribe( - es.ctx, string(s.ID()), - tmtypes.QueryForEvent(s.event).String(), - ) +func (s Subscription) Unsubscribe(es *EventSystem) error { + return es.client.Unsubscribe(es.ctx, string(s.ID()), s.event) } From 5efe8e1334694ce6a734ac2fec428c13999f064d Mon Sep 17 00:00:00 2001 From: Federico Kunze Date: Thu, 25 Jun 2020 17:39:37 +0200 Subject: [PATCH 18/36] updates wip --- rpc/filter_api.go | 15 ++++++-- rpc/filter_system.go | 92 +++++++++++++++++++++++++++++++++----------- 2 files changed, 80 insertions(+), 27 deletions(-) diff --git a/rpc/filter_api.go b/rpc/filter_api.go index ffa3024fb..ba15274d5 100644 --- a/rpc/filter_api.go +++ b/rpc/filter_api.go @@ -208,6 +208,8 @@ func (api *PublicFilterAPI) NewBlockFilter() rpc.ID { api.filters[headerSub.ID()] = &filter{typ: filters.BlocksSubscription, deadline: time.NewTimer(deadline), hashes: make([]common.Hash, 0), s: headerSub} api.filtersMu.Unlock() + log.Println("starting block header loop") + go func() { // nolint: gosimple for { @@ -429,11 +431,20 @@ func (api *PublicFilterAPI) GetLogs(ctx context.Context, crit filters.FilterCrit // // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_uninstallfilter func (api *PublicFilterAPI) UninstallFilter(id rpc.ID) bool { + var err error + api.filtersMu.Lock() f, found := api.filters[id] if found { log.Println("deleting filter", id) + + err = f.s.Unsubscribe(api.events) + if err != nil { + log.Println("error unsubscribing:", err) + } + delete(api.filters, id) + log.Println("uninstall complete", id) } api.filtersMu.Unlock() @@ -442,10 +453,6 @@ func (api *PublicFilterAPI) UninstallFilter(id rpc.ID) bool { return false } - err := f.s.Unsubscribe(api.events) - if err != nil { - log.Println("error unsubscribing", err) - } return err == nil } diff --git a/rpc/filter_system.go b/rpc/filter_system.go index 1d58e7b76..4019f4fff 100644 --- a/rpc/filter_system.go +++ b/rpc/filter_system.go @@ -35,6 +35,8 @@ type EventSystem struct { // light client mode lightMode bool + index filterIndex + // Subscriptions txsSub *Subscription // Subscription for new transaction event logsSub *Subscription // Subscription for new log event @@ -59,10 +61,16 @@ type EventSystem struct { // The returned manager has a loop that needs to be stopped with the Stop function // or by stopping the given mux. func NewEventSystem(client rpcclient.Client) *EventSystem { + index := make(filterIndex) + for i := filters.UnknownSubscription; i < filters.LastIndexSubscription; i++ { + index[i] = make(map[rpc.ID]*Subscription) + } + es := &EventSystem{ ctx: context.Background(), client: client, lightMode: false, + index: index, } go es.eventLoop() @@ -98,7 +106,24 @@ func (es *EventSystem) subscribe(sub *Subscription) (*Subscription, context.Canc default: err = fmt.Errorf("invalid filter subscription type %d", sub.typ) } - return sub, cancelFn, err + + if err != nil { + return nil, cancelFn, err + } + + go func() { + subscribeLoop: + for { + select { + case es.install <- sub: + break subscribeLoop + case <-sub.installed: + break subscribeLoop + } + } + }() + + return sub, cancelFn, nil } // SubscribeLogs creates a subscription that will write all logs matching the @@ -210,7 +235,7 @@ func (es EventSystem) SubscribePendingTxs() (*Subscription, context.CancelFunc, type filterIndex map[filters.Type]map[rpc.ID]*Subscription -func (es *EventSystem) handleLogs(filterIdx filterIndex, ev coretypes.ResultEvent) { +func (es *EventSystem) handleLogs(ev coretypes.ResultEvent) { data, _ := ev.Data.(tmtypes.EventDataTx) resultData, err := evmtypes.DecodeResultData(data.TxResult.Result.Data) if err != nil { @@ -220,7 +245,7 @@ func (es *EventSystem) handleLogs(filterIdx filterIndex, ev coretypes.ResultEven if len(resultData.Logs) == 0 { return } - for _, f := range filterIdx[filters.LogsSubscription] { + for _, f := range es.index[filters.LogsSubscription] { matchedLogs := filterLogs(resultData.Logs, f.logsCrit.FromBlock, f.logsCrit.ToBlock, f.logsCrit.Addresses, f.logsCrit.Topics) if len(matchedLogs) > 0 { f.logs <- matchedLogs @@ -228,16 +253,16 @@ func (es *EventSystem) handleLogs(filterIdx filterIndex, ev coretypes.ResultEven } } -func (es *EventSystem) handleTxsEvent(filterIdx filterIndex, ev coretypes.ResultEvent) { +func (es *EventSystem) handleTxsEvent(ev coretypes.ResultEvent) { data, _ := ev.Data.(tmtypes.EventDataTx) - for _, f := range filterIdx[filters.PendingTransactionsSubscription] { + for _, f := range es.index[filters.PendingTransactionsSubscription] { f.hashes <- []common.Hash{common.BytesToHash(data.Tx.Hash())} } } -func (es *EventSystem) handleChainEvent(filterIdx filterIndex, ev coretypes.ResultEvent) { +func (es *EventSystem) handleChainEvent(ev coretypes.ResultEvent) { data, _ := ev.Data.(tmtypes.EventDataNewBlockHeader) - for _, f := range filterIdx[filters.BlocksSubscription] { + for _, f := range es.index[filters.BlocksSubscription] { f.headers <- EthHeaderFromTendermint(data.Header) } // TODO: light client @@ -281,39 +306,39 @@ func (es *EventSystem) eventLoop() { _ = es.chainSub.Unsubscribe(es) }() - index := make(filterIndex) - for i := filters.UnknownSubscription; i < filters.LastIndexSubscription; i++ { - index[i] = make(map[rpc.ID]*Subscription) - } - go func() { for { select { case txEvent := <-es.txsCh: - es.handleTxsEvent(index, txEvent) + log.Println("received tx event", txEvent) + go es.handleTxsEvent(txEvent) case headerEv := <-es.chainCh: - es.handleChainEvent(index, headerEv) + log.Println("received header event", headerEv) + go es.handleChainEvent(headerEv) case logsEv := <-es.logsCh: - es.handleLogs(index, logsEv) + log.Println("received logs event", logsEv) + go es.handleLogs(logsEv) case f := <-es.install: if f.typ == filters.MinedAndPendingLogsSubscription { // the type are logs and pending logs subscriptions - index[filters.LogsSubscription][f.id] = f - index[filters.PendingLogsSubscription][f.id] = f + es.index[filters.LogsSubscription][f.id] = f + es.index[filters.PendingLogsSubscription][f.id] = f } else { - index[f.typ][f.id] = f + es.index[f.typ][f.id] = f } close(f.installed) + log.Println("filter installed", f.id) case f := <-es.uninstall: if f.typ == filters.MinedAndPendingLogsSubscription { // the type are logs and pending logs subscriptions - delete(index[filters.LogsSubscription], f.id) - delete(index[filters.PendingLogsSubscription], f.id) + delete(es.index[filters.LogsSubscription], f.id) + delete(es.index[filters.PendingLogsSubscription], f.id) } else { - delete(index[f.typ], f.id) + delete(es.index[f.typ], f.id) } + log.Println("filter installed", f.id) } } }() @@ -338,6 +363,27 @@ func (s Subscription) ID() rpc.ID { } // Unsubscribe the current subscription from Tendermint Websocket. -func (s Subscription) Unsubscribe(es *EventSystem) error { - return es.client.Unsubscribe(es.ctx, string(s.ID()), s.event) +func (s *Subscription) Unsubscribe(es *EventSystem) error { + if err := es.client.Unsubscribe(es.ctx, string(s.ID()), s.event); err != nil { + return err + } + + go func() { + uninstallLoop: + for { + // write uninstall request and consume logs/hashes. This prevents + // the eventLoop broadcast method to deadlock when writing to the + // filter event channel while the subscription loop is waiting for + // this method to return (and thus not reading these events). + select { + case es.uninstall <- s: + break uninstallLoop + case <-s.logs: + case <-s.hashes: + case <-s.headers: + } + } + }() + + return nil } From 8a443f98f62bb2034b11465bd9b0dfadebeef198 Mon Sep 17 00:00:00 2001 From: Federico Kunze Date: Tue, 30 Jun 2020 15:19:29 +0200 Subject: [PATCH 19/36] initialize channels --- rpc/filter_api.go | 5 +- rpc/filter_system.go | 118 +++++++++++++++++++++++++------------------ rpc/net_api.go | 2 +- 3 files changed, 72 insertions(+), 53 deletions(-) diff --git a/rpc/filter_api.go b/rpc/filter_api.go index ba15274d5..7ab846de9 100644 --- a/rpc/filter_api.go +++ b/rpc/filter_api.go @@ -95,8 +95,6 @@ func (api *PublicFilterAPI) timeoutLoop() { log.Println("error unsubscribing", err) } delete(api.filters, id) - default: - continue } } api.filtersMu.Unlock() @@ -150,11 +148,12 @@ func (api *PublicFilterAPI) NewPendingTransactions(ctx context.Context) (*rpc.Su return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported } + rpcSub := notifier.CreateSubscription() + ctx, cancelFn := context.WithTimeout(context.Background(), deadline) defer cancelFn() api.events.WithContext(ctx) - rpcSub := notifier.CreateSubscription() pendingTxSub, cancelSubs, err := api.events.SubscribePendingTxs() if err != nil { diff --git a/rpc/filter_system.go b/rpc/filter_system.go index 4019f4fff..bf0c4a2bc 100644 --- a/rpc/filter_system.go +++ b/rpc/filter_system.go @@ -67,10 +67,17 @@ func NewEventSystem(client rpcclient.Client) *EventSystem { } es := &EventSystem{ - ctx: context.Background(), - client: client, - lightMode: false, - index: index, + ctx: context.Background(), + client: client, + lightMode: false, + index: index, + install: make(chan *Subscription), + uninstall: make(chan *Subscription), + txsCh: make(chan coretypes.ResultEvent), + logsCh: make(chan coretypes.ResultEvent), + pendingLogsCh: make(chan coretypes.ResultEvent), + rmLogsCh: make(chan coretypes.ResultEvent), + chainCh: make(chan coretypes.ResultEvent), } go es.eventLoop() @@ -112,15 +119,19 @@ func (es *EventSystem) subscribe(sub *Subscription) (*Subscription, context.Canc } go func() { - subscribeLoop: - for { - select { - case es.install <- sub: - break subscribeLoop - case <-sub.installed: - break subscribeLoop - } - } + es.install <- sub + <-sub.installed + log.Println("installed") + // subscribeLoop: + // for { + // select { + // case es.install <- sub: + // log.Println("installed") + // break subscribeLoop + // case <-sub.installed: + // break subscribeLoop + // } + // } }() return sub, cancelFn, nil @@ -299,49 +310,58 @@ func (es *EventSystem) eventLoop() { // Ensure all subscriptions get cleaned up defer func() { - _ = es.txsSub.Unsubscribe(es) - _ = es.logsSub.Unsubscribe(es) + err = es.txsSub.Unsubscribe(es) + if err != nil { + log.Printf("failed to unsubscribe pending txs: %v", err) + } + err = es.logsSub.Unsubscribe(es) + if err != nil { + log.Printf("failed to unsubscribe logs: %v", err) + } // _ = es.rmLogsSub.Unsubscribe(es) // _ = es.pendingLogsSub.Unsubscribe(es) _ = es.chainSub.Unsubscribe(es) + if err != nil { + log.Printf("failed to unsubscribe headers: %v", err) + } }() - go func() { - for { - select { - case txEvent := <-es.txsCh: - log.Println("received tx event", txEvent) - go es.handleTxsEvent(txEvent) - case headerEv := <-es.chainCh: - log.Println("received header event", headerEv) - go es.handleChainEvent(headerEv) - case logsEv := <-es.logsCh: - log.Println("received logs event", logsEv) - go es.handleLogs(logsEv) - - case f := <-es.install: - if f.typ == filters.MinedAndPendingLogsSubscription { - // the type are logs and pending logs subscriptions - es.index[filters.LogsSubscription][f.id] = f - es.index[filters.PendingLogsSubscription][f.id] = f - } else { - es.index[f.typ][f.id] = f - } - close(f.installed) - log.Println("filter installed", f.id) - - case f := <-es.uninstall: - if f.typ == filters.MinedAndPendingLogsSubscription { - // the type are logs and pending logs subscriptions - delete(es.index[filters.LogsSubscription], f.id) - delete(es.index[filters.PendingLogsSubscription], f.id) - } else { - delete(es.index[f.typ], f.id) - } - log.Println("filter installed", f.id) + // go func() { + for { + select { + case txEvent := <-es.txsCh: + log.Println("received tx event", txEvent) + es.handleTxsEvent(txEvent) + case headerEv := <-es.chainCh: + log.Println("received header event", headerEv) + es.handleChainEvent(headerEv) + case logsEv := <-es.logsCh: + log.Println("received logs event", logsEv) + es.handleLogs(logsEv) + + case f := <-es.install: + if f.typ == filters.MinedAndPendingLogsSubscription { + // the type are logs and pending logs subscriptions + es.index[filters.LogsSubscription][f.id] = f + es.index[filters.PendingLogsSubscription][f.id] = f + } else { + es.index[f.typ][f.id] = f } + close(f.installed) + log.Println("filter installed", f.id) + + case f := <-es.uninstall: + if f.typ == filters.MinedAndPendingLogsSubscription { + // the type are logs and pending logs subscriptions + delete(es.index[filters.LogsSubscription], f.id) + delete(es.index[filters.PendingLogsSubscription], f.id) + } else { + delete(es.index[f.typ], f.id) + } + log.Println("filter uninstalled", f.id) } - }() + } + // }() } // Subscription defines a wrapper for the private subscription diff --git a/rpc/net_api.go b/rpc/net_api.go index c67e2a73b..c63d73006 100644 --- a/rpc/net_api.go +++ b/rpc/net_api.go @@ -15,7 +15,7 @@ type PublicNetAPI struct { } // NewPublicNetAPI creates an instance of the public Net Web3 API. -func NewPublicNetAPI(cliCtx context.CLIContext) *PublicNetAPI { +func NewPublicNetAPI(_ context.CLIContext) *PublicNetAPI { chainID := viper.GetString(flags.FlagChainID) // parse the chainID from a integer string intChainID, err := strconv.ParseUint(chainID, 0, 64) From 5cac4a76ecc55337b39706abc79ebd3fe903f164 Mon Sep 17 00:00:00 2001 From: Federico Kunze Date: Tue, 30 Jun 2020 17:08:34 +0200 Subject: [PATCH 20/36] cleanup go routines --- rpc/filter_system.go | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/rpc/filter_system.go b/rpc/filter_system.go index bf0c4a2bc..d05086911 100644 --- a/rpc/filter_system.go +++ b/rpc/filter_system.go @@ -118,20 +118,10 @@ func (es *EventSystem) subscribe(sub *Subscription) (*Subscription, context.Canc return nil, cancelFn, err } + // wrap events in a go routine to prevent blocking go func() { es.install <- sub <-sub.installed - log.Println("installed") - // subscribeLoop: - // for { - // select { - // case es.install <- sub: - // log.Println("installed") - // break subscribeLoop - // case <-sub.installed: - // break subscribeLoop - // } - // } }() return sub, cancelFn, nil @@ -257,6 +247,7 @@ func (es *EventSystem) handleLogs(ev coretypes.ResultEvent) { return } for _, f := range es.index[filters.LogsSubscription] { + log.Println(es.index[filters.LogsSubscription]) matchedLogs := filterLogs(resultData.Logs, f.logsCrit.FromBlock, f.logsCrit.ToBlock, f.logsCrit.Addresses, f.logsCrit.Topics) if len(matchedLogs) > 0 { f.logs <- matchedLogs @@ -266,6 +257,7 @@ func (es *EventSystem) handleLogs(ev coretypes.ResultEvent) { func (es *EventSystem) handleTxsEvent(ev coretypes.ResultEvent) { data, _ := ev.Data.(tmtypes.EventDataTx) + log.Println(es.index[filters.PendingTransactionsSubscription]) for _, f := range es.index[filters.PendingTransactionsSubscription] { f.hashes <- []common.Hash{common.BytesToHash(data.Tx.Hash())} } @@ -273,6 +265,7 @@ func (es *EventSystem) handleTxsEvent(ev coretypes.ResultEvent) { func (es *EventSystem) handleChainEvent(ev coretypes.ResultEvent) { data, _ := ev.Data.(tmtypes.EventDataNewBlockHeader) + log.Println(es.index[filters.BlocksSubscription]) for _, f := range es.index[filters.BlocksSubscription] { f.headers <- EthHeaderFromTendermint(data.Header) } @@ -327,15 +320,19 @@ func (es *EventSystem) eventLoop() { }() // go func() { + log.Println("start event loop") for { select { case txEvent := <-es.txsCh: + // FIXME: does't work log.Println("received tx event", txEvent) es.handleTxsEvent(txEvent) case headerEv := <-es.chainCh: + // FIXME: does't work log.Println("received header event", headerEv) es.handleChainEvent(headerEv) case logsEv := <-es.logsCh: + // FIXME: does't work log.Println("received logs event", logsEv) es.handleLogs(logsEv) @@ -389,6 +386,10 @@ func (s *Subscription) Unsubscribe(es *EventSystem) error { } go func() { + defer func() { + log.Println("successfully unsubscribed to event", s.event) + }() + uninstallLoop: for { // write uninstall request and consume logs/hashes. This prevents From fd959dd62471354617ef0b4cf3e17d1824f884f8 Mon Sep 17 00:00:00 2001 From: Federico Kunze Date: Tue, 30 Jun 2020 18:18:15 +0200 Subject: [PATCH 21/36] pass ResultEvent channel on subscription --- rpc/filter_api.go | 32 +++++++++------- rpc/filter_system.go | 90 ++++++++++++++++++++++---------------------- 2 files changed, 65 insertions(+), 57 deletions(-) diff --git a/rpc/filter_api.go b/rpc/filter_api.go index 7ab846de9..fa65f07c0 100644 --- a/rpc/filter_api.go +++ b/rpc/filter_api.go @@ -7,6 +7,7 @@ import ( "sync" "time" + coretypes "github.com/tendermint/tendermint/rpc/core/types" tmtypes "github.com/tendermint/tendermint/types" "github.com/ethereum/go-ethereum/common" @@ -109,7 +110,8 @@ func (api *PublicFilterAPI) timeoutLoop() { // // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newPendingTransactionFilter func (api *PublicFilterAPI) NewPendingTransactionFilter() rpc.ID { - pendingTxSub, cancelSubs, err := api.events.SubscribePendingTxs() + txsCh := make(<-chan coretypes.ResultEvent) + pendingTxSub, cancelSubs, err := api.events.SubscribePendingTxs(txsCh) if err != nil { // wrap error on the ID return rpc.ID(fmt.Sprintf("error creating pending tx filter: %s", err.Error())) @@ -124,7 +126,7 @@ func (api *PublicFilterAPI) NewPendingTransactionFilter() rpc.ID { go func() { for { select { - case ev := <-api.events.txsCh: + case ev := <-txsCh: data, _ := ev.Data.(tmtypes.EventDataTx) txHash := common.BytesToHash(data.Tx.Hash()) @@ -155,7 +157,8 @@ func (api *PublicFilterAPI) NewPendingTransactions(ctx context.Context) (*rpc.Su api.events.WithContext(ctx) - pendingTxSub, cancelSubs, err := api.events.SubscribePendingTxs() + txsCh := make(<-chan coretypes.ResultEvent) + pendingTxSub, cancelSubs, err := api.events.SubscribePendingTxs(txsCh) if err != nil { return nil, err } @@ -165,7 +168,7 @@ func (api *PublicFilterAPI) NewPendingTransactions(ctx context.Context) (*rpc.Su go func() { for { select { - case ev := <-api.events.txsCh: + case ev := <-txsCh: data, _ := ev.Data.(tmtypes.EventDataTx) txHash := common.BytesToHash(data.Tx.Hash()) @@ -193,7 +196,8 @@ func (api *PublicFilterAPI) NewPendingTransactions(ctx context.Context) (*rpc.Su // // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newblockfilter func (api *PublicFilterAPI) NewBlockFilter() rpc.ID { - headerSub, cancelSubs, err := api.events.SubscribeNewHeads() + headersCh := make(<-chan coretypes.ResultEvent) + headerSub, cancelSubs, err := api.events.SubscribeNewHeads(headersCh) if err != nil { // wrap error on the ID return rpc.ID(fmt.Sprintf("error creating block filter: %s", err.Error())) @@ -213,11 +217,10 @@ func (api *PublicFilterAPI) NewBlockFilter() rpc.ID { // nolint: gosimple for { select { - case ev := <-api.events.chainCh: - log.Println("event", ev) + case ev := <-headersCh: data, _ := ev.Data.(tmtypes.EventDataNewBlockHeader) header := EthHeaderFromTendermint(data.Header) - + log.Println("header", header) api.filtersMu.Lock() if f, found := api.filters[headerSub.ID()]; found { f.hashes = append(f.hashes, header.Hash()) @@ -242,7 +245,8 @@ func (api *PublicFilterAPI) NewHeads(ctx context.Context) (*rpc.Subscription, er var err error go func() { - headersSub, cancelSubs, err := api.events.SubscribeNewHeads() + headersCh := make(<-chan coretypes.ResultEvent) + headersSub, cancelSubs, err := api.events.SubscribeNewHeads(headersCh) if err != nil { return } @@ -288,7 +292,8 @@ func (api *PublicFilterAPI) Logs(ctx context.Context, crit filters.FilterCriteri var err error go func() { - logsSub, cancelSubs, err := api.events.SubscribeLogs(crit) + logsCh := make(<-chan coretypes.ResultEvent) + logsSub, cancelSubs, err := api.events.SubscribeLogs(crit, logsCh) if err != nil { return } @@ -297,7 +302,7 @@ func (api *PublicFilterAPI) Logs(ctx context.Context, crit filters.FilterCriteri for { select { - case event := <-api.events.logsCh: + case event := <-logsCh: // filter only events from EVM module txs _, isMsgEthermint := event.Events[evmtypes.TypeMsgEthermint] _, isMsgEthereumTx := event.Events[evmtypes.TypeMsgEthereumTx] @@ -360,7 +365,8 @@ func (api *PublicFilterAPI) NewFilter(criteria filters.FilterCriteria) (rpc.ID, ) go func() { - logsSub, cancelSubs, err := api.events.SubscribeLogs(criteria) + logsCh := make(<-chan coretypes.ResultEvent) + logsSub, cancelSubs, err := api.events.SubscribeLogs(criteria, logsCh) if err != nil { return } @@ -371,7 +377,7 @@ func (api *PublicFilterAPI) NewFilter(criteria filters.FilterCriteria) (rpc.ID, for { select { - case event := <-api.events.logsCh: + case event := <-logsCh: dataTx, ok := event.Data.(tmtypes.EventDataTx) if !ok { err = fmt.Errorf("invalid event data %T, expected EventDataTx", event.Data) diff --git a/rpc/filter_system.go b/rpc/filter_system.go index d05086911..e028a1833 100644 --- a/rpc/filter_system.go +++ b/rpc/filter_system.go @@ -6,6 +6,7 @@ import ( "log" "time" + tmquery "github.com/tendermint/tendermint/libs/pubsub/query" rpcclient "github.com/tendermint/tendermint/rpc/client" coretypes "github.com/tendermint/tendermint/rpc/core/types" tmtypes "github.com/tendermint/tendermint/types" @@ -21,9 +22,9 @@ import ( ) var ( - txEvents = fmt.Sprintf("%s = '%s'", tmtypes.EventTypeKey, tmtypes.EventTx) - evmEvents = fmt.Sprintf("%s = '%s' AND %s.%s = '%s'", tmtypes.EventTypeKey, tmtypes.EventTx, sdk.EventTypeMessage, sdk.AttributeKeyModule, evmtypes.ModuleName) - headerEvents = fmt.Sprintf("%s = '%s'", tmtypes.EventTypeKey, tmtypes.EventNewBlockHeader) + txEvents = tmtypes.QueryForEvent(tmtypes.EventTx).String() + evmEvents = tmquery.MustParse(fmt.Sprintf("%s='%s' AND %s.%s='%s'", tmtypes.EventTypeKey, tmtypes.EventTx, sdk.EventTypeMessage, sdk.AttributeKeyModule, evmtypes.ModuleName)).String() + headerEvents = tmtypes.QueryForEvent(tmtypes.EventNewBlockHeader).String() ) // EventSystem creates subscriptions, processes events and broadcasts them to the @@ -45,8 +46,10 @@ type EventSystem struct { chainSub *Subscription // Subscription for new chain event // Channels - install chan *Subscription // install filter for event notification - uninstall chan *Subscription // remove filter for event notification + install chan *Subscription // install filter for event notification + uninstall chan *Subscription // remove filter for event notification + + // Unidirectional channels to receive Tendermint ResultEvents txsCh <-chan coretypes.ResultEvent // Channel to receive new pending transactions event logsCh <-chan coretypes.ResultEvent // Channel to receive new log event pendingLogsCh <-chan coretypes.ResultEvent // Channel to receive new log event @@ -73,11 +76,11 @@ func NewEventSystem(client rpcclient.Client) *EventSystem { index: index, install: make(chan *Subscription), uninstall: make(chan *Subscription), - txsCh: make(chan coretypes.ResultEvent), - logsCh: make(chan coretypes.ResultEvent), - pendingLogsCh: make(chan coretypes.ResultEvent), - rmLogsCh: make(chan coretypes.ResultEvent), - chainCh: make(chan coretypes.ResultEvent), + txsCh: make(<-chan coretypes.ResultEvent), + logsCh: make(<-chan coretypes.ResultEvent), + pendingLogsCh: make(<-chan coretypes.ResultEvent), + rmLogsCh: make(<-chan coretypes.ResultEvent), + chainCh: make(<-chan coretypes.ResultEvent), } go es.eventLoop() @@ -89,7 +92,7 @@ func (es *EventSystem) WithContext(ctx context.Context) { es.ctx = ctx } -func (es *EventSystem) subscribe(sub *Subscription) (*Subscription, context.CancelFunc, error) { +func (es *EventSystem) subscribe(sub *Subscription, eventCh <-chan coretypes.ResultEvent) (*Subscription, context.CancelFunc, error) { var ( err error cancelFn context.CancelFunc @@ -99,16 +102,16 @@ func (es *EventSystem) subscribe(sub *Subscription) (*Subscription, context.Canc switch sub.typ { case filters.PendingTransactionsSubscription: - es.txsCh, err = es.client.Subscribe(es.ctx, string(sub.id), sub.event, 1000) + eventCh, err = es.client.Subscribe(es.ctx, string(sub.id), sub.event) log.Println("subscribed to pending txs") case filters.PendingLogsSubscription, filters.MinedAndPendingLogsSubscription: - es.pendingLogsCh, err = es.client.Subscribe(es.ctx, string(sub.id), sub.event, 1000) + eventCh, err = es.client.Subscribe(es.ctx, string(sub.id), sub.event) log.Println("subscribed to pending logs") case filters.LogsSubscription: - es.logsCh, err = es.client.Subscribe(es.ctx, string(sub.id), sub.event, 1000) + eventCh, err = es.client.Subscribe(es.ctx, string(sub.id), sub.event) log.Println("subscribed to logs") case filters.BlocksSubscription: - es.chainCh, err = es.client.Subscribe(es.ctx, string(sub.id), sub.event, 1000) + eventCh, err = es.client.Subscribe(es.ctx, string(sub.id), sub.event) log.Println("subscribed to headers") default: err = fmt.Errorf("invalid filter subscription type %d", sub.typ) @@ -130,7 +133,7 @@ func (es *EventSystem) subscribe(sub *Subscription) (*Subscription, context.Canc // SubscribeLogs creates a subscription that will write all logs matching the // given criteria to the given logs channel. Default value for the from and to // block is "latest". If the fromBlock > toBlock an error is returned. -func (es *EventSystem) SubscribeLogs(crit filters.FilterCriteria) (*Subscription, context.CancelFunc, error) { +func (es *EventSystem) SubscribeLogs(crit filters.FilterCriteria, eventCh <-chan coretypes.ResultEvent) (*Subscription, context.CancelFunc, error) { var from, to rpc.BlockNumber if crit.FromBlock == nil { from = rpc.LatestBlockNumber @@ -146,17 +149,17 @@ func (es *EventSystem) SubscribeLogs(crit filters.FilterCriteria) (*Subscription switch { // only interested in pending logs case from == rpc.PendingBlockNumber && to == rpc.PendingBlockNumber: - return es.subscribePendingLogs(crit) + return es.subscribePendingLogs(crit, eventCh) // only interested in new mined logs, mined logs within a specific block range, or // logs from a specific block number to new mined blocks case (from == rpc.LatestBlockNumber && to == rpc.LatestBlockNumber), (from >= 0 && to >= 0 && to >= from): - return es.subscribeLogs(crit) + return es.subscribeLogs(crit, eventCh) // interested in mined logs from a specific block number, new logs and pending logs case from >= rpc.LatestBlockNumber && to == rpc.PendingBlockNumber: - return es.subscribeMinedPendingLogs(crit) + return es.subscribeMinedPendingLogs(crit, eventCh) default: return nil, nil, fmt.Errorf("invalid from and to block combination: from > to (%d > %d)", from, to) @@ -165,73 +168,73 @@ func (es *EventSystem) SubscribeLogs(crit filters.FilterCriteria) (*Subscription // subscribeMinedPendingLogs creates a subscription that returned mined and // pending logs that match the given criteria. -func (es *EventSystem) subscribeMinedPendingLogs(crit filters.FilterCriteria) (*Subscription, context.CancelFunc, error) { +func (es *EventSystem) subscribeMinedPendingLogs(crit filters.FilterCriteria, eventCh <-chan coretypes.ResultEvent) (*Subscription, context.CancelFunc, error) { sub := &Subscription{ id: rpc.NewID(), typ: filters.MinedAndPendingLogsSubscription, event: evmEvents, logsCrit: crit, - created: time.Now(), - logs: make(chan []*ethtypes.Log, 1000), + created: time.Now().UTC(), + logs: make(chan []*ethtypes.Log), installed: make(chan struct{}, 1), } - return es.subscribe(sub) + return es.subscribe(sub, eventCh) } // subscribeLogs creates a subscription that will write all logs matching the // given criteria to the given logs channel. -func (es *EventSystem) subscribeLogs(crit filters.FilterCriteria) (*Subscription, context.CancelFunc, error) { +func (es *EventSystem) subscribeLogs(crit filters.FilterCriteria, eventCh <-chan coretypes.ResultEvent) (*Subscription, context.CancelFunc, error) { sub := &Subscription{ id: rpc.NewID(), typ: filters.LogsSubscription, event: evmEvents, logsCrit: crit, - created: time.Now(), - logs: make(chan []*ethtypes.Log, 1000), + created: time.Now().UTC(), + logs: make(chan []*ethtypes.Log), installed: make(chan struct{}, 1), } - return es.subscribe(sub) + return es.subscribe(sub, eventCh) } // subscribePendingLogs creates a subscription that writes transaction hashes for // transactions that enter the transaction pool. -func (es *EventSystem) subscribePendingLogs(crit filters.FilterCriteria) (*Subscription, context.CancelFunc, error) { +func (es *EventSystem) subscribePendingLogs(crit filters.FilterCriteria, eventCh <-chan coretypes.ResultEvent) (*Subscription, context.CancelFunc, error) { sub := &Subscription{ id: rpc.NewID(), typ: filters.PendingLogsSubscription, event: evmEvents, logsCrit: crit, - created: time.Now(), - logs: make(chan []*ethtypes.Log, 1000), + created: time.Now().UTC(), + logs: make(chan []*ethtypes.Log), installed: make(chan struct{}, 1), } - return es.subscribe(sub) + return es.subscribe(sub, eventCh) } // SubscribeNewHeads subscribes to new block headers events. -func (es EventSystem) SubscribeNewHeads() (*Subscription, context.CancelFunc, error) { +func (es EventSystem) SubscribeNewHeads(eventCh <-chan coretypes.ResultEvent) (*Subscription, context.CancelFunc, error) { sub := &Subscription{ id: rpc.NewID(), typ: filters.BlocksSubscription, - event: tmtypes.EventNewBlockHeader, - created: time.Now(), - headers: make(chan *ethtypes.Header, 1000), + event: headerEvents, + created: time.Now().UTC(), + headers: make(chan *ethtypes.Header), installed: make(chan struct{}, 1), } - return es.subscribe(sub) + return es.subscribe(sub, eventCh) } // SubscribePendingTxs subscribes to new pending transactions events from the mempool. -func (es EventSystem) SubscribePendingTxs() (*Subscription, context.CancelFunc, error) { +func (es EventSystem) SubscribePendingTxs(eventCh <-chan coretypes.ResultEvent) (*Subscription, context.CancelFunc, error) { sub := &Subscription{ id: rpc.NewID(), typ: filters.PendingTransactionsSubscription, event: txEvents, - created: time.Now(), - hashes: make(chan []common.Hash, 1000), + created: time.Now().UTC(), + hashes: make(chan []common.Hash), installed: make(chan struct{}, 1), } - return es.subscribe(sub) + return es.subscribe(sub, eventCh) } type filterIndex map[filters.Type]map[rpc.ID]*Subscription @@ -280,21 +283,21 @@ func (es *EventSystem) eventLoop() { ) // Subscribe events - es.txsSub, cancelPendingTxsSubs, err = es.SubscribePendingTxs() + es.txsSub, cancelPendingTxsSubs, err = es.SubscribePendingTxs(es.txsCh) if err != nil { panic(fmt.Errorf("failed to subscribe pending txs: %w", err)) } defer cancelPendingTxsSubs() - es.logsSub, cancelLogsSubs, err = es.SubscribeLogs(filters.FilterCriteria{}) + es.logsSub, cancelLogsSubs, err = es.SubscribeLogs(filters.FilterCriteria{}, es.logsCh) if err != nil { panic(fmt.Errorf("failed to subscribe logs: %w", err)) } defer cancelLogsSubs() - es.chainSub, cancelHeaderSubs, err = es.SubscribeNewHeads() + es.chainSub, cancelHeaderSubs, err = es.SubscribeNewHeads(es.chainCh) if err != nil { panic(fmt.Errorf("failed to subscribe headers: %w", err)) } @@ -319,7 +322,6 @@ func (es *EventSystem) eventLoop() { } }() - // go func() { log.Println("start event loop") for { select { From 5df76d0620212e87f6b481037bed54b4810586a7 Mon Sep 17 00:00:00 2001 From: Federico Kunze Date: Tue, 30 Jun 2020 20:02:00 +0200 Subject: [PATCH 22/36] error channel --- rpc/filter_api.go | 47 ++++++++++++++++++++++------------------- rpc/filter_system.go | 50 ++++++++++++++++++++++++++++---------------- 2 files changed, 58 insertions(+), 39 deletions(-) diff --git a/rpc/filter_api.go b/rpc/filter_api.go index fa65f07c0..cdfe1c61f 100644 --- a/rpc/filter_api.go +++ b/rpc/filter_api.go @@ -84,18 +84,16 @@ func (api *PublicFilterAPI) timeoutLoop() { ticker := time.NewTicker(deadline) defer ticker.Stop() - var err error for { <-ticker.C api.filtersMu.Lock() for id, f := range api.filters { select { case <-f.deadline.C: - err = f.s.Unsubscribe(api.events) - if err != nil { - log.Println("error unsubscribing", err) - } + f.s.Unsubscribe(api.events) delete(api.filters, id) + default: + continue } } api.filtersMu.Unlock() @@ -135,6 +133,11 @@ func (api *PublicFilterAPI) NewPendingTransactionFilter() rpc.ID { f.hashes = append(f.hashes, txHash) } api.filtersMu.Unlock() + case <-pendingTxSub.Err(): + api.filtersMu.Lock() + delete(api.filters, pendingTxSub.ID()) + api.filtersMu.Unlock() + return } } }() @@ -179,10 +182,10 @@ func (api *PublicFilterAPI) NewPendingTransactions(ctx context.Context) (*rpc.Su return } case <-rpcSub.Err(): - err = pendingTxSub.Unsubscribe(api.events) + pendingTxSub.Unsubscribe(api.events) return case <-notifier.Closed(): - err = pendingTxSub.Unsubscribe(api.events) + pendingTxSub.Unsubscribe(api.events) return } } @@ -226,6 +229,11 @@ func (api *PublicFilterAPI) NewBlockFilter() rpc.ID { f.hashes = append(f.hashes, header.Hash()) } api.filtersMu.Unlock() + case <-headerSub.Err(): + api.filtersMu.Lock() + delete(api.filters, headerSub.ID()) + api.filtersMu.Unlock() + return } } }() @@ -268,10 +276,10 @@ func (api *PublicFilterAPI) NewHeads(ctx context.Context) (*rpc.Subscription, er return } case <-rpcSub.Err(): - err = headersSub.Unsubscribe(api.events) + headersSub.Unsubscribe(api.events) return case <-notifier.Closed(): - err = headersSub.Unsubscribe(api.events) + headersSub.Unsubscribe(api.events) return } } @@ -333,10 +341,10 @@ func (api *PublicFilterAPI) Logs(ctx context.Context, crit filters.FilterCriteri } } case <-rpcSub.Err(): // client send an unsubscribe request - err = logsSub.Unsubscribe(api.events) + logsSub.Unsubscribe(api.events) return case <-notifier.Closed(): // connection dropped - err = logsSub.Unsubscribe(api.events) + logsSub.Unsubscribe(api.events) return } } @@ -396,6 +404,11 @@ func (api *PublicFilterAPI) NewFilter(criteria filters.FilterCriteria) (rpc.ID, f.logs = append(f.logs, logs...) } api.filtersMu.Unlock() + case <-logsSub.Err(): + api.filtersMu.Lock() + delete(api.filters, logsSub.ID()) + api.filtersMu.Unlock() + return } } }() @@ -436,18 +449,10 @@ func (api *PublicFilterAPI) GetLogs(ctx context.Context, crit filters.FilterCrit // // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_uninstallfilter func (api *PublicFilterAPI) UninstallFilter(id rpc.ID) bool { - var err error - api.filtersMu.Lock() f, found := api.filters[id] if found { log.Println("deleting filter", id) - - err = f.s.Unsubscribe(api.events) - if err != nil { - log.Println("error unsubscribing:", err) - } - delete(api.filters, id) log.Println("uninstall complete", id) } @@ -457,8 +462,8 @@ func (api *PublicFilterAPI) UninstallFilter(id rpc.ID) bool { log.Println("filter not found", id) return false } - - return err == nil + f.s.Unsubscribe(api.events) + return true } // GetFilterLogs returns the logs for the filter with the given id. diff --git a/rpc/filter_system.go b/rpc/filter_system.go index e028a1833..8c4e84dfd 100644 --- a/rpc/filter_system.go +++ b/rpc/filter_system.go @@ -118,6 +118,7 @@ func (es *EventSystem) subscribe(sub *Subscription, eventCh <-chan coretypes.Res } if err != nil { + sub.err <- err return nil, cancelFn, err } @@ -177,6 +178,7 @@ func (es *EventSystem) subscribeMinedPendingLogs(crit filters.FilterCriteria, ev created: time.Now().UTC(), logs: make(chan []*ethtypes.Log), installed: make(chan struct{}, 1), + err: make(chan error, 1), } return es.subscribe(sub, eventCh) } @@ -192,6 +194,7 @@ func (es *EventSystem) subscribeLogs(crit filters.FilterCriteria, eventCh <-chan created: time.Now().UTC(), logs: make(chan []*ethtypes.Log), installed: make(chan struct{}, 1), + err: make(chan error, 1), } return es.subscribe(sub, eventCh) } @@ -207,6 +210,7 @@ func (es *EventSystem) subscribePendingLogs(crit filters.FilterCriteria, eventCh created: time.Now().UTC(), logs: make(chan []*ethtypes.Log), installed: make(chan struct{}, 1), + err: make(chan error, 1), } return es.subscribe(sub, eventCh) } @@ -220,6 +224,7 @@ func (es EventSystem) SubscribeNewHeads(eventCh <-chan coretypes.ResultEvent) (* created: time.Now().UTC(), headers: make(chan *ethtypes.Header), installed: make(chan struct{}, 1), + err: make(chan error, 1), } return es.subscribe(sub, eventCh) } @@ -233,6 +238,7 @@ func (es EventSystem) SubscribePendingTxs(eventCh <-chan coretypes.ResultEvent) created: time.Now().UTC(), hashes: make(chan []common.Hash), installed: make(chan struct{}, 1), + err: make(chan error, 1), } return es.subscribe(sub, eventCh) } @@ -306,20 +312,11 @@ func (es *EventSystem) eventLoop() { // Ensure all subscriptions get cleaned up defer func() { - err = es.txsSub.Unsubscribe(es) - if err != nil { - log.Printf("failed to unsubscribe pending txs: %v", err) - } - err = es.logsSub.Unsubscribe(es) - if err != nil { - log.Printf("failed to unsubscribe logs: %v", err) - } - // _ = es.rmLogsSub.Unsubscribe(es) - // _ = es.pendingLogsSub.Unsubscribe(es) - _ = es.chainSub.Unsubscribe(es) - if err != nil { - log.Printf("failed to unsubscribe headers: %v", err) - } + es.txsSub.Unsubscribe(es) + es.logsSub.Unsubscribe(es) + // es.rmLogsSub.Unsubscribe(es) + // es.pendingLogsSub.Unsubscribe(es) + es.chainSub.Unsubscribe(es) }() log.Println("start event loop") @@ -357,7 +354,19 @@ func (es *EventSystem) eventLoop() { } else { delete(es.index[f.typ], f.id) } + close(f.err) log.Println("filter uninstalled", f.id) + // System stopped + case <-es.txsSub.Err(): + return + case <-es.logsSub.Err(): + return + // case <-es.rmLogsSub.Err(): + // return + // case <-es.pendingLogsSub.Err(): + // return + case <-es.chainSub.Err(): + return } } // }() @@ -374,6 +383,7 @@ type Subscription struct { hashes chan []common.Hash headers chan *ethtypes.Header installed chan struct{} // closed when the filter is installed + err chan error } // ID returns the underlying subscription RPC identifier. @@ -381,10 +391,11 @@ func (s Subscription) ID() rpc.ID { return s.id } -// Unsubscribe the current subscription from Tendermint Websocket. -func (s *Subscription) Unsubscribe(es *EventSystem) error { +// Unsubscribe to the current subscription from Tendermint Websocket. It sends an error to the +// subscription error channel if unsubscription fails. +func (s *Subscription) Unsubscribe(es *EventSystem) { if err := es.client.Unsubscribe(es.ctx, string(s.ID()), s.event); err != nil { - return err + s.err <- err } go func() { @@ -407,6 +418,9 @@ func (s *Subscription) Unsubscribe(es *EventSystem) error { } } }() +} - return nil +// Err returns the error channel +func (s *Subscription) Err() <-chan error { + return s.err } From dea0702bcf4fa0b3941cc1cf3cbccf94d0533c7c Mon Sep 17 00:00:00 2001 From: noot Date: Thu, 2 Jul 2020 11:55:24 -0400 Subject: [PATCH 23/36] add block filter changes test --- tests/rpc_test.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/rpc_test.go b/tests/rpc_test.go index 102c35f68..67fbf791e 100644 --- a/tests/rpc_test.go +++ b/tests/rpc_test.go @@ -276,6 +276,23 @@ func TestEth_NewBlockFilter(t *testing.T) { require.NoError(t, err) } + +func TestEth_GetFilterChanges_BlockFilter(t *testing.T) { + rpcRes := call(t, "eth_newBlockFilter", []string{}) + + var ID hexutil.Bytes + err := json.Unmarshal(rpcRes.Result, &ID) + require.NoError(t, err) + + time.Sleep(3 *time.Second) + + changesRes := call(t, "eth_getFilterChanges", []string{ID.String()}) + var hashes []ethcmn.Hash + err = json.Unmarshal(changesRes.Result, &hashes) + require.NoError(t, err) + t.Log(hashes) +} + func TestEth_GetFilterChanges_NoLogs(t *testing.T) { param := make([]map[string][]string, 1) param[0] = make(map[string][]string) From 3c0fa3895c6cf3451fff95fc663f2dfe50da693f Mon Sep 17 00:00:00 2001 From: noot Date: Thu, 2 Jul 2020 12:08:37 -0400 Subject: [PATCH 24/36] add eventCh loop --- rpc/filter_api.go | 7 ++++--- rpc/filter_system.go | 6 ++++++ tests/rpc_test.go | 5 +++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/rpc/filter_api.go b/rpc/filter_api.go index cdfe1c61f..1058877be 100644 --- a/rpc/filter_api.go +++ b/rpc/filter_api.go @@ -211,10 +211,10 @@ func (api *PublicFilterAPI) NewBlockFilter() rpc.ID { defer cancelSubs() api.filtersMu.Lock() - api.filters[headerSub.ID()] = &filter{typ: filters.BlocksSubscription, deadline: time.NewTimer(deadline), hashes: make([]common.Hash, 0), s: headerSub} + api.filters[headerSub.ID()] = &filter{typ: filters.BlocksSubscription, deadline: time.NewTimer(deadline), hashes: []common.Hash{}, s: headerSub} api.filtersMu.Unlock() - log.Println("starting block header loop") + log.Println("starting block header loop, id =", headerSub.ID()) go func() { // nolint: gosimple @@ -229,10 +229,11 @@ func (api *PublicFilterAPI) NewBlockFilter() rpc.ID { f.hashes = append(f.hashes, header.Hash()) } api.filtersMu.Unlock() - case <-headerSub.Err(): + case err := <-headerSub.Err(): api.filtersMu.Lock() delete(api.filters, headerSub.ID()) api.filtersMu.Unlock() + log.Println("block filter loop err", err) return } } diff --git a/rpc/filter_system.go b/rpc/filter_system.go index 8c4e84dfd..f69e24f18 100644 --- a/rpc/filter_system.go +++ b/rpc/filter_system.go @@ -128,6 +128,12 @@ func (es *EventSystem) subscribe(sub *Subscription, eventCh <-chan coretypes.Res <-sub.installed }() + go func(ch <-chan coretypes.ResultEvent) { + for e := range ch { + log.Println("got event!!!", e) + } + }(eventCh) + return sub, cancelFn, nil } diff --git a/tests/rpc_test.go b/tests/rpc_test.go index 67fbf791e..6df43b936 100644 --- a/tests/rpc_test.go +++ b/tests/rpc_test.go @@ -276,7 +276,6 @@ func TestEth_NewBlockFilter(t *testing.T) { require.NoError(t, err) } - func TestEth_GetFilterChanges_BlockFilter(t *testing.T) { rpcRes := call(t, "eth_newBlockFilter", []string{}) @@ -284,7 +283,9 @@ func TestEth_GetFilterChanges_BlockFilter(t *testing.T) { err := json.Unmarshal(rpcRes.Result, &ID) require.NoError(t, err) - time.Sleep(3 *time.Second) + time.Sleep(3 * time.Second) + + t.Log(ID.String()) changesRes := call(t, "eth_getFilterChanges", []string{ID.String()}) var hashes []ethcmn.Hash From 35c8f2ff6ff4702ebb24195e8c786d06804a4019 Mon Sep 17 00:00:00 2001 From: noot Date: Thu, 2 Jul 2020 12:24:03 -0400 Subject: [PATCH 25/36] pass funcs in select go func, block filter working --- rpc/filter_api.go | 25 ++++++++++++++++++++----- rpc/filter_system.go | 36 ++++++++++++++++++++++-------------- tests/rpc_test.go | 2 +- 3 files changed, 43 insertions(+), 20 deletions(-) diff --git a/rpc/filter_api.go b/rpc/filter_api.go index 1058877be..258091dfa 100644 --- a/rpc/filter_api.go +++ b/rpc/filter_api.go @@ -200,7 +200,7 @@ func (api *PublicFilterAPI) NewPendingTransactions(ctx context.Context) (*rpc.Su // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newblockfilter func (api *PublicFilterAPI) NewBlockFilter() rpc.ID { headersCh := make(<-chan coretypes.ResultEvent) - headerSub, cancelSubs, err := api.events.SubscribeNewHeads(headersCh) + headerSub, headersCh, cancelSubs, err := api.events.SubscribeNewHeads(headersCh) if err != nil { // wrap error on the ID return rpc.ID(fmt.Sprintf("error creating block filter: %s", err.Error())) @@ -216,7 +216,22 @@ func (api *PublicFilterAPI) NewBlockFilter() rpc.ID { log.Println("starting block header loop, id =", headerSub.ID()) - go func() { + // go func(ch <-chan coretypes.ResultEvent) { + // for ev := range ch { + // log.Println("got event!!!", ev) + + // data, _ := ev.Data.(tmtypes.EventDataNewBlockHeader) + // header := EthHeaderFromTendermint(data.Header) + // log.Println("header", header) + // api.filtersMu.Lock() + // if f, found := api.filters[headerSub.ID()]; found { + // f.hashes = append(f.hashes, header.Hash()) + // } + // api.filtersMu.Unlock() + // } + // }(headersCh) + + go func(headersCh <-chan coretypes.ResultEvent, errCh <-chan error) { // nolint: gosimple for { select { @@ -229,7 +244,7 @@ func (api *PublicFilterAPI) NewBlockFilter() rpc.ID { f.hashes = append(f.hashes, header.Hash()) } api.filtersMu.Unlock() - case err := <-headerSub.Err(): + case err := <-errCh: api.filtersMu.Lock() delete(api.filters, headerSub.ID()) api.filtersMu.Unlock() @@ -237,7 +252,7 @@ func (api *PublicFilterAPI) NewBlockFilter() rpc.ID { return } } - }() + }(headersCh, headerSub.Err()) return headerSub.ID() } @@ -255,7 +270,7 @@ func (api *PublicFilterAPI) NewHeads(ctx context.Context) (*rpc.Subscription, er var err error go func() { headersCh := make(<-chan coretypes.ResultEvent) - headersSub, cancelSubs, err := api.events.SubscribeNewHeads(headersCh) + headersSub, headersCh, cancelSubs, err := api.events.SubscribeNewHeads(headersCh) if err != nil { return } diff --git a/rpc/filter_system.go b/rpc/filter_system.go index f69e24f18..d5c65e2cd 100644 --- a/rpc/filter_system.go +++ b/rpc/filter_system.go @@ -92,7 +92,7 @@ func (es *EventSystem) WithContext(ctx context.Context) { es.ctx = ctx } -func (es *EventSystem) subscribe(sub *Subscription, eventCh <-chan coretypes.ResultEvent) (*Subscription, context.CancelFunc, error) { +func (es *EventSystem) subscribe(sub *Subscription, eventCh <-chan coretypes.ResultEvent) (*Subscription, <-chan coretypes.ResultEvent, context.CancelFunc, error) { var ( err error cancelFn context.CancelFunc @@ -119,7 +119,7 @@ func (es *EventSystem) subscribe(sub *Subscription, eventCh <-chan coretypes.Res if err != nil { sub.err <- err - return nil, cancelFn, err + return nil, nil, cancelFn, err } // wrap events in a go routine to prevent blocking @@ -128,13 +128,13 @@ func (es *EventSystem) subscribe(sub *Subscription, eventCh <-chan coretypes.Res <-sub.installed }() - go func(ch <-chan coretypes.ResultEvent) { - for e := range ch { - log.Println("got event!!!", e) - } - }(eventCh) + // go func(ch <-chan coretypes.ResultEvent) { + // for e := range ch { + // log.Println("got event!!!", e) + // } + // }(eventCh) - return sub, cancelFn, nil + return sub, eventCh, cancelFn, nil } // SubscribeLogs creates a subscription that will write all logs matching the @@ -186,7 +186,9 @@ func (es *EventSystem) subscribeMinedPendingLogs(crit filters.FilterCriteria, ev installed: make(chan struct{}, 1), err: make(chan error, 1), } - return es.subscribe(sub, eventCh) + sub, ch, cancel, err := es.subscribe(sub, eventCh) + eventCh = ch + return sub, cancel, err } // subscribeLogs creates a subscription that will write all logs matching the @@ -202,7 +204,9 @@ func (es *EventSystem) subscribeLogs(crit filters.FilterCriteria, eventCh <-chan installed: make(chan struct{}, 1), err: make(chan error, 1), } - return es.subscribe(sub, eventCh) + sub, ch, cancel, err := es.subscribe(sub, eventCh) + eventCh = ch + return sub, cancel, err } // subscribePendingLogs creates a subscription that writes transaction hashes for @@ -218,11 +222,13 @@ func (es *EventSystem) subscribePendingLogs(crit filters.FilterCriteria, eventCh installed: make(chan struct{}, 1), err: make(chan error, 1), } - return es.subscribe(sub, eventCh) + sub, ch, cancel, err := es.subscribe(sub, eventCh) + eventCh = ch + return sub, cancel, err } // SubscribeNewHeads subscribes to new block headers events. -func (es EventSystem) SubscribeNewHeads(eventCh <-chan coretypes.ResultEvent) (*Subscription, context.CancelFunc, error) { +func (es EventSystem) SubscribeNewHeads(eventCh <-chan coretypes.ResultEvent) (*Subscription, <-chan coretypes.ResultEvent, context.CancelFunc, error) { sub := &Subscription{ id: rpc.NewID(), typ: filters.BlocksSubscription, @@ -246,7 +252,9 @@ func (es EventSystem) SubscribePendingTxs(eventCh <-chan coretypes.ResultEvent) installed: make(chan struct{}, 1), err: make(chan error, 1), } - return es.subscribe(sub, eventCh) + sub, ch, cancel, err := es.subscribe(sub, eventCh) + eventCh = ch + return sub, cancel, err } type filterIndex map[filters.Type]map[rpc.ID]*Subscription @@ -309,7 +317,7 @@ func (es *EventSystem) eventLoop() { defer cancelLogsSubs() - es.chainSub, cancelHeaderSubs, err = es.SubscribeNewHeads(es.chainCh) + es.chainSub, es.chainCh, cancelHeaderSubs, err = es.SubscribeNewHeads(es.chainCh) if err != nil { panic(fmt.Errorf("failed to subscribe headers: %w", err)) } diff --git a/tests/rpc_test.go b/tests/rpc_test.go index 6df43b936..a776ad129 100644 --- a/tests/rpc_test.go +++ b/tests/rpc_test.go @@ -283,7 +283,7 @@ func TestEth_GetFilterChanges_BlockFilter(t *testing.T) { err := json.Unmarshal(rpcRes.Result, &ID) require.NoError(t, err) - time.Sleep(3 * time.Second) + time.Sleep(5 * time.Second) t.Log(ID.String()) From 324c69231852f37f8e82a5a8b25fe934dee7f454 Mon Sep 17 00:00:00 2001 From: noot Date: Thu, 2 Jul 2020 12:30:54 -0400 Subject: [PATCH 26/36] cleanup --- rpc/filter_api.go | 15 --------------- rpc/filter_system.go | 6 ------ tests/rpc_test.go | 4 +--- 3 files changed, 1 insertion(+), 24 deletions(-) diff --git a/rpc/filter_api.go b/rpc/filter_api.go index 258091dfa..9732f4912 100644 --- a/rpc/filter_api.go +++ b/rpc/filter_api.go @@ -216,21 +216,6 @@ func (api *PublicFilterAPI) NewBlockFilter() rpc.ID { log.Println("starting block header loop, id =", headerSub.ID()) - // go func(ch <-chan coretypes.ResultEvent) { - // for ev := range ch { - // log.Println("got event!!!", ev) - - // data, _ := ev.Data.(tmtypes.EventDataNewBlockHeader) - // header := EthHeaderFromTendermint(data.Header) - // log.Println("header", header) - // api.filtersMu.Lock() - // if f, found := api.filters[headerSub.ID()]; found { - // f.hashes = append(f.hashes, header.Hash()) - // } - // api.filtersMu.Unlock() - // } - // }(headersCh) - go func(headersCh <-chan coretypes.ResultEvent, errCh <-chan error) { // nolint: gosimple for { diff --git a/rpc/filter_system.go b/rpc/filter_system.go index d5c65e2cd..976e7c1fe 100644 --- a/rpc/filter_system.go +++ b/rpc/filter_system.go @@ -128,12 +128,6 @@ func (es *EventSystem) subscribe(sub *Subscription, eventCh <-chan coretypes.Res <-sub.installed }() - // go func(ch <-chan coretypes.ResultEvent) { - // for e := range ch { - // log.Println("got event!!!", e) - // } - // }(eventCh) - return sub, eventCh, cancelFn, nil } diff --git a/tests/rpc_test.go b/tests/rpc_test.go index a776ad129..f38f6a447 100644 --- a/tests/rpc_test.go +++ b/tests/rpc_test.go @@ -285,13 +285,11 @@ func TestEth_GetFilterChanges_BlockFilter(t *testing.T) { time.Sleep(5 * time.Second) - t.Log(ID.String()) - changesRes := call(t, "eth_getFilterChanges", []string{ID.String()}) var hashes []ethcmn.Hash err = json.Unmarshal(changesRes.Result, &hashes) require.NoError(t, err) - t.Log(hashes) + require.GreaterOrEqual(t, len(hashes), 1) } func TestEth_GetFilterChanges_NoLogs(t *testing.T) { From 7130bea56203948538c225c4b086ad25d35d4b99 Mon Sep 17 00:00:00 2001 From: Federico Kunze Date: Thu, 2 Jul 2020 18:33:27 +0200 Subject: [PATCH 27/36] lint --- rpc/filter_api.go | 27 ++++++++++++++++++++------- rpc/filter_system.go | 3 ++- rpc/filters.go | 10 +++------- 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/rpc/filter_api.go b/rpc/filter_api.go index cdfe1c61f..d134e5438 100644 --- a/rpc/filter_api.go +++ b/rpc/filter_api.go @@ -11,7 +11,6 @@ import ( tmtypes "github.com/tendermint/tendermint/types" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth/filters" "github.com/ethereum/go-ethereum/rpc" @@ -44,7 +43,7 @@ type filter struct { deadline *time.Timer // filter is inactive when deadline triggers hashes []common.Hash crit filters.FilterCriteria - logs []*types.Log + logs []*ethtypes.Log s *Subscription // associated subscription in event system } @@ -253,8 +252,12 @@ func (api *PublicFilterAPI) NewHeads(ctx context.Context) (*rpc.Subscription, er var err error go func() { + var ( + headersSub *Subscription + cancelSubs context.CancelFunc + ) headersCh := make(<-chan coretypes.ResultEvent) - headersSub, cancelSubs, err := api.events.SubscribeNewHeads(headersCh) + headersSub, cancelSubs, err = api.events.SubscribeNewHeads(headersCh) if err != nil { return } @@ -300,8 +303,13 @@ func (api *PublicFilterAPI) Logs(ctx context.Context, crit filters.FilterCriteri var err error go func() { + var ( + logsSub *Subscription + cancelSubs context.CancelFunc + ) + logsCh := make(<-chan coretypes.ResultEvent) - logsSub, cancelSubs, err := api.events.SubscribeLogs(crit, logsCh) + logsSub, cancelSubs, err = api.events.SubscribeLogs(crit, logsCh) if err != nil { return } @@ -373,8 +381,13 @@ func (api *PublicFilterAPI) NewFilter(criteria filters.FilterCriteria) (rpc.ID, ) go func() { + var ( + logsSub *Subscription + cancelSubs context.CancelFunc + ) + logsCh := make(<-chan coretypes.ResultEvent) - logsSub, cancelSubs, err := api.events.SubscribeLogs(criteria, logsCh) + logsSub, cancelSubs, err = api.events.SubscribeLogs(criteria, logsCh) if err != nil { return } @@ -556,9 +569,9 @@ func returnHashes(hashes []common.Hash) []common.Hash { // returnLogs is a helper that will return an empty log array in case the given logs array is nil, // otherwise the given logs array is returned. -func returnLogs(logs []*types.Log) []*types.Log { +func returnLogs(logs []*ethtypes.Log) []*ethtypes.Log { if logs == nil { - return []*types.Log{} + return []*ethtypes.Log{} } return logs } diff --git a/rpc/filter_system.go b/rpc/filter_system.go index 8c4e84dfd..c1d56dd24 100644 --- a/rpc/filter_system.go +++ b/rpc/filter_system.go @@ -42,6 +42,7 @@ type EventSystem struct { txsSub *Subscription // Subscription for new transaction event logsSub *Subscription // Subscription for new log event // rmLogsSub *Subscription // Subscription for removed log event + pendingLogsSub *Subscription // Subscription for pending log event chainSub *Subscription // Subscription for new chain event @@ -315,7 +316,7 @@ func (es *EventSystem) eventLoop() { es.txsSub.Unsubscribe(es) es.logsSub.Unsubscribe(es) // es.rmLogsSub.Unsubscribe(es) - // es.pendingLogsSub.Unsubscribe(es) + es.pendingLogsSub.Unsubscribe(es) es.chainSub.Unsubscribe(es) }() diff --git a/rpc/filters.go b/rpc/filters.go index 31ccf5194..74c6c3c2a 100644 --- a/rpc/filters.go +++ b/rpc/filters.go @@ -117,11 +117,7 @@ func (f *Filter) Logs(_ context.Context) ([]*ethtypes.Log, error) { continue } - logsMatched, err := f.checkMatches(txs) - if err != nil { - return logs, err - } - + logsMatched := f.checkMatches(txs) logs = append(logs, logsMatched...) } @@ -153,7 +149,7 @@ func (f *Filter) blockLogs(header *ethtypes.Header) ([]*ethtypes.Log, error) { // checkMatches checks if the logs from the a list of transactions transaction // contain any log events that match the filter criteria. This function is // called when the bloom filter signals a potential match. -func (f *Filter) checkMatches(transactions []common.Hash) ([]*ethtypes.Log, error) { +func (f *Filter) checkMatches(transactions []common.Hash) []*ethtypes.Log { unfiltered := []*ethtypes.Log{} for _, tx := range transactions { logs, err := f.backend.GetTransactionLogs(tx) @@ -166,7 +162,7 @@ func (f *Filter) checkMatches(transactions []common.Hash) ([]*ethtypes.Log, erro unfiltered = append(unfiltered, logs...) } - return filterLogs(unfiltered, f.criteria.FromBlock, f.criteria.ToBlock, f.criteria.Addresses, f.criteria.Topics), nil + return filterLogs(unfiltered, f.criteria.FromBlock, f.criteria.ToBlock, f.criteria.Addresses, f.criteria.Topics) } // filterLogs creates a slice of logs matching the given criteria. From 5a4b15cef4713334b3f896c1c90765375604c3a6 Mon Sep 17 00:00:00 2001 From: noot Date: Thu, 2 Jul 2020 14:37:30 -0400 Subject: [PATCH 28/36] NewFilter and GetFilterChanges working --- rpc/filter_api.go | 64 +++++++++++++++++++++----------------------- rpc/filter_system.go | 61 +++++++++++++++++++---------------------- tests/rpc_test.go | 7 ++--- 3 files changed, 61 insertions(+), 71 deletions(-) diff --git a/rpc/filter_api.go b/rpc/filter_api.go index 9732f4912..a9b9bef5b 100644 --- a/rpc/filter_api.go +++ b/rpc/filter_api.go @@ -108,8 +108,7 @@ func (api *PublicFilterAPI) timeoutLoop() { // // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newPendingTransactionFilter func (api *PublicFilterAPI) NewPendingTransactionFilter() rpc.ID { - txsCh := make(<-chan coretypes.ResultEvent) - pendingTxSub, cancelSubs, err := api.events.SubscribePendingTxs(txsCh) + pendingTxSub, cancelSubs, err := api.events.SubscribePendingTxs() if err != nil { // wrap error on the ID return rpc.ID(fmt.Sprintf("error creating pending tx filter: %s", err.Error())) @@ -121,7 +120,7 @@ func (api *PublicFilterAPI) NewPendingTransactionFilter() rpc.ID { api.filters[pendingTxSub.ID()] = &filter{typ: filters.PendingTransactionsSubscription, deadline: time.NewTimer(deadline), hashes: make([]common.Hash, 0), s: pendingTxSub} api.filtersMu.Unlock() - go func() { + go func(txsCh <-chan coretypes.ResultEvent) { for { select { case ev := <-txsCh: @@ -140,7 +139,7 @@ func (api *PublicFilterAPI) NewPendingTransactionFilter() rpc.ID { return } } - }() + }(pendingTxSub.eventCh) return pendingTxSub.ID() } @@ -160,15 +159,14 @@ func (api *PublicFilterAPI) NewPendingTransactions(ctx context.Context) (*rpc.Su api.events.WithContext(ctx) - txsCh := make(<-chan coretypes.ResultEvent) - pendingTxSub, cancelSubs, err := api.events.SubscribePendingTxs(txsCh) + pendingTxSub, cancelSubs, err := api.events.SubscribePendingTxs() if err != nil { return nil, err } defer cancelSubs() - go func() { + go func(txsCh <-chan coretypes.ResultEvent) { for { select { case ev := <-txsCh: @@ -189,7 +187,7 @@ func (api *PublicFilterAPI) NewPendingTransactions(ctx context.Context) (*rpc.Su return } } - }() + }(pendingTxSub.eventCh) return rpcSub, err } @@ -199,15 +197,12 @@ func (api *PublicFilterAPI) NewPendingTransactions(ctx context.Context) (*rpc.Su // // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newblockfilter func (api *PublicFilterAPI) NewBlockFilter() rpc.ID { - headersCh := make(<-chan coretypes.ResultEvent) - headerSub, headersCh, cancelSubs, err := api.events.SubscribeNewHeads(headersCh) + headerSub, cancelSubs, err := api.events.SubscribeNewHeads() if err != nil { // wrap error on the ID return rpc.ID(fmt.Sprintf("error creating block filter: %s", err.Error())) } - log.Println("new block filter") - defer cancelSubs() api.filtersMu.Lock() @@ -223,7 +218,6 @@ func (api *PublicFilterAPI) NewBlockFilter() rpc.ID { case ev := <-headersCh: data, _ := ev.Data.(tmtypes.EventDataNewBlockHeader) header := EthHeaderFromTendermint(data.Header) - log.Println("header", header) api.filtersMu.Lock() if f, found := api.filters[headerSub.ID()]; found { f.hashes = append(f.hashes, header.Hash()) @@ -237,7 +231,7 @@ func (api *PublicFilterAPI) NewBlockFilter() rpc.ID { return } } - }(headersCh, headerSub.Err()) + }(headerSub.eventCh, headerSub.Err()) return headerSub.ID() } @@ -254,8 +248,7 @@ func (api *PublicFilterAPI) NewHeads(ctx context.Context) (*rpc.Subscription, er var err error go func() { - headersCh := make(<-chan coretypes.ResultEvent) - headersSub, headersCh, cancelSubs, err := api.events.SubscribeNewHeads(headersCh) + headersSub, cancelSubs, err := api.events.SubscribeNewHeads() if err != nil { return } @@ -301,8 +294,7 @@ func (api *PublicFilterAPI) Logs(ctx context.Context, crit filters.FilterCriteri var err error go func() { - logsCh := make(<-chan coretypes.ResultEvent) - logsSub, cancelSubs, err := api.events.SubscribeLogs(crit, logsCh) + logsSub, cancelSubs, err := api.events.SubscribeLogs(crit) if err != nil { return } @@ -311,7 +303,7 @@ func (api *PublicFilterAPI) Logs(ctx context.Context, crit filters.FilterCriteri for { select { - case event := <-logsCh: + case event := <-logsSub.eventCh: // filter only events from EVM module txs _, isMsgEthermint := event.Events[evmtypes.TypeMsgEthermint] _, isMsgEthereumTx := event.Events[evmtypes.TypeMsgEthereumTx] @@ -373,46 +365,51 @@ func (api *PublicFilterAPI) NewFilter(criteria filters.FilterCriteria) (rpc.ID, err error ) - go func() { - logsCh := make(<-chan coretypes.ResultEvent) - logsSub, cancelSubs, err := api.events.SubscribeLogs(criteria, logsCh) - if err != nil { - return - } + logsSub, cancelSubs, err := api.events.SubscribeLogs(criteria) + if err != nil { + return rpc.ID(0), err + } - defer cancelSubs() + filterID = logsSub.ID() + + api.filtersMu.Lock() + api.filters[filterID] = &filter{typ: filters.LogsSubscription, deadline: time.NewTimer(deadline), hashes: []common.Hash{}, s: logsSub} + api.filtersMu.Unlock() - filterID = logsSub.ID() + go func(eventCh <-chan coretypes.ResultEvent) { + defer cancelSubs() for { select { - case event := <-logsCh: + case event := <-eventCh: dataTx, ok := event.Data.(tmtypes.EventDataTx) if !ok { err = fmt.Errorf("invalid event data %T, expected EventDataTx", event.Data) + log.Println("error:", err) return } resultData, err := evmtypes.DecodeResultData(dataTx.TxResult.Result.Data) if err != nil { + log.Println("cannot decode result data; error:", err) return } logs := filterLogs(resultData.Logs, criteria.FromBlock, criteria.ToBlock, criteria.Addresses, criteria.Topics) api.filtersMu.Lock() - if f, found := api.filters[logsSub.ID()]; found { + if f, found := api.filters[filterID]; found { f.logs = append(f.logs, logs...) } api.filtersMu.Unlock() case <-logsSub.Err(): api.filtersMu.Lock() - delete(api.filters, logsSub.ID()) + delete(api.filters, filterID) api.filtersMu.Unlock() return } } - }() + }(logsSub.eventCh) return filterID, err } @@ -538,8 +535,9 @@ func (api *PublicFilterAPI) GetFilterChanges(id rpc.ID) (interface{}, error) { f.hashes = nil return returnHashes(hashes), nil case filters.LogsSubscription, filters.MinedAndPendingLogsSubscription: - logs := f.logs - f.logs = nil + logs := make([]*ethtypes.Log, len(f.logs)) + copy(logs, f.logs) + f.logs = []*ethtypes.Log{} return returnLogs(logs), nil default: return nil, fmt.Errorf("invalid filter %s type %d", id, f.typ) diff --git a/rpc/filter_system.go b/rpc/filter_system.go index 976e7c1fe..27b6c9668 100644 --- a/rpc/filter_system.go +++ b/rpc/filter_system.go @@ -92,10 +92,11 @@ func (es *EventSystem) WithContext(ctx context.Context) { es.ctx = ctx } -func (es *EventSystem) subscribe(sub *Subscription, eventCh <-chan coretypes.ResultEvent) (*Subscription, <-chan coretypes.ResultEvent, context.CancelFunc, error) { +func (es *EventSystem) subscribe(sub *Subscription) (*Subscription, context.CancelFunc, error) { var ( err error cancelFn context.CancelFunc + eventCh <-chan coretypes.ResultEvent ) es.ctx, cancelFn = context.WithTimeout(context.Background(), deadline) @@ -119,7 +120,7 @@ func (es *EventSystem) subscribe(sub *Subscription, eventCh <-chan coretypes.Res if err != nil { sub.err <- err - return nil, nil, cancelFn, err + return nil, cancelFn, err } // wrap events in a go routine to prevent blocking @@ -128,13 +129,14 @@ func (es *EventSystem) subscribe(sub *Subscription, eventCh <-chan coretypes.Res <-sub.installed }() - return sub, eventCh, cancelFn, nil + sub.eventCh = eventCh + return sub, cancelFn, nil } // SubscribeLogs creates a subscription that will write all logs matching the // given criteria to the given logs channel. Default value for the from and to // block is "latest". If the fromBlock > toBlock an error is returned. -func (es *EventSystem) SubscribeLogs(crit filters.FilterCriteria, eventCh <-chan coretypes.ResultEvent) (*Subscription, context.CancelFunc, error) { +func (es *EventSystem) SubscribeLogs(crit filters.FilterCriteria) (*Subscription, context.CancelFunc, error) { var from, to rpc.BlockNumber if crit.FromBlock == nil { from = rpc.LatestBlockNumber @@ -150,17 +152,17 @@ func (es *EventSystem) SubscribeLogs(crit filters.FilterCriteria, eventCh <-chan switch { // only interested in pending logs case from == rpc.PendingBlockNumber && to == rpc.PendingBlockNumber: - return es.subscribePendingLogs(crit, eventCh) + return es.subscribePendingLogs(crit) // only interested in new mined logs, mined logs within a specific block range, or // logs from a specific block number to new mined blocks case (from == rpc.LatestBlockNumber && to == rpc.LatestBlockNumber), (from >= 0 && to >= 0 && to >= from): - return es.subscribeLogs(crit, eventCh) + return es.subscribeLogs(crit) // interested in mined logs from a specific block number, new logs and pending logs - case from >= rpc.LatestBlockNumber && to == rpc.PendingBlockNumber: - return es.subscribeMinedPendingLogs(crit, eventCh) + case from >= rpc.LatestBlockNumber && (to == rpc.PendingBlockNumber || to == rpc.LatestBlockNumber): + return es.subscribeMinedPendingLogs(crit) default: return nil, nil, fmt.Errorf("invalid from and to block combination: from > to (%d > %d)", from, to) @@ -169,7 +171,7 @@ func (es *EventSystem) SubscribeLogs(crit filters.FilterCriteria, eventCh <-chan // subscribeMinedPendingLogs creates a subscription that returned mined and // pending logs that match the given criteria. -func (es *EventSystem) subscribeMinedPendingLogs(crit filters.FilterCriteria, eventCh <-chan coretypes.ResultEvent) (*Subscription, context.CancelFunc, error) { +func (es *EventSystem) subscribeMinedPendingLogs(crit filters.FilterCriteria) (*Subscription, context.CancelFunc, error) { sub := &Subscription{ id: rpc.NewID(), typ: filters.MinedAndPendingLogsSubscription, @@ -180,14 +182,12 @@ func (es *EventSystem) subscribeMinedPendingLogs(crit filters.FilterCriteria, ev installed: make(chan struct{}, 1), err: make(chan error, 1), } - sub, ch, cancel, err := es.subscribe(sub, eventCh) - eventCh = ch - return sub, cancel, err + return es.subscribe(sub) } // subscribeLogs creates a subscription that will write all logs matching the // given criteria to the given logs channel. -func (es *EventSystem) subscribeLogs(crit filters.FilterCriteria, eventCh <-chan coretypes.ResultEvent) (*Subscription, context.CancelFunc, error) { +func (es *EventSystem) subscribeLogs(crit filters.FilterCriteria) (*Subscription, context.CancelFunc, error) { sub := &Subscription{ id: rpc.NewID(), typ: filters.LogsSubscription, @@ -198,14 +198,12 @@ func (es *EventSystem) subscribeLogs(crit filters.FilterCriteria, eventCh <-chan installed: make(chan struct{}, 1), err: make(chan error, 1), } - sub, ch, cancel, err := es.subscribe(sub, eventCh) - eventCh = ch - return sub, cancel, err + return es.subscribe(sub) } // subscribePendingLogs creates a subscription that writes transaction hashes for // transactions that enter the transaction pool. -func (es *EventSystem) subscribePendingLogs(crit filters.FilterCriteria, eventCh <-chan coretypes.ResultEvent) (*Subscription, context.CancelFunc, error) { +func (es *EventSystem) subscribePendingLogs(crit filters.FilterCriteria) (*Subscription, context.CancelFunc, error) { sub := &Subscription{ id: rpc.NewID(), typ: filters.PendingLogsSubscription, @@ -216,13 +214,11 @@ func (es *EventSystem) subscribePendingLogs(crit filters.FilterCriteria, eventCh installed: make(chan struct{}, 1), err: make(chan error, 1), } - sub, ch, cancel, err := es.subscribe(sub, eventCh) - eventCh = ch - return sub, cancel, err + return es.subscribe(sub) } // SubscribeNewHeads subscribes to new block headers events. -func (es EventSystem) SubscribeNewHeads(eventCh <-chan coretypes.ResultEvent) (*Subscription, <-chan coretypes.ResultEvent, context.CancelFunc, error) { +func (es EventSystem) SubscribeNewHeads() (*Subscription, context.CancelFunc, error) { sub := &Subscription{ id: rpc.NewID(), typ: filters.BlocksSubscription, @@ -232,11 +228,11 @@ func (es EventSystem) SubscribeNewHeads(eventCh <-chan coretypes.ResultEvent) (* installed: make(chan struct{}, 1), err: make(chan error, 1), } - return es.subscribe(sub, eventCh) + return es.subscribe(sub) } // SubscribePendingTxs subscribes to new pending transactions events from the mempool. -func (es EventSystem) SubscribePendingTxs(eventCh <-chan coretypes.ResultEvent) (*Subscription, context.CancelFunc, error) { +func (es EventSystem) SubscribePendingTxs() (*Subscription, context.CancelFunc, error) { sub := &Subscription{ id: rpc.NewID(), typ: filters.PendingTransactionsSubscription, @@ -246,9 +242,7 @@ func (es EventSystem) SubscribePendingTxs(eventCh <-chan coretypes.ResultEvent) installed: make(chan struct{}, 1), err: make(chan error, 1), } - sub, ch, cancel, err := es.subscribe(sub, eventCh) - eventCh = ch - return sub, cancel, err + return es.subscribe(sub) } type filterIndex map[filters.Type]map[rpc.ID]*Subscription @@ -297,21 +291,21 @@ func (es *EventSystem) eventLoop() { ) // Subscribe events - es.txsSub, cancelPendingTxsSubs, err = es.SubscribePendingTxs(es.txsCh) + es.txsSub, cancelPendingTxsSubs, err = es.SubscribePendingTxs() if err != nil { panic(fmt.Errorf("failed to subscribe pending txs: %w", err)) } defer cancelPendingTxsSubs() - es.logsSub, cancelLogsSubs, err = es.SubscribeLogs(filters.FilterCriteria{}, es.logsCh) + es.logsSub, cancelLogsSubs, err = es.SubscribeLogs(filters.FilterCriteria{}) if err != nil { panic(fmt.Errorf("failed to subscribe logs: %w", err)) } defer cancelLogsSubs() - es.chainSub, es.chainCh, cancelHeaderSubs, err = es.SubscribeNewHeads(es.chainCh) + es.chainSub, cancelHeaderSubs, err = es.SubscribeNewHeads() if err != nil { panic(fmt.Errorf("failed to subscribe headers: %w", err)) } @@ -330,15 +324,15 @@ func (es *EventSystem) eventLoop() { log.Println("start event loop") for { select { - case txEvent := <-es.txsCh: + case txEvent := <-es.txsSub.eventCh: // FIXME: does't work log.Println("received tx event", txEvent) es.handleTxsEvent(txEvent) - case headerEv := <-es.chainCh: + case headerEv := <-es.chainSub.eventCh: // FIXME: does't work - log.Println("received header event", headerEv) + //log.Println("received header event", headerEv) es.handleChainEvent(headerEv) - case logsEv := <-es.logsCh: + case logsEv := <-es.logsSub.eventCh: // FIXME: does't work log.Println("received logs event", logsEv) es.handleLogs(logsEv) @@ -391,6 +385,7 @@ type Subscription struct { hashes chan []common.Hash headers chan *ethtypes.Header installed chan struct{} // closed when the filter is installed + eventCh <-chan coretypes.ResultEvent err chan error } diff --git a/tests/rpc_test.go b/tests/rpc_test.go index f38f6a447..e1990f8fa 100644 --- a/tests/rpc_test.go +++ b/tests/rpc_test.go @@ -302,6 +302,8 @@ func TestEth_GetFilterChanges_NoLogs(t *testing.T) { err := json.Unmarshal(rpcRes.Result, &ID) require.NoError(t, err) + t.Log(ID.String()) + changesRes := call(t, "eth_getFilterChanges", []string{ID.String()}) var logs []*ethtypes.Log @@ -435,7 +437,6 @@ func TestEth_GetTransactionLogs(t *testing.T) { logs := new([]*ethtypes.Log) err := json.Unmarshal(rpcRes.Result, logs) require.NoError(t, err) - require.Equal(t, 1, len(*logs)) } @@ -450,7 +451,6 @@ func TestEth_GetFilterChanges_NoTopics(t *testing.T) { param[0] = make(map[string]interface{}) param[0]["topics"] = []string{} param[0]["fromBlock"] = res.String() - param[0]["toBlock"] = zeroString // latest // instantiate new filter rpcRes = call(t, "eth_newFilter", param) @@ -542,7 +542,6 @@ func TestEth_GetFilterChanges_Topics_AB(t *testing.T) { param[0] = make(map[string]interface{}) param[0]["topics"] = []string{helloTopic, worldTopic} param[0]["fromBlock"] = res.String() - param[0]["toBlock"] = zeroString // latest // instantiate new filter rpcRes = call(t, "eth_newFilter", param) @@ -573,7 +572,6 @@ func TestEth_GetFilterChanges_Topics_XB(t *testing.T) { param[0] = make(map[string]interface{}) param[0]["topics"] = []interface{}{nil, worldTopic} param[0]["fromBlock"] = res.String() - param[0]["toBlock"] = "0x0" // latest // instantiate new filter rpcRes = call(t, "eth_newFilter", param) @@ -653,7 +651,6 @@ func TestEth_PendingTransactionFilter(t *testing.T) { require.NoError(t, err, string(changesRes.Result)) require.True(t, len(txs) >= 2, "could not get any txs", "changesRes.Result", string(changesRes.Result)) - } func TestBlockBloom(t *testing.T) { From 4f6f172d002c9722ee2d2a6555de11c40f956036 Mon Sep 17 00:00:00 2001 From: noot Date: Thu, 2 Jul 2020 15:30:36 -0400 Subject: [PATCH 29/36] eth_getLogs working --- rpc/backend.go | 10 ++++++++++ rpc/filter_api.go | 2 ++ rpc/filters.go | 11 ++++++----- tests/rpc_test.go | 1 - 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/rpc/backend.go b/rpc/backend.go index ebb84b6ed..2d970353a 100644 --- a/rpc/backend.go +++ b/rpc/backend.go @@ -112,6 +112,16 @@ func (e *EthermintBackend) HeaderByHash(blockHash common.Hash) (*ethtypes.Header } func (e *EthermintBackend) getBlockHeader(height int64) (*ethtypes.Header, error) { + if height <= 0 { + // get latest block height + num, err := e.BlockNumber() + if err != nil { + return nil, err + } + + height = int64(num) + } + block, err := e.cliCtx.Client.Block(&height) if err != nil { return nil, err diff --git a/rpc/filter_api.go b/rpc/filter_api.go index 3265cd88c..86728bdc9 100644 --- a/rpc/filter_api.go +++ b/rpc/filter_api.go @@ -433,11 +433,13 @@ func (api *PublicFilterAPI) GetLogs(ctx context.Context, crit filters.FilterCrit // Construct the range filter filter = NewRangeFilter(api.backend, begin, end, crit.Addresses, crit.Topics) } + // Run the filter and return all the logs logs, err := filter.Logs(ctx) if err != nil { return nil, err } + return returnLogs(logs), err } diff --git a/rpc/filters.go b/rpc/filters.go index 74c6c3c2a..a2bd6562d 100644 --- a/rpc/filters.go +++ b/rpc/filters.go @@ -76,8 +76,9 @@ func newFilter(backend FiltersBackend, criteria filters.FilterCriteria, matcher func (f *Filter) Logs(_ context.Context) ([]*ethtypes.Log, error) { logs := []*ethtypes.Log{} var err error + // If we're doing singleton block filtering, execute and return - if f.criteria.BlockHash != nil || f.criteria.BlockHash != (&common.Hash{}) { + if f.criteria.BlockHash != nil && f.criteria.BlockHash != (&common.Hash{}) { header, err := f.backend.HeaderByHash(*f.criteria.BlockHash) if err != nil { return nil, err @@ -94,16 +95,16 @@ func (f *Filter) Logs(_ context.Context) ([]*ethtypes.Log, error) { return nil, err } - if header == nil { + if header == nil || header.Number == nil { return nil, nil } - head := header.Number.Uint64() + head := header.Number.Int64() if f.criteria.FromBlock.Int64() == -1 { - f.criteria.FromBlock = big.NewInt(int64(head)) + f.criteria.FromBlock = big.NewInt(head) } if f.criteria.ToBlock.Int64() == -1 { - f.criteria.ToBlock = big.NewInt(int64(head)) + f.criteria.ToBlock = big.NewInt(head) } for i := f.criteria.FromBlock.Int64(); i <= f.criteria.ToBlock.Int64(); i++ { diff --git a/tests/rpc_test.go b/tests/rpc_test.go index e1990f8fa..fde006183 100644 --- a/tests/rpc_test.go +++ b/tests/rpc_test.go @@ -614,7 +614,6 @@ func TestEth_GetLogs_Topics_AB(t *testing.T) { param[0] = make(map[string]interface{}) param[0]["topics"] = []string{helloTopic, worldTopic} param[0]["fromBlock"] = res.String() - param[0]["toBlock"] = zeroString // latest hash := deployTestContractWithFunction(t) waitForReceipt(t, hash) From d839c25c509713c68e46c8867a34adcd44d0c5a9 Mon Sep 17 00:00:00 2001 From: noot Date: Thu, 2 Jul 2020 17:03:00 -0400 Subject: [PATCH 30/36] lint --- rpc/filter_api.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rpc/filter_api.go b/rpc/filter_api.go index 86728bdc9..e772377e1 100644 --- a/rpc/filter_api.go +++ b/rpc/filter_api.go @@ -260,12 +260,14 @@ func (api *PublicFilterAPI) NewHeads(ctx context.Context) (*rpc.Subscription, er data, ok := ev.Data.(tmtypes.EventDataNewBlockHeader) if !ok { err = fmt.Errorf("invalid event data %T, expected %s", ev.Data, tmtypes.EventNewBlockHeader) + headersSub.err <- err return } header := EthHeaderFromTendermint(data.Header) err = notifier.Notify(rpcSub.ID, header) if err != nil { + headersSub.err <- err return } case <-rpcSub.Err(): From 2b046b06f876f2af32824db3aad39e8c2033a517 Mon Sep 17 00:00:00 2001 From: noot Date: Thu, 2 Jul 2020 17:14:05 -0400 Subject: [PATCH 31/36] lint --- rpc/filter_api.go | 1 + 1 file changed, 1 insertion(+) diff --git a/rpc/filter_api.go b/rpc/filter_api.go index e772377e1..e48cdc75f 100644 --- a/rpc/filter_api.go +++ b/rpc/filter_api.go @@ -318,6 +318,7 @@ func (api *PublicFilterAPI) Logs(ctx context.Context, crit filters.FilterCriteri dataTx, ok := event.Data.(tmtypes.EventDataTx) if !ok { err = fmt.Errorf("invalid event data %T, expected %s", event.Data, tmtypes.EventTx) + logsSub.err <- err return } From 414c943ddf13325017e5fe5ec03cfbd5246c9327 Mon Sep 17 00:00:00 2001 From: Federico Kunze Date: Fri, 3 Jul 2020 10:31:12 +0200 Subject: [PATCH 32/36] cleanup --- rpc/filter_api.go | 3 --- rpc/filter_system.go | 55 ++++++++++++++++++++++------------------- rpc/filters.go | 15 ----------- x/evm/keeper/querier.go | 11 --------- 4 files changed, 29 insertions(+), 55 deletions(-) diff --git a/rpc/filter_api.go b/rpc/filter_api.go index e48cdc75f..b3ad41f5c 100644 --- a/rpc/filter_api.go +++ b/rpc/filter_api.go @@ -208,10 +208,7 @@ func (api *PublicFilterAPI) NewBlockFilter() rpc.ID { api.filters[headerSub.ID()] = &filter{typ: filters.BlocksSubscription, deadline: time.NewTimer(deadline), hashes: []common.Hash{}, s: headerSub} api.filtersMu.Unlock() - log.Println("starting block header loop, id =", headerSub.ID()) - go func(headersCh <-chan coretypes.ResultEvent, errCh <-chan error) { - // nolint: gosimple for { select { case ev := <-headersCh: diff --git a/rpc/filter_system.go b/rpc/filter_system.go index 08d67eda6..00e7aa96b 100644 --- a/rpc/filter_system.go +++ b/rpc/filter_system.go @@ -53,9 +53,10 @@ type EventSystem struct { // Unidirectional channels to receive Tendermint ResultEvents txsCh <-chan coretypes.ResultEvent // Channel to receive new pending transactions event logsCh <-chan coretypes.ResultEvent // Channel to receive new log event - pendingLogsCh <-chan coretypes.ResultEvent // Channel to receive new log event - rmLogsCh <-chan coretypes.ResultEvent // Channel to receive removed log event - chainCh <-chan coretypes.ResultEvent // Channel to receive new chain event + pendingLogsCh <-chan coretypes.ResultEvent // Channel to receive new pending log event + // rmLogsCh <-chan coretypes.ResultEvent // Channel to receive removed log event + + chainCh <-chan coretypes.ResultEvent // Channel to receive new chain event } // NewEventSystem creates a new manager that listens for event on the given mux, @@ -80,19 +81,25 @@ func NewEventSystem(client rpcclient.Client) *EventSystem { txsCh: make(<-chan coretypes.ResultEvent), logsCh: make(<-chan coretypes.ResultEvent), pendingLogsCh: make(<-chan coretypes.ResultEvent), - rmLogsCh: make(<-chan coretypes.ResultEvent), - chainCh: make(<-chan coretypes.ResultEvent), + // rmLogsCh: make(<-chan coretypes.ResultEvent), + chainCh: make(<-chan coretypes.ResultEvent), } go es.eventLoop() return es } -// WithContext sets the a given context to the +// WithContext sets a new context to the EventSystem. This is required to set a timeout context when +// a new filter is intantiated. func (es *EventSystem) WithContext(ctx context.Context) { es.ctx = ctx } +// subscribe performs a new event subscription to a given Tendermint event. +// The subscription creates a unidirectional receive event channel to receive the ResultEvent. By +// default, the subscription timeouts (i.e is canceled) after 5 minutes. This function returns an +// error if the subscription fails (eg: if the identifier is already subscribed) or if the filter +// type is invalid. func (es *EventSystem) subscribe(sub *Subscription) (*Subscription, context.CancelFunc, error) { var ( err error @@ -105,16 +112,12 @@ func (es *EventSystem) subscribe(sub *Subscription) (*Subscription, context.Canc switch sub.typ { case filters.PendingTransactionsSubscription: eventCh, err = es.client.Subscribe(es.ctx, string(sub.id), sub.event) - log.Println("subscribed to pending txs") case filters.PendingLogsSubscription, filters.MinedAndPendingLogsSubscription: eventCh, err = es.client.Subscribe(es.ctx, string(sub.id), sub.event) - log.Println("subscribed to pending logs") case filters.LogsSubscription: eventCh, err = es.client.Subscribe(es.ctx, string(sub.id), sub.event) - log.Println("subscribed to logs") case filters.BlocksSubscription: eventCh, err = es.client.Subscribe(es.ctx, string(sub.id), sub.event) - log.Println("subscribed to headers") default: err = fmt.Errorf("invalid filter subscription type %d", sub.typ) } @@ -259,7 +262,6 @@ func (es *EventSystem) handleLogs(ev coretypes.ResultEvent) { return } for _, f := range es.index[filters.LogsSubscription] { - log.Println(es.index[filters.LogsSubscription]) matchedLogs := filterLogs(resultData.Logs, f.logsCrit.FromBlock, f.logsCrit.ToBlock, f.logsCrit.Addresses, f.logsCrit.Topics) if len(matchedLogs) > 0 { f.logs <- matchedLogs @@ -269,7 +271,6 @@ func (es *EventSystem) handleLogs(ev coretypes.ResultEvent) { func (es *EventSystem) handleTxsEvent(ev coretypes.ResultEvent) { data, _ := ev.Data.(tmtypes.EventDataTx) - log.Println(es.index[filters.PendingTransactionsSubscription]) for _, f := range es.index[filters.PendingTransactionsSubscription] { f.hashes <- []common.Hash{common.BytesToHash(data.Tx.Hash())} } @@ -277,7 +278,6 @@ func (es *EventSystem) handleTxsEvent(ev coretypes.ResultEvent) { func (es *EventSystem) handleChainEvent(ev coretypes.ResultEvent) { data, _ := ev.Data.(tmtypes.EventDataNewBlockHeader) - log.Println(es.index[filters.BlocksSubscription]) for _, f := range es.index[filters.BlocksSubscription] { f.headers <- EthHeaderFromTendermint(data.Header) } @@ -287,8 +287,8 @@ func (es *EventSystem) handleChainEvent(ev coretypes.ResultEvent) { // eventLoop (un)installs filters and processes mux events. func (es *EventSystem) eventLoop() { var ( - err error - cancelPendingTxsSubs, cancelLogsSubs, cancelHeaderSubs context.CancelFunc + err error + cancelPendingTxsSubs, cancelLogsSubs, cancelPendingLogsSubs, cancelHeaderSubs context.CancelFunc ) // Subscribe events @@ -306,6 +306,13 @@ func (es *EventSystem) eventLoop() { defer cancelLogsSubs() + es.pendingLogsSub, cancelPendingLogsSubs, err = es.subscribePendingLogs(filters.FilterCriteria{}) + if err != nil { + panic(fmt.Errorf("failed to subscribe pending logs: %w", err)) + } + + defer cancelPendingLogsSubs() + es.chainSub, cancelHeaderSubs, err = es.SubscribeNewHeads() if err != nil { panic(fmt.Errorf("failed to subscribe headers: %w", err)) @@ -322,20 +329,18 @@ func (es *EventSystem) eventLoop() { es.chainSub.Unsubscribe(es) }() - log.Println("start event loop") for { select { case txEvent := <-es.txsSub.eventCh: - // FIXME: does't work - log.Println("received tx event", txEvent) es.handleTxsEvent(txEvent) case headerEv := <-es.chainSub.eventCh: - // FIXME: does't work - //log.Println("received header event", headerEv) es.handleChainEvent(headerEv) case logsEv := <-es.logsSub.eventCh: - // FIXME: does't work - log.Println("received logs event", logsEv) + es.handleLogs(logsEv) + // TODO: figure out how to handle removed logs + // case logsEv := <-es.rmLogsSub.eventCh: + // es.handleLogs(logsEv) + case logsEv := <-es.pendingLogsSub.eventCh: es.handleLogs(logsEv) case f := <-es.install: @@ -347,7 +352,6 @@ func (es *EventSystem) eventLoop() { es.index[f.typ][f.id] = f } close(f.installed) - log.Println("filter installed", f.id) case f := <-es.uninstall: if f.typ == filters.MinedAndPendingLogsSubscription { @@ -358,7 +362,6 @@ func (es *EventSystem) eventLoop() { delete(es.index[f.typ], f.id) } close(f.err) - log.Println("filter uninstalled", f.id) // System stopped case <-es.txsSub.Err(): return @@ -366,8 +369,8 @@ func (es *EventSystem) eventLoop() { return // case <-es.rmLogsSub.Err(): // return - // case <-es.pendingLogsSub.Err(): - // return + case <-es.pendingLogsSub.Err(): + return case <-es.chainSub.Err(): return } diff --git a/rpc/filters.go b/rpc/filters.go index a2bd6562d..c69b725d8 100644 --- a/rpc/filters.go +++ b/rpc/filters.go @@ -241,18 +241,3 @@ func bloomFilter(bloom ethtypes.Bloom, addresses []common.Address, topics [][]co } return included } - -// func (f *Filter) pollForTransactions(hashCh chan []common.Hash, errCh chan error) { -// for { -// txs, err := f.backend.PendingTransactions() -// if err != nil { -// errCh <- err -// } - -// for _, tx := range txs { -// hashCh <- tx.Hash -// } - -// <-time.After(6 * time.Second) -// } -// } diff --git a/x/evm/keeper/querier.go b/x/evm/keeper/querier.go index 6c250247f..066a00894 100644 --- a/x/evm/keeper/querier.go +++ b/x/evm/keeper/querier.go @@ -89,17 +89,6 @@ func queryBlockNumber(ctx sdk.Context, keeper Keeper) ([]byte, error) { return bz, nil } -// func queryHeaderByNumber(ctx sdk.Context, keeper Keeper) ([]byte, error) { -// num := ctx.BlockHeight() -// bnRes := types.QueryResBlockNumber{Number: num} -// bz, err := codec.MarshalJSONIndent(keeper.cdc, bnRes) -// if err != nil { -// return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error()) -// } - -// return bz, nil -// } - func queryStorage(ctx sdk.Context, path []string, keeper Keeper) ([]byte, error) { addr := ethcmn.HexToAddress(path[1]) key := ethcmn.HexToHash(path[2]) From ec79038fe4641dff6d412f54d7563ae964138f21 Mon Sep 17 00:00:00 2001 From: Federico Kunze Date: Fri, 3 Jul 2020 10:58:45 +0200 Subject: [PATCH 33/36] remove logs and minor fixes --- rpc/filter_api.go | 56 ++++++++++++++++++++--------------------------- 1 file changed, 24 insertions(+), 32 deletions(-) diff --git a/rpc/filter_api.go b/rpc/filter_api.go index b3ad41f5c..b04b423b5 100644 --- a/rpc/filter_api.go +++ b/rpc/filter_api.go @@ -3,7 +3,6 @@ package rpc import ( "context" "fmt" - "log" "sync" "time" @@ -119,7 +118,7 @@ func (api *PublicFilterAPI) NewPendingTransactionFilter() rpc.ID { api.filters[pendingTxSub.ID()] = &filter{typ: filters.PendingTransactionsSubscription, deadline: time.NewTimer(deadline), hashes: make([]common.Hash, 0), s: pendingTxSub} api.filtersMu.Unlock() - go func(txsCh <-chan coretypes.ResultEvent) { + go func(txsCh <-chan coretypes.ResultEvent, errCh <-chan error) { for { select { case ev := <-txsCh: @@ -131,14 +130,13 @@ func (api *PublicFilterAPI) NewPendingTransactionFilter() rpc.ID { f.hashes = append(f.hashes, txHash) } api.filtersMu.Unlock() - case <-pendingTxSub.Err(): + case <-errCh: api.filtersMu.Lock() delete(api.filters, pendingTxSub.ID()) api.filtersMu.Unlock() - return } } - }(pendingTxSub.eventCh) + }(pendingTxSub.eventCh, pendingTxSub.Err()) return pendingTxSub.ID() } @@ -219,11 +217,10 @@ func (api *PublicFilterAPI) NewBlockFilter() rpc.ID { f.hashes = append(f.hashes, header.Hash()) } api.filtersMu.Unlock() - case err := <-errCh: + case <-errCh: api.filtersMu.Lock() delete(api.filters, headerSub.ID()) api.filtersMu.Unlock() - log.Println("block filter loop err", err) return } } @@ -242,18 +239,17 @@ func (api *PublicFilterAPI) NewHeads(ctx context.Context) (*rpc.Subscription, er api.events.WithContext(ctx) rpcSub := notifier.CreateSubscription() - var err error - go func() { - headersSub, cancelSubs, err := api.events.SubscribeNewHeads() - if err != nil { - return - } + headersSub, cancelSubs, err := api.events.SubscribeNewHeads() + if err != nil { + return &rpc.Subscription{}, err + } - defer cancelSubs() + defer cancelSubs() + go func(headersCh <-chan coretypes.ResultEvent) { for { select { - case ev := <-api.events.chainCh: + case ev := <-headersCh: data, ok := ev.Data.(tmtypes.EventDataNewBlockHeader) if !ok { err = fmt.Errorf("invalid event data %T, expected %s", ev.Data, tmtypes.EventNewBlockHeader) @@ -275,7 +271,7 @@ func (api *PublicFilterAPI) NewHeads(ctx context.Context) (*rpc.Subscription, er return } } - }() + }(headersSub.eventCh) return rpcSub, err } @@ -290,18 +286,17 @@ func (api *PublicFilterAPI) Logs(ctx context.Context, crit filters.FilterCriteri api.events.WithContext(ctx) rpcSub := notifier.CreateSubscription() - var err error - go func() { - logsSub, cancelSubs, err := api.events.SubscribeLogs(crit) - if err != nil { - return - } + logsSub, cancelSubs, err := api.events.SubscribeLogs(crit) + if err != nil { + return &rpc.Subscription{}, err + } - defer cancelSubs() + defer cancelSubs() + go func(logsCh <-chan coretypes.ResultEvent) { for { select { - case event := <-logsSub.eventCh: + case event := <-logsCh: // filter only events from EVM module txs _, isMsgEthermint := event.Events[evmtypes.TypeMsgEthermint] _, isMsgEthereumTx := event.Events[evmtypes.TypeMsgEthereumTx] @@ -340,7 +335,7 @@ func (api *PublicFilterAPI) Logs(ctx context.Context, crit filters.FilterCriteri return } } - }() + }(logsSub.eventCh) return rpcSub, err } @@ -366,8 +361,9 @@ func (api *PublicFilterAPI) NewFilter(criteria filters.FilterCriteria) (rpc.ID, logsSub, cancelSubs, err := api.events.SubscribeLogs(criteria) if err != nil { - return rpc.ID(0), err + return rpc.ID(""), err } + filterID = logsSub.ID() api.filtersMu.Lock() @@ -383,13 +379,12 @@ func (api *PublicFilterAPI) NewFilter(criteria filters.FilterCriteria) (rpc.ID, dataTx, ok := event.Data.(tmtypes.EventDataTx) if !ok { err = fmt.Errorf("invalid event data %T, expected EventDataTx", event.Data) - log.Println("error:", err) return } - resultData, err := evmtypes.DecodeResultData(dataTx.TxResult.Result.Data) + var resultData evmtypes.ResultData + resultData, err = evmtypes.DecodeResultData(dataTx.TxResult.Result.Data) if err != nil { - log.Println("cannot decode result data; error:", err) return } @@ -450,14 +445,11 @@ func (api *PublicFilterAPI) UninstallFilter(id rpc.ID) bool { api.filtersMu.Lock() f, found := api.filters[id] if found { - log.Println("deleting filter", id) delete(api.filters, id) - log.Println("uninstall complete", id) } api.filtersMu.Unlock() if !found { - log.Println("filter not found", id) return false } f.s.Unsubscribe(api.events) From 936b0e89018bbfc1ed1884c868b9c98dbf5f9675 Mon Sep 17 00:00:00 2001 From: Federico Kunze Date: Fri, 3 Jul 2020 11:02:30 +0200 Subject: [PATCH 34/36] changelog --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e6af17504..4eb12d621 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -65,8 +65,9 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Features -* (rpc) [\#231](https://github.com/ChainSafe/ethermint/issues/231) Implement NewBlockFilter in rpc/filters.go which instantiates a polling block filter - * Polls for new blocks via BlockNumber rpc call; if block number changes, it requests the new block via GetBlockByNumber rpc call and adds it to its internal list of blocks +* (rpc) [\#330](https://github.com/ChainSafe/ethermint/issues/330) Implement `PublicFilterAPI`'s `EventSystem` which subscribes to Tendermint events upon `Filter` creation. +* (rpc) [\#231](https://github.com/ChainSafe/ethermint/issues/231) Implement `NewBlockFilter` in rpc/filters.go which instantiates a polling block filter + * Polls for new blocks via `BlockNumber` rpc call; if block number changes, it requests the new block via `GetBlockByNumber` rpc call and adds it to its internal list of blocks * Update uninstallFilter and getFilterChanges accordingly * uninstallFilter stops the polling goroutine * getFilterChanges returns the filter's internal list of block hashes and resets it From 216a0c28659f02a7b1cfb56ba7e6d593e60e1387 Mon Sep 17 00:00:00 2001 From: Federico Kunze Date: Fri, 3 Jul 2020 17:24:23 +0200 Subject: [PATCH 35/36] address @noot comments --- rpc/filter_api.go | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/rpc/filter_api.go b/rpc/filter_api.go index b04b423b5..91a4b3713 100644 --- a/rpc/filter_api.go +++ b/rpc/filter_api.go @@ -24,12 +24,10 @@ type FiltersBackend interface { GetBlockByNumber(blockNum rpc.BlockNumber, fullTx bool) (map[string]interface{}, error) HeaderByNumber(blockNr rpc.BlockNumber) (*ethtypes.Header, error) HeaderByHash(blockHash common.Hash) (*ethtypes.Header, error) - // GetReceipts(blockHash common.Hash) (ethtypes.Receipts, error) GetLogs(blockHash common.Hash) ([][]*ethtypes.Log, error) GetTransactionLogs(txHash common.Hash) ([]*ethtypes.Log, error) BloomStatus() (uint64, uint64) - // ServiceFilter(session *bloombits.MatcherSession) } // consider a filter inactive if it has not been polled for within deadline @@ -112,13 +110,13 @@ func (api *PublicFilterAPI) NewPendingTransactionFilter() rpc.ID { return rpc.ID(fmt.Sprintf("error creating pending tx filter: %s", err.Error())) } - defer cancelSubs() - api.filtersMu.Lock() api.filters[pendingTxSub.ID()] = &filter{typ: filters.PendingTransactionsSubscription, deadline: time.NewTimer(deadline), hashes: make([]common.Hash, 0), s: pendingTxSub} api.filtersMu.Unlock() go func(txsCh <-chan coretypes.ResultEvent, errCh <-chan error) { + defer cancelSubs() + for { select { case ev := <-txsCh: @@ -161,9 +159,9 @@ func (api *PublicFilterAPI) NewPendingTransactions(ctx context.Context) (*rpc.Su return nil, err } - defer cancelSubs() - go func(txsCh <-chan coretypes.ResultEvent) { + defer cancelSubs() + for { select { case ev := <-txsCh: @@ -200,13 +198,13 @@ func (api *PublicFilterAPI) NewBlockFilter() rpc.ID { return rpc.ID(fmt.Sprintf("error creating block filter: %s", err.Error())) } - defer cancelSubs() - api.filtersMu.Lock() api.filters[headerSub.ID()] = &filter{typ: filters.BlocksSubscription, deadline: time.NewTimer(deadline), hashes: []common.Hash{}, s: headerSub} api.filtersMu.Unlock() go func(headersCh <-chan coretypes.ResultEvent, errCh <-chan error) { + defer cancelSubs() + for { select { case ev := <-headersCh: @@ -244,9 +242,9 @@ func (api *PublicFilterAPI) NewHeads(ctx context.Context) (*rpc.Subscription, er return &rpc.Subscription{}, err } - defer cancelSubs() - go func(headersCh <-chan coretypes.ResultEvent) { + defer cancelSubs() + for { select { case ev := <-headersCh: @@ -291,9 +289,9 @@ func (api *PublicFilterAPI) Logs(ctx context.Context, crit filters.FilterCriteri return &rpc.Subscription{}, err } - defer cancelSubs() - go func(logsCh <-chan coretypes.ResultEvent) { + defer cancelSubs() + for { select { case event := <-logsCh: @@ -302,11 +300,11 @@ func (api *PublicFilterAPI) Logs(ctx context.Context, crit filters.FilterCriteri _, isMsgEthereumTx := event.Events[evmtypes.TypeMsgEthereumTx] if !(isMsgEthermint || isMsgEthereumTx) { - // ignore transaction + // ignore transaction as it's not from the evm module return } - // get the + // get transaction result data dataTx, ok := event.Data.(tmtypes.EventDataTx) if !ok { err = fmt.Errorf("invalid event data %T, expected %s", event.Data, tmtypes.EventTx) From 322dac2cf4b607acd8e97b0f91903c2a48054a5d Mon Sep 17 00:00:00 2001 From: Federico Kunze Date: Fri, 3 Jul 2020 17:37:09 +0200 Subject: [PATCH 36/36] revert BlockNumber removal --- rpc/backend.go | 9 +++---- rpc/eth_api.go | 25 +++++++++--------- rpc/filter_api.go | 4 +-- rpc/filters.go | 5 ++-- rpc/types.go | 67 +++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 87 insertions(+), 23 deletions(-) create mode 100644 rpc/types.go diff --git a/rpc/backend.go b/rpc/backend.go index 2d970353a..b0accbc9b 100644 --- a/rpc/backend.go +++ b/rpc/backend.go @@ -14,7 +14,6 @@ import ( "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/rpc" ) // Backend implements the functionality needed to filter changes. @@ -22,9 +21,9 @@ import ( type Backend interface { // Used by block filter; also used for polling BlockNumber() (hexutil.Uint64, error) - HeaderByNumber(blockNum rpc.BlockNumber) (*ethtypes.Header, error) + HeaderByNumber(blockNum BlockNumber) (*ethtypes.Header, error) HeaderByHash(blockHash common.Hash) (*ethtypes.Header, error) - GetBlockByNumber(blockNum rpc.BlockNumber, fullTx bool) (map[string]interface{}, error) + GetBlockByNumber(blockNum BlockNumber, fullTx bool) (map[string]interface{}, error) GetBlockByHash(hash common.Hash, fullTx bool) (map[string]interface{}, error) getEthBlockByNumber(height int64, fullTx bool) (map[string]interface{}, error) getGasLimit() (int64, error) @@ -70,7 +69,7 @@ func (e *EthermintBackend) BlockNumber() (hexutil.Uint64, error) { } // GetBlockByNumber returns the block identified by number. -func (e *EthermintBackend) GetBlockByNumber(blockNum rpc.BlockNumber, fullTx bool) (map[string]interface{}, error) { +func (e *EthermintBackend) GetBlockByNumber(blockNum BlockNumber, fullTx bool) (map[string]interface{}, error) { value := blockNum.Int64() return e.getEthBlockByNumber(value, fullTx) } @@ -92,7 +91,7 @@ func (e *EthermintBackend) GetBlockByHash(hash common.Hash, fullTx bool) (map[st } // HeaderByNumber returns the block header identified by height. -func (e *EthermintBackend) HeaderByNumber(blockNum rpc.BlockNumber) (*ethtypes.Header, error) { +func (e *EthermintBackend) HeaderByNumber(blockNum BlockNumber) (*ethtypes.Header, error) { return e.getBlockHeader(blockNum.Int64()) } diff --git a/rpc/eth_api.go b/rpc/eth_api.go index 15119281b..8dbc29326 100644 --- a/rpc/eth_api.go +++ b/rpc/eth_api.go @@ -30,7 +30,6 @@ import ( "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/rpc" "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/flags" @@ -157,7 +156,7 @@ func (e *PublicEthAPI) BlockNumber() (hexutil.Uint64, error) { } // GetBalance returns the provided account's balance up to the provided block number. -func (e *PublicEthAPI) GetBalance(address common.Address, blockNum rpc.BlockNumber) (*hexutil.Big, error) { +func (e *PublicEthAPI) GetBalance(address common.Address, blockNum BlockNumber) (*hexutil.Big, error) { ctx := e.cliCtx.WithHeight(blockNum.Int64()) res, _, err := ctx.QueryWithData(fmt.Sprintf("custom/%s/balance/%s", evmtypes.ModuleName, address.Hex()), nil) if err != nil { @@ -175,7 +174,7 @@ func (e *PublicEthAPI) GetBalance(address common.Address, blockNum rpc.BlockNumb } // GetStorageAt returns the contract storage at the given address, block number, and key. -func (e *PublicEthAPI) GetStorageAt(address common.Address, key string, blockNum rpc.BlockNumber) (hexutil.Bytes, error) { +func (e *PublicEthAPI) GetStorageAt(address common.Address, key string, blockNum BlockNumber) (hexutil.Bytes, error) { ctx := e.cliCtx.WithHeight(blockNum.Int64()) res, _, err := ctx.QueryWithData(fmt.Sprintf("custom/%s/storage/%s/%s", evmtypes.ModuleName, address.Hex(), key), nil) if err != nil { @@ -188,7 +187,7 @@ func (e *PublicEthAPI) GetStorageAt(address common.Address, key string, blockNum } // GetTransactionCount returns the number of transactions at the given address up to the given block number. -func (e *PublicEthAPI) GetTransactionCount(address common.Address, blockNum rpc.BlockNumber) (*hexutil.Uint64, error) { +func (e *PublicEthAPI) GetTransactionCount(address common.Address, blockNum BlockNumber) (*hexutil.Uint64, error) { ctx := e.cliCtx.WithHeight(blockNum.Int64()) // Get nonce (sequence) from account @@ -226,7 +225,7 @@ func (e *PublicEthAPI) GetBlockTransactionCountByHash(hash common.Hash) *hexutil } // GetBlockTransactionCountByNumber returns the number of transactions in the block identified by number. -func (e *PublicEthAPI) GetBlockTransactionCountByNumber(blockNum rpc.BlockNumber) *hexutil.Uint { +func (e *PublicEthAPI) GetBlockTransactionCountByNumber(blockNum BlockNumber) *hexutil.Uint { height := blockNum.Int64() return e.getBlockTransactionCountByNumber(height) } @@ -248,12 +247,12 @@ func (e *PublicEthAPI) GetUncleCountByBlockHash(hash common.Hash) hexutil.Uint { } // GetUncleCountByBlockNumber returns the number of uncles in the block idenfied by number. Always zero. -func (e *PublicEthAPI) GetUncleCountByBlockNumber(blockNum rpc.BlockNumber) hexutil.Uint { +func (e *PublicEthAPI) GetUncleCountByBlockNumber(blockNum BlockNumber) hexutil.Uint { return 0 } // GetCode returns the contract code at the given address and block number. -func (e *PublicEthAPI) GetCode(address common.Address, blockNumber rpc.BlockNumber) (hexutil.Bytes, error) { +func (e *PublicEthAPI) GetCode(address common.Address, blockNumber BlockNumber) (hexutil.Bytes, error) { ctx := e.cliCtx.WithHeight(blockNumber.Int64()) res, _, err := ctx.QueryWithData(fmt.Sprintf("custom/%s/%s/%s", evmtypes.ModuleName, evmtypes.QueryCode, address.Hex()), nil) if err != nil { @@ -272,7 +271,7 @@ func (e *PublicEthAPI) GetTransactionLogs(txHash common.Hash) ([]*ethtypes.Log, // ExportAccount exports an account's balance, code, and storage at the given block number // TODO: deprecate this once the export genesis command works -func (e *PublicEthAPI) ExportAccount(address common.Address, blockNumber rpc.BlockNumber) (string, error) { +func (e *PublicEthAPI) ExportAccount(address common.Address, blockNumber BlockNumber) (string, error) { ctx := e.cliCtx.WithHeight(blockNumber.Int64()) res, _, err := ctx.QueryWithData(fmt.Sprintf("custom/%s/%s/%s", evmtypes.ModuleName, evmtypes.QueryExportAccount, address.Hex()), nil) @@ -388,7 +387,7 @@ type CallArgs struct { } // Call performs a raw contract call. -func (e *PublicEthAPI) Call(args CallArgs, blockNr rpc.BlockNumber, overrides *map[common.Address]account) (hexutil.Bytes, error) { +func (e *PublicEthAPI) Call(args CallArgs, blockNr BlockNumber, overrides *map[common.Address]account) (hexutil.Bytes, error) { simRes, err := e.doCall(args, blockNr, big.NewInt(emint.DefaultRPCGasLimit)) if err != nil { return []byte{}, err @@ -419,7 +418,7 @@ type account struct { // DoCall performs a simulated call operation through the evmtypes. It returns the // estimated gas used on the operation or an error if fails. func (e *PublicEthAPI) doCall( - args CallArgs, blockNr rpc.BlockNumber, globalGasCap *big.Int, + args CallArgs, blockNr BlockNumber, globalGasCap *big.Int, ) (*sdk.SimulationResponse, error) { // Set height for historical queries ctx := e.cliCtx @@ -524,7 +523,7 @@ func (e *PublicEthAPI) GetBlockByHash(hash common.Hash, fullTx bool) (map[string } // GetBlockByNumber returns the block identified by number. -func (e *PublicEthAPI) GetBlockByNumber(blockNum rpc.BlockNumber, fullTx bool) (map[string]interface{}, error) { +func (e *PublicEthAPI) GetBlockByNumber(blockNum BlockNumber, fullTx bool) (map[string]interface{}, error) { return e.backend.GetBlockByNumber(blockNum, fullTx) } @@ -677,7 +676,7 @@ func (e *PublicEthAPI) GetTransactionByBlockHashAndIndex(hash common.Hash, idx h } // GetTransactionByBlockNumberAndIndex returns the transaction identified by number and index. -func (e *PublicEthAPI) GetTransactionByBlockNumberAndIndex(blockNum rpc.BlockNumber, idx hexutil.Uint) (*Transaction, error) { +func (e *PublicEthAPI) GetTransactionByBlockNumberAndIndex(blockNum BlockNumber, idx hexutil.Uint) (*Transaction, error) { value := blockNum.Int64() return e.getTransactionByBlockNumberAndIndex(value, idx) } @@ -812,7 +811,7 @@ type StorageResult struct { } // GetProof returns an account object with proof and any storage proofs -func (e *PublicEthAPI) GetProof(address common.Address, storageKeys []string, block rpc.BlockNumber) (*AccountResult, error) { +func (e *PublicEthAPI) GetProof(address common.Address, storageKeys []string, block BlockNumber) (*AccountResult, error) { e.cliCtx = e.cliCtx.WithHeight(int64(block)) path := fmt.Sprintf("custom/%s/%s/%s", evmtypes.ModuleName, evmtypes.QueryAccount, address.Hex()) diff --git a/rpc/filter_api.go b/rpc/filter_api.go index 91a4b3713..6d2fdc845 100644 --- a/rpc/filter_api.go +++ b/rpc/filter_api.go @@ -21,8 +21,8 @@ import ( // FiltersBackend defines the methods requided by the PublicFilterAPI backend type FiltersBackend interface { - GetBlockByNumber(blockNum rpc.BlockNumber, fullTx bool) (map[string]interface{}, error) - HeaderByNumber(blockNr rpc.BlockNumber) (*ethtypes.Header, error) + GetBlockByNumber(blockNum BlockNumber, fullTx bool) (map[string]interface{}, error) + HeaderByNumber(blockNr BlockNumber) (*ethtypes.Header, error) HeaderByHash(blockHash common.Hash) (*ethtypes.Header, error) GetLogs(blockHash common.Hash) ([][]*ethtypes.Log, error) diff --git a/rpc/filters.go b/rpc/filters.go index c69b725d8..2a94040dd 100644 --- a/rpc/filters.go +++ b/rpc/filters.go @@ -9,7 +9,6 @@ import ( "github.com/ethereum/go-ethereum/core/bloombits" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth/filters" - "github.com/ethereum/go-ethereum/rpc" ) // Filter can be used to retrieve and filter logs. @@ -90,7 +89,7 @@ func (f *Filter) Logs(_ context.Context) ([]*ethtypes.Log, error) { } // Figure out the limits of the filter range - header, err := f.backend.HeaderByNumber(rpc.LatestBlockNumber) + header, err := f.backend.HeaderByNumber(LatestBlockNumber) if err != nil { return nil, err } @@ -108,7 +107,7 @@ func (f *Filter) Logs(_ context.Context) ([]*ethtypes.Log, error) { } for i := f.criteria.FromBlock.Int64(); i <= f.criteria.ToBlock.Int64(); i++ { - block, err := f.backend.GetBlockByNumber(rpc.BlockNumber(i), true) + block, err := f.backend.GetBlockByNumber(BlockNumber(i), true) if err != nil { return logs, err } diff --git a/rpc/types.go b/rpc/types.go new file mode 100644 index 000000000..935947a29 --- /dev/null +++ b/rpc/types.go @@ -0,0 +1,67 @@ +package rpc + +import ( + "fmt" + "math" + "math/big" + "strings" + + "github.com/ethereum/go-ethereum/common/hexutil" +) + +// BlockNumber represents decoding hex string to block values +type BlockNumber int64 + +const ( + // LatestBlockNumber mapping from "latest" to 0 for tm query + LatestBlockNumber = BlockNumber(0) + + // EarliestBlockNumber mapping from "earliest" to 1 for tm query (earliest query not supported) + EarliestBlockNumber = BlockNumber(1) +) + +// NewBlockNumber creates a new BlockNumber instance. +func NewBlockNumber(n *big.Int) BlockNumber { + return BlockNumber(n.Int64()) +} + +// UnmarshalJSON parses the given JSON fragment into a BlockNumber. It supports: +// - "latest", "earliest" or "pending" as string arguments +// - the block number +// Returned errors: +// - an invalid block number error when the given argument isn't a known strings +// - an out of range error when the given block number is either too little or too large +func (bn *BlockNumber) UnmarshalJSON(data []byte) error { + input := strings.TrimSpace(string(data)) + if len(input) >= 2 && input[0] == '"' && input[len(input)-1] == '"' { + input = input[1 : len(input)-1] + } + + switch input { + case "earliest": + *bn = EarliestBlockNumber + return nil + case "latest": + *bn = LatestBlockNumber + return nil + case "pending": + *bn = LatestBlockNumber + return nil + } + + blckNum, err := hexutil.DecodeUint64(input) + if err != nil { + return err + } + if blckNum > math.MaxInt64 { + return fmt.Errorf("blocknumber too high") + } + + *bn = BlockNumber(blckNum) + return nil +} + +// Int64 converts block number to primitive type +func (bn BlockNumber) Int64() int64 { + return (int64)(bn) +}