mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-20 10:52:25 +00:00
Delete eth/filters directory
This commit is contained in:
parent
9f7d2f649b
commit
9afd96afda
7 changed files with 0 additions and 3377 deletions
|
|
@ -1,609 +0,0 @@
|
||||||
// Copyright 2015 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package filters
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"math/big"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum"
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
|
||||||
"github.com/ethereum/go-ethereum/internal/ethapi"
|
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
errInvalidTopic = errors.New("invalid topic(s)")
|
|
||||||
errFilterNotFound = errors.New("filter not found")
|
|
||||||
errInvalidBlockRange = errors.New("invalid block range params")
|
|
||||||
errExceedMaxTopics = errors.New("exceed max topics")
|
|
||||||
)
|
|
||||||
|
|
||||||
// The maximum number of topic criteria allowed, vm.LOG4 - vm.LOG0
|
|
||||||
const maxTopics = 4
|
|
||||||
|
|
||||||
// filter is a helper struct that holds meta information over the filter type
|
|
||||||
// and associated subscription in the event system.
|
|
||||||
type filter struct {
|
|
||||||
typ Type
|
|
||||||
deadline *time.Timer // filter is inactive when deadline triggers
|
|
||||||
hashes []common.Hash
|
|
||||||
fullTx bool
|
|
||||||
txs []*types.Transaction
|
|
||||||
crit FilterCriteria
|
|
||||||
logs []*types.Log
|
|
||||||
s *Subscription // associated subscription in event system
|
|
||||||
}
|
|
||||||
|
|
||||||
// FilterAPI 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 FilterAPI struct {
|
|
||||||
sys *FilterSystem
|
|
||||||
events *EventSystem
|
|
||||||
filtersMu sync.Mutex
|
|
||||||
filters map[rpc.ID]*filter
|
|
||||||
timeout time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewFilterAPI returns a new FilterAPI instance.
|
|
||||||
func NewFilterAPI(system *FilterSystem, lightMode bool) *FilterAPI {
|
|
||||||
api := &FilterAPI{
|
|
||||||
sys: system,
|
|
||||||
events: NewEventSystem(system, lightMode),
|
|
||||||
filters: make(map[rpc.ID]*filter),
|
|
||||||
timeout: system.cfg.Timeout,
|
|
||||||
}
|
|
||||||
go api.timeoutLoop(system.cfg.Timeout)
|
|
||||||
|
|
||||||
return api
|
|
||||||
}
|
|
||||||
|
|
||||||
// timeoutLoop runs at the interval set by 'timeout' and deletes filters
|
|
||||||
// that have not been recently used. It is started when the API is created.
|
|
||||||
func (api *FilterAPI) timeoutLoop(timeout time.Duration) {
|
|
||||||
var toUninstall []*Subscription
|
|
||||||
ticker := time.NewTicker(timeout)
|
|
||||||
defer ticker.Stop()
|
|
||||||
for {
|
|
||||||
<-ticker.C
|
|
||||||
api.filtersMu.Lock()
|
|
||||||
for id, f := range api.filters {
|
|
||||||
select {
|
|
||||||
case <-f.deadline.C:
|
|
||||||
toUninstall = append(toUninstall, f.s)
|
|
||||||
delete(api.filters, id)
|
|
||||||
default:
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
api.filtersMu.Unlock()
|
|
||||||
|
|
||||||
// Unsubscribes are processed outside the lock to avoid the following scenario:
|
|
||||||
// event loop attempts broadcasting events to still active filters while
|
|
||||||
// Unsubscribe is waiting for it to process the uninstall request.
|
|
||||||
for _, s := range toUninstall {
|
|
||||||
s.Unsubscribe()
|
|
||||||
}
|
|
||||||
toUninstall = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPendingTransactionFilter creates a filter that fetches pending transactions
|
|
||||||
// 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.
|
|
||||||
func (api *FilterAPI) NewPendingTransactionFilter(fullTx *bool) rpc.ID {
|
|
||||||
var (
|
|
||||||
pendingTxs = make(chan []*types.Transaction)
|
|
||||||
pendingTxSub = api.events.SubscribePendingTxs(pendingTxs)
|
|
||||||
)
|
|
||||||
|
|
||||||
api.filtersMu.Lock()
|
|
||||||
api.filters[pendingTxSub.ID] = &filter{typ: PendingTransactionsSubscription, fullTx: fullTx != nil && *fullTx, deadline: time.NewTimer(api.timeout), txs: make([]*types.Transaction, 0), s: pendingTxSub}
|
|
||||||
api.filtersMu.Unlock()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case pTx := <-pendingTxs:
|
|
||||||
api.filtersMu.Lock()
|
|
||||||
if f, found := api.filters[pendingTxSub.ID]; found {
|
|
||||||
f.txs = append(f.txs, pTx...)
|
|
||||||
}
|
|
||||||
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. If fullTx is true the full tx is
|
|
||||||
// sent to the client, otherwise the hash is sent.
|
|
||||||
func (api *FilterAPI) NewPendingTransactions(ctx context.Context, fullTx *bool) (*rpc.Subscription, error) {
|
|
||||||
notifier, supported := rpc.NotifierFromContext(ctx)
|
|
||||||
if !supported {
|
|
||||||
return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported
|
|
||||||
}
|
|
||||||
|
|
||||||
rpcSub := notifier.CreateSubscription()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
txs := make(chan []*types.Transaction, 128)
|
|
||||||
pendingTxSub := api.events.SubscribePendingTxs(txs)
|
|
||||||
chainConfig := api.sys.backend.ChainConfig()
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case txs := <-txs:
|
|
||||||
// To keep the original behaviour, send a single tx hash in one notification.
|
|
||||||
// TODO(rjl493456442) Send a batch of tx hashes in one notification
|
|
||||||
latest := api.sys.backend.CurrentHeader()
|
|
||||||
for _, tx := range txs {
|
|
||||||
if fullTx != nil && *fullTx {
|
|
||||||
rpcTx := ethapi.NewRPCPendingTransaction(tx, latest, chainConfig)
|
|
||||||
notifier.Notify(rpcSub.ID, rpcTx)
|
|
||||||
} else {
|
|
||||||
notifier.Notify(rpcSub.ID, tx.Hash())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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.
|
|
||||||
func (api *FilterAPI) NewBlockFilter() rpc.ID {
|
|
||||||
var (
|
|
||||||
headers = make(chan *types.Header)
|
|
||||||
headerSub = api.events.SubscribeNewHeads(headers)
|
|
||||||
)
|
|
||||||
|
|
||||||
api.filtersMu.Lock()
|
|
||||||
api.filters[headerSub.ID] = &filter{typ: BlocksSubscription, deadline: time.NewTimer(api.timeout), hashes: make([]common.Hash, 0), s: headerSub}
|
|
||||||
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, h.Hash())
|
|
||||||
}
|
|
||||||
api.filtersMu.Unlock()
|
|
||||||
case <-headerSub.Err():
|
|
||||||
api.filtersMu.Lock()
|
|
||||||
delete(api.filters, headerSub.ID)
|
|
||||||
api.filtersMu.Unlock()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return headerSub.ID
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewHeads send a notification each time a new (header) block is appended to the chain.
|
|
||||||
func (api *FilterAPI) 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 *types.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
|
|
||||||
}
|
|
||||||
|
|
||||||
// Logs creates a subscription that fires for all new log that match the given filter criteria.
|
|
||||||
func (api *FilterAPI) Logs(ctx context.Context, crit 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
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case logs := <-matchedLogs:
|
|
||||||
for _, log := range logs {
|
|
||||||
log := log
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// FilterCriteria represents a request to create a new filter.
|
|
||||||
// Same as ethereum.FilterQuery but with UnmarshalJSON() method.
|
|
||||||
type FilterCriteria ethereum.FilterQuery
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
func (api *FilterAPI) NewFilter(crit FilterCriteria) (rpc.ID, error) {
|
|
||||||
logs := make(chan []*types.Log)
|
|
||||||
logsSub, err := api.events.SubscribeLogs(ethereum.FilterQuery(crit), logs)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
api.filtersMu.Lock()
|
|
||||||
api.filters[logsSub.ID] = &filter{typ: LogsSubscription, crit: crit, deadline: time.NewTimer(api.timeout), logs: make([]*types.Log, 0), s: logsSub}
|
|
||||||
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.
|
|
||||||
func (api *FilterAPI) GetLogs(ctx context.Context, crit FilterCriteria) ([]*types.Log, error) {
|
|
||||||
if len(crit.Topics) > maxTopics {
|
|
||||||
return nil, errExceedMaxTopics
|
|
||||||
}
|
|
||||||
var filter *Filter
|
|
||||||
if crit.BlockHash != nil {
|
|
||||||
// Block filter requested, construct a single-shot filter
|
|
||||||
filter = api.sys.NewBlockFilter(*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()
|
|
||||||
}
|
|
||||||
if begin > 0 && end > 0 && begin > end {
|
|
||||||
return nil, errInvalidBlockRange
|
|
||||||
}
|
|
||||||
// Construct the range filter
|
|
||||||
filter = api.sys.NewRangeFilter(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.
|
|
||||||
func (api *FilterAPI) UninstallFilter(id rpc.ID) bool {
|
|
||||||
api.filtersMu.Lock()
|
|
||||||
f, found := api.filters[id]
|
|
||||||
if found {
|
|
||||||
delete(api.filters, id)
|
|
||||||
}
|
|
||||||
api.filtersMu.Unlock()
|
|
||||||
if found {
|
|
||||||
f.s.Unsubscribe()
|
|
||||||
}
|
|
||||||
|
|
||||||
return found
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
func (api *FilterAPI) GetFilterLogs(ctx context.Context, id rpc.ID) ([]*types.Log, error) {
|
|
||||||
api.filtersMu.Lock()
|
|
||||||
f, found := api.filters[id]
|
|
||||||
api.filtersMu.Unlock()
|
|
||||||
|
|
||||||
if !found || f.typ != LogsSubscription {
|
|
||||||
return nil, errFilterNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
var filter *Filter
|
|
||||||
if f.crit.BlockHash != nil {
|
|
||||||
// Block filter requested, construct a single-shot filter
|
|
||||||
filter = api.sys.NewBlockFilter(*f.crit.BlockHash, f.crit.Addresses, f.crit.Topics)
|
|
||||||
} 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 = api.sys.NewRangeFilter(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
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
func (api *FilterAPI) GetFilterChanges(id rpc.ID) (interface{}, error) {
|
|
||||||
api.filtersMu.Lock()
|
|
||||||
defer api.filtersMu.Unlock()
|
|
||||||
|
|
||||||
chainConfig := api.sys.backend.ChainConfig()
|
|
||||||
latest := api.sys.backend.CurrentHeader()
|
|
||||||
|
|
||||||
if f, found := api.filters[id]; found {
|
|
||||||
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(api.timeout)
|
|
||||||
|
|
||||||
switch f.typ {
|
|
||||||
case BlocksSubscription:
|
|
||||||
hashes := f.hashes
|
|
||||||
f.hashes = nil
|
|
||||||
return returnHashes(hashes), nil
|
|
||||||
case PendingTransactionsSubscription:
|
|
||||||
if f.fullTx {
|
|
||||||
txs := make([]*ethapi.RPCTransaction, 0, len(f.txs))
|
|
||||||
for _, tx := range f.txs {
|
|
||||||
txs = append(txs, ethapi.NewRPCPendingTransaction(tx, latest, chainConfig))
|
|
||||||
}
|
|
||||||
f.txs = nil
|
|
||||||
return txs, nil
|
|
||||||
} else {
|
|
||||||
hashes := make([]common.Hash, 0, len(f.txs))
|
|
||||||
for _, tx := range f.txs {
|
|
||||||
hashes = append(hashes, tx.Hash())
|
|
||||||
}
|
|
||||||
f.txs = nil
|
|
||||||
return hashes, nil
|
|
||||||
}
|
|
||||||
case LogsSubscription, MinedAndPendingLogsSubscription:
|
|
||||||
logs := f.logs
|
|
||||||
f.logs = nil
|
|
||||||
return returnLogs(logs), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return []interface{}{}, errFilterNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON sets *args fields with given data.
|
|
||||||
func (args *FilterCriteria) UnmarshalJSON(data []byte) error {
|
|
||||||
type input struct {
|
|
||||||
BlockHash *common.Hash `json:"blockHash"`
|
|
||||||
FromBlock *rpc.BlockNumber `json:"fromBlock"`
|
|
||||||
ToBlock *rpc.BlockNumber `json:"toBlock"`
|
|
||||||
Addresses interface{} `json:"address"`
|
|
||||||
Topics []interface{} `json:"topics"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var raw input
|
|
||||||
if err := json.Unmarshal(data, &raw); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if raw.BlockHash != nil {
|
|
||||||
if raw.FromBlock != nil || raw.ToBlock != nil {
|
|
||||||
// BlockHash is mutually exclusive with FromBlock/ToBlock criteria
|
|
||||||
return errors.New("cannot specify both BlockHash and FromBlock/ToBlock, choose one or the other")
|
|
||||||
}
|
|
||||||
args.BlockHash = raw.BlockHash
|
|
||||||
} else {
|
|
||||||
if raw.FromBlock != nil {
|
|
||||||
args.FromBlock = big.NewInt(raw.FromBlock.Int64())
|
|
||||||
}
|
|
||||||
|
|
||||||
if raw.ToBlock != nil {
|
|
||||||
args.ToBlock = big.NewInt(raw.ToBlock.Int64())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
args.Addresses = []common.Address{}
|
|
||||||
|
|
||||||
if raw.Addresses != nil {
|
|
||||||
// raw.Address can contain a single address or an array of addresses
|
|
||||||
switch rawAddr := raw.Addresses.(type) {
|
|
||||||
case []interface{}:
|
|
||||||
for i, addr := range rawAddr {
|
|
||||||
if strAddr, ok := addr.(string); ok {
|
|
||||||
addr, err := decodeAddress(strAddr)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid address at index %d: %v", i, err)
|
|
||||||
}
|
|
||||||
args.Addresses = append(args.Addresses, addr)
|
|
||||||
} else {
|
|
||||||
return fmt.Errorf("non-string address at index %d", i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case string:
|
|
||||||
addr, err := decodeAddress(rawAddr)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid address: %v", err)
|
|
||||||
}
|
|
||||||
args.Addresses = []common.Address{addr}
|
|
||||||
default:
|
|
||||||
return errors.New("invalid addresses in query")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// topics is an array consisting of strings and/or arrays of strings.
|
|
||||||
// JSON null values are converted to common.Hash{} and ignored by the filter manager.
|
|
||||||
if len(raw.Topics) > 0 {
|
|
||||||
args.Topics = make([][]common.Hash, len(raw.Topics))
|
|
||||||
for i, t := range raw.Topics {
|
|
||||||
switch topic := t.(type) {
|
|
||||||
case nil:
|
|
||||||
// ignore topic when matching logs
|
|
||||||
|
|
||||||
case string:
|
|
||||||
// match specific topic
|
|
||||||
top, err := decodeTopic(topic)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
args.Topics[i] = []common.Hash{top}
|
|
||||||
|
|
||||||
case []interface{}:
|
|
||||||
// or case e.g. [null, "topic0", "topic1"]
|
|
||||||
for _, rawTopic := range topic {
|
|
||||||
if rawTopic == nil {
|
|
||||||
// null component, match all
|
|
||||||
args.Topics[i] = nil
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if topic, ok := rawTopic.(string); ok {
|
|
||||||
parsed, err := decodeTopic(topic)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
args.Topics[i] = append(args.Topics[i], parsed)
|
|
||||||
} else {
|
|
||||||
return errInvalidTopic
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return errInvalidTopic
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func decodeAddress(s string) (common.Address, error) {
|
|
||||||
b, err := hexutil.Decode(s)
|
|
||||||
if err == nil && len(b) != common.AddressLength {
|
|
||||||
err = fmt.Errorf("hex has invalid length %d after decoding; expected %d for address", len(b), common.AddressLength)
|
|
||||||
}
|
|
||||||
return common.BytesToAddress(b), err
|
|
||||||
}
|
|
||||||
|
|
||||||
func decodeTopic(s string) (common.Hash, error) {
|
|
||||||
b, err := hexutil.Decode(s)
|
|
||||||
if err == nil && len(b) != common.HashLength {
|
|
||||||
err = fmt.Errorf("hex has invalid length %d after decoding; expected %d for topic", len(b), common.HashLength)
|
|
||||||
}
|
|
||||||
return common.BytesToHash(b), err
|
|
||||||
}
|
|
||||||
|
|
@ -1,185 +0,0 @@
|
||||||
// Copyright 2016 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package filters
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestUnmarshalJSONNewFilterArgs(t *testing.T) {
|
|
||||||
var (
|
|
||||||
fromBlock rpc.BlockNumber = 0x123435
|
|
||||||
toBlock rpc.BlockNumber = 0xabcdef
|
|
||||||
address0 = common.HexToAddress("70c87d191324e6712a591f304b4eedef6ad9bb9d")
|
|
||||||
address1 = common.HexToAddress("9b2055d370f73ec7d8a03e965129118dc8f5bf83")
|
|
||||||
topic0 = common.HexToHash("3ac225168df54212a25c1c01fd35bebfea408fdac2e31ddd6f80a4bbf9a5f1ca")
|
|
||||||
topic1 = common.HexToHash("9084a792d2f8b16a62b882fd56f7860c07bf5fa91dd8a2ae7e809e5180fef0b3")
|
|
||||||
topic2 = common.HexToHash("6ccae1c4af4152f460ff510e573399795dfab5dcf1fa60d1f33ac8fdc1e480ce")
|
|
||||||
)
|
|
||||||
|
|
||||||
// default values
|
|
||||||
var test0 FilterCriteria
|
|
||||||
if err := json.Unmarshal([]byte("{}"), &test0); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if test0.FromBlock != nil {
|
|
||||||
t.Fatalf("expected nil, got %d", test0.FromBlock)
|
|
||||||
}
|
|
||||||
if test0.ToBlock != nil {
|
|
||||||
t.Fatalf("expected nil, got %d", test0.ToBlock)
|
|
||||||
}
|
|
||||||
if len(test0.Addresses) != 0 {
|
|
||||||
t.Fatalf("expected 0 addresses, got %d", len(test0.Addresses))
|
|
||||||
}
|
|
||||||
if len(test0.Topics) != 0 {
|
|
||||||
t.Fatalf("expected 0 topics, got %d topics", len(test0.Topics))
|
|
||||||
}
|
|
||||||
|
|
||||||
// from, to block number
|
|
||||||
var test1 FilterCriteria
|
|
||||||
vector := fmt.Sprintf(`{"fromBlock":"%v","toBlock":"%v"}`, fromBlock, toBlock)
|
|
||||||
if err := json.Unmarshal([]byte(vector), &test1); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if test1.FromBlock.Int64() != fromBlock.Int64() {
|
|
||||||
t.Fatalf("expected FromBlock %d, got %d", fromBlock, test1.FromBlock)
|
|
||||||
}
|
|
||||||
if test1.ToBlock.Int64() != toBlock.Int64() {
|
|
||||||
t.Fatalf("expected ToBlock %d, got %d", toBlock, test1.ToBlock)
|
|
||||||
}
|
|
||||||
|
|
||||||
// single address
|
|
||||||
var test2 FilterCriteria
|
|
||||||
vector = fmt.Sprintf(`{"address": "%s"}`, address0.Hex())
|
|
||||||
if err := json.Unmarshal([]byte(vector), &test2); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if len(test2.Addresses) != 1 {
|
|
||||||
t.Fatalf("expected 1 address, got %d address(es)", len(test2.Addresses))
|
|
||||||
}
|
|
||||||
if test2.Addresses[0] != address0 {
|
|
||||||
t.Fatalf("expected address %x, got %x", address0, test2.Addresses[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
// multiple address
|
|
||||||
var test3 FilterCriteria
|
|
||||||
vector = fmt.Sprintf(`{"address": ["%s", "%s"]}`, address0.Hex(), address1.Hex())
|
|
||||||
if err := json.Unmarshal([]byte(vector), &test3); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if len(test3.Addresses) != 2 {
|
|
||||||
t.Fatalf("expected 2 addresses, got %d address(es)", len(test3.Addresses))
|
|
||||||
}
|
|
||||||
if test3.Addresses[0] != address0 {
|
|
||||||
t.Fatalf("expected address %x, got %x", address0, test3.Addresses[0])
|
|
||||||
}
|
|
||||||
if test3.Addresses[1] != address1 {
|
|
||||||
t.Fatalf("expected address %x, got %x", address1, test3.Addresses[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
// single topic
|
|
||||||
var test4 FilterCriteria
|
|
||||||
vector = fmt.Sprintf(`{"topics": ["%s"]}`, topic0.Hex())
|
|
||||||
if err := json.Unmarshal([]byte(vector), &test4); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if len(test4.Topics) != 1 {
|
|
||||||
t.Fatalf("expected 1 topic, got %d", len(test4.Topics))
|
|
||||||
}
|
|
||||||
if len(test4.Topics[0]) != 1 {
|
|
||||||
t.Fatalf("expected len(topics[0]) to be 1, got %d", len(test4.Topics[0]))
|
|
||||||
}
|
|
||||||
if test4.Topics[0][0] != topic0 {
|
|
||||||
t.Fatalf("got %x, expected %x", test4.Topics[0][0], topic0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// test multiple "AND" topics
|
|
||||||
var test5 FilterCriteria
|
|
||||||
vector = fmt.Sprintf(`{"topics": ["%s", "%s"]}`, topic0.Hex(), topic1.Hex())
|
|
||||||
if err := json.Unmarshal([]byte(vector), &test5); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if len(test5.Topics) != 2 {
|
|
||||||
t.Fatalf("expected 2 topics, got %d", len(test5.Topics))
|
|
||||||
}
|
|
||||||
if len(test5.Topics[0]) != 1 {
|
|
||||||
t.Fatalf("expected 1 topic, got %d", len(test5.Topics[0]))
|
|
||||||
}
|
|
||||||
if test5.Topics[0][0] != topic0 {
|
|
||||||
t.Fatalf("got %x, expected %x", test5.Topics[0][0], topic0)
|
|
||||||
}
|
|
||||||
if len(test5.Topics[1]) != 1 {
|
|
||||||
t.Fatalf("expected 1 topic, got %d", len(test5.Topics[1]))
|
|
||||||
}
|
|
||||||
if test5.Topics[1][0] != topic1 {
|
|
||||||
t.Fatalf("got %x, expected %x", test5.Topics[1][0], topic1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// test optional topic
|
|
||||||
var test6 FilterCriteria
|
|
||||||
vector = fmt.Sprintf(`{"topics": ["%s", null, "%s"]}`, topic0.Hex(), topic2.Hex())
|
|
||||||
if err := json.Unmarshal([]byte(vector), &test6); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if len(test6.Topics) != 3 {
|
|
||||||
t.Fatalf("expected 3 topics, got %d", len(test6.Topics))
|
|
||||||
}
|
|
||||||
if len(test6.Topics[0]) != 1 {
|
|
||||||
t.Fatalf("expected 1 topic, got %d", len(test6.Topics[0]))
|
|
||||||
}
|
|
||||||
if test6.Topics[0][0] != topic0 {
|
|
||||||
t.Fatalf("got %x, expected %x", test6.Topics[0][0], topic0)
|
|
||||||
}
|
|
||||||
if len(test6.Topics[1]) != 0 {
|
|
||||||
t.Fatalf("expected 0 topic, got %d", len(test6.Topics[1]))
|
|
||||||
}
|
|
||||||
if len(test6.Topics[2]) != 1 {
|
|
||||||
t.Fatalf("expected 1 topic, got %d", len(test6.Topics[2]))
|
|
||||||
}
|
|
||||||
if test6.Topics[2][0] != topic2 {
|
|
||||||
t.Fatalf("got %x, expected %x", test6.Topics[2][0], topic2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// test OR topics
|
|
||||||
var test7 FilterCriteria
|
|
||||||
vector = fmt.Sprintf(`{"topics": [["%s", "%s"], null, ["%s", null]]}`, topic0.Hex(), topic1.Hex(), topic2.Hex())
|
|
||||||
if err := json.Unmarshal([]byte(vector), &test7); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if len(test7.Topics) != 3 {
|
|
||||||
t.Fatalf("expected 3 topics, got %d topics", len(test7.Topics))
|
|
||||||
}
|
|
||||||
if len(test7.Topics[0]) != 2 {
|
|
||||||
t.Fatalf("expected 2 topics, got %d topics", len(test7.Topics[0]))
|
|
||||||
}
|
|
||||||
if test7.Topics[0][0] != topic0 || test7.Topics[0][1] != topic1 {
|
|
||||||
t.Fatalf("invalid topics expected [%x,%x], got [%x,%x]",
|
|
||||||
topic0, topic1, test7.Topics[0][0], test7.Topics[0][1],
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if len(test7.Topics[1]) != 0 {
|
|
||||||
t.Fatalf("expected 0 topic, got %d topics", len(test7.Topics[1]))
|
|
||||||
}
|
|
||||||
if len(test7.Topics[2]) != 0 {
|
|
||||||
t.Fatalf("expected 0 topics, got %d topics", len(test7.Topics[2]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,189 +0,0 @@
|
||||||
// Copyright 2017 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package filters
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/common/bitutil"
|
|
||||||
"github.com/ethereum/go-ethereum/core/bloombits"
|
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
|
||||||
"github.com/ethereum/go-ethereum/node"
|
|
||||||
)
|
|
||||||
|
|
||||||
func BenchmarkBloomBits512(b *testing.B) {
|
|
||||||
benchmarkBloomBits(b, 512)
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkBloomBits1k(b *testing.B) {
|
|
||||||
benchmarkBloomBits(b, 1024)
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkBloomBits2k(b *testing.B) {
|
|
||||||
benchmarkBloomBits(b, 2048)
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkBloomBits4k(b *testing.B) {
|
|
||||||
benchmarkBloomBits(b, 4096)
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkBloomBits8k(b *testing.B) {
|
|
||||||
benchmarkBloomBits(b, 8192)
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkBloomBits16k(b *testing.B) {
|
|
||||||
benchmarkBloomBits(b, 16384)
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkBloomBits32k(b *testing.B) {
|
|
||||||
benchmarkBloomBits(b, 32768)
|
|
||||||
}
|
|
||||||
|
|
||||||
const benchFilterCnt = 2000
|
|
||||||
|
|
||||||
func benchmarkBloomBits(b *testing.B, sectionSize uint64) {
|
|
||||||
b.Skip("test disabled: this tests presume (and modify) an existing datadir.")
|
|
||||||
benchDataDir := node.DefaultDataDir() + "/geth/chaindata"
|
|
||||||
b.Log("Running bloombits benchmark section size:", sectionSize)
|
|
||||||
|
|
||||||
db, err := rawdb.NewLevelDBDatabase(benchDataDir, 128, 1024, "", false)
|
|
||||||
if err != nil {
|
|
||||||
b.Fatalf("error opening database at %v: %v", benchDataDir, err)
|
|
||||||
}
|
|
||||||
head := rawdb.ReadHeadBlockHash(db)
|
|
||||||
if head == (common.Hash{}) {
|
|
||||||
b.Fatalf("chain data not found at %v", benchDataDir)
|
|
||||||
}
|
|
||||||
|
|
||||||
clearBloomBits(db)
|
|
||||||
b.Log("Generating bloombits data...")
|
|
||||||
headNum := rawdb.ReadHeaderNumber(db, head)
|
|
||||||
if headNum == nil || *headNum < sectionSize+512 {
|
|
||||||
b.Fatalf("not enough blocks for running a benchmark")
|
|
||||||
}
|
|
||||||
|
|
||||||
start := time.Now()
|
|
||||||
cnt := (*headNum - 512) / sectionSize
|
|
||||||
var dataSize, compSize uint64
|
|
||||||
for sectionIdx := uint64(0); sectionIdx < cnt; sectionIdx++ {
|
|
||||||
bc, err := bloombits.NewGenerator(uint(sectionSize))
|
|
||||||
if err != nil {
|
|
||||||
b.Fatalf("failed to create generator: %v", err)
|
|
||||||
}
|
|
||||||
var header *types.Header
|
|
||||||
for i := sectionIdx * sectionSize; i < (sectionIdx+1)*sectionSize; i++ {
|
|
||||||
hash := rawdb.ReadCanonicalHash(db, i)
|
|
||||||
if header = rawdb.ReadHeader(db, hash, i); header == nil {
|
|
||||||
b.Fatalf("Error creating bloomBits data")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
bc.AddBloom(uint(i-sectionIdx*sectionSize), header.Bloom)
|
|
||||||
}
|
|
||||||
sectionHead := rawdb.ReadCanonicalHash(db, (sectionIdx+1)*sectionSize-1)
|
|
||||||
for i := 0; i < types.BloomBitLength; i++ {
|
|
||||||
data, err := bc.Bitset(uint(i))
|
|
||||||
if err != nil {
|
|
||||||
b.Fatalf("failed to retrieve bitset: %v", err)
|
|
||||||
}
|
|
||||||
comp := bitutil.CompressBytes(data)
|
|
||||||
dataSize += uint64(len(data))
|
|
||||||
compSize += uint64(len(comp))
|
|
||||||
rawdb.WriteBloomBits(db, uint(i), sectionIdx, sectionHead, comp)
|
|
||||||
}
|
|
||||||
//if sectionIdx%50 == 0 {
|
|
||||||
// b.Log(" section", sectionIdx, "/", cnt)
|
|
||||||
//}
|
|
||||||
}
|
|
||||||
|
|
||||||
d := time.Since(start)
|
|
||||||
b.Log("Finished generating bloombits data")
|
|
||||||
b.Log(" ", d, "total ", d/time.Duration(cnt*sectionSize), "per block")
|
|
||||||
b.Log(" data size:", dataSize, " compressed size:", compSize, " compression ratio:", float64(compSize)/float64(dataSize))
|
|
||||||
|
|
||||||
b.Log("Running filter benchmarks...")
|
|
||||||
start = time.Now()
|
|
||||||
|
|
||||||
var (
|
|
||||||
backend *testBackend
|
|
||||||
sys *FilterSystem
|
|
||||||
)
|
|
||||||
for i := 0; i < benchFilterCnt; i++ {
|
|
||||||
if i%20 == 0 {
|
|
||||||
db.Close()
|
|
||||||
db, _ = rawdb.NewLevelDBDatabase(benchDataDir, 128, 1024, "", false)
|
|
||||||
backend = &testBackend{db: db, sections: cnt}
|
|
||||||
sys = NewFilterSystem(backend, Config{})
|
|
||||||
}
|
|
||||||
var addr common.Address
|
|
||||||
addr[0] = byte(i)
|
|
||||||
addr[1] = byte(i / 256)
|
|
||||||
filter := sys.NewRangeFilter(0, int64(cnt*sectionSize-1), []common.Address{addr}, nil)
|
|
||||||
if _, err := filter.Logs(context.Background()); err != nil {
|
|
||||||
b.Error("filter.Logs error:", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
d = time.Since(start)
|
|
||||||
b.Log("Finished running filter benchmarks")
|
|
||||||
b.Log(" ", d, "total ", d/time.Duration(benchFilterCnt), "per address", d*time.Duration(1000000)/time.Duration(benchFilterCnt*cnt*sectionSize), "per million blocks")
|
|
||||||
db.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
//nolint:unused
|
|
||||||
func clearBloomBits(db ethdb.Database) {
|
|
||||||
var bloomBitsPrefix = []byte("bloomBits-")
|
|
||||||
fmt.Println("Clearing bloombits data...")
|
|
||||||
it := db.NewIterator(bloomBitsPrefix, nil)
|
|
||||||
for it.Next() {
|
|
||||||
db.Delete(it.Key())
|
|
||||||
}
|
|
||||||
it.Release()
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkNoBloomBits(b *testing.B) {
|
|
||||||
b.Skip("test disabled: this tests presume (and modify) an existing datadir.")
|
|
||||||
benchDataDir := node.DefaultDataDir() + "/geth/chaindata"
|
|
||||||
b.Log("Running benchmark without bloombits")
|
|
||||||
db, err := rawdb.NewLevelDBDatabase(benchDataDir, 128, 1024, "", false)
|
|
||||||
if err != nil {
|
|
||||||
b.Fatalf("error opening database at %v: %v", benchDataDir, err)
|
|
||||||
}
|
|
||||||
head := rawdb.ReadHeadBlockHash(db)
|
|
||||||
if head == (common.Hash{}) {
|
|
||||||
b.Fatalf("chain data not found at %v", benchDataDir)
|
|
||||||
}
|
|
||||||
headNum := rawdb.ReadHeaderNumber(db, head)
|
|
||||||
|
|
||||||
clearBloomBits(db)
|
|
||||||
|
|
||||||
_, sys := newTestFilterSystem(b, db, Config{})
|
|
||||||
|
|
||||||
b.Log("Running filter benchmarks...")
|
|
||||||
start := time.Now()
|
|
||||||
filter := sys.NewRangeFilter(0, int64(*headNum), []common.Address{{}}, nil)
|
|
||||||
filter.Logs(context.Background())
|
|
||||||
d := time.Since(start)
|
|
||||||
b.Log("Finished running filter benchmarks")
|
|
||||||
b.Log(" ", d, "total ", d*time.Duration(1000000)/time.Duration(*headNum+1), "per million blocks")
|
|
||||||
db.Close()
|
|
||||||
}
|
|
||||||
|
|
@ -1,422 +0,0 @@
|
||||||
// Copyright 2014 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package filters
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"math/big"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/core/bloombits"
|
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Filter can be used to retrieve and filter logs.
|
|
||||||
type Filter struct {
|
|
||||||
sys *FilterSystem
|
|
||||||
|
|
||||||
addresses []common.Address
|
|
||||||
topics [][]common.Hash
|
|
||||||
|
|
||||||
block *common.Hash // Block hash if filtering a single block
|
|
||||||
begin, end int64 // Range interval if filtering multiple blocks
|
|
||||||
|
|
||||||
matcher *bloombits.Matcher
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewRangeFilter creates a new filter which uses a bloom filter on blocks to
|
|
||||||
// figure out whether a particular block is interesting or not.
|
|
||||||
func (sys *FilterSystem) NewRangeFilter(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 filters [][][]byte
|
|
||||||
if len(addresses) > 0 {
|
|
||||||
filter := make([][]byte, len(addresses))
|
|
||||||
for i, address := range addresses {
|
|
||||||
filter[i] = address.Bytes()
|
|
||||||
}
|
|
||||||
filters = append(filters, filter)
|
|
||||||
}
|
|
||||||
for _, topicList := range topics {
|
|
||||||
filter := make([][]byte, len(topicList))
|
|
||||||
for i, topic := range topicList {
|
|
||||||
filter[i] = topic.Bytes()
|
|
||||||
}
|
|
||||||
filters = append(filters, filter)
|
|
||||||
}
|
|
||||||
size, _ := sys.backend.BloomStatus()
|
|
||||||
|
|
||||||
// Create a generic filter and convert it into a range filter
|
|
||||||
filter := newFilter(sys, addresses, topics)
|
|
||||||
|
|
||||||
filter.matcher = bloombits.NewMatcher(size, filters)
|
|
||||||
filter.begin = begin
|
|
||||||
filter.end = end
|
|
||||||
|
|
||||||
return filter
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewBlockFilter creates a new filter which directly inspects the contents of
|
|
||||||
// a block to figure out whether it is interesting or not.
|
|
||||||
func (sys *FilterSystem) NewBlockFilter(block common.Hash, addresses []common.Address, topics [][]common.Hash) *Filter {
|
|
||||||
// Create a generic filter and convert it into a block filter
|
|
||||||
filter := newFilter(sys, addresses, topics)
|
|
||||||
filter.block = &block
|
|
||||||
return filter
|
|
||||||
}
|
|
||||||
|
|
||||||
// newFilter creates a generic filter that can either filter based on a block hash,
|
|
||||||
// or based on range queries. The search criteria needs to be explicitly set.
|
|
||||||
func newFilter(sys *FilterSystem, addresses []common.Address, topics [][]common.Hash) *Filter {
|
|
||||||
return &Filter{
|
|
||||||
sys: sys,
|
|
||||||
addresses: addresses,
|
|
||||||
topics: topics,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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(ctx context.Context) ([]*types.Log, error) {
|
|
||||||
// If we're doing singleton block filtering, execute and return
|
|
||||||
if f.block != nil {
|
|
||||||
header, err := f.sys.backend.HeaderByHash(ctx, *f.block)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if header == nil {
|
|
||||||
return nil, errors.New("unknown block")
|
|
||||||
}
|
|
||||||
return f.blockLogs(ctx, header)
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
beginPending = f.begin == rpc.PendingBlockNumber.Int64()
|
|
||||||
endPending = f.end == rpc.PendingBlockNumber.Int64()
|
|
||||||
)
|
|
||||||
|
|
||||||
// special case for pending logs
|
|
||||||
if beginPending && !endPending {
|
|
||||||
return nil, errInvalidBlockRange
|
|
||||||
}
|
|
||||||
|
|
||||||
// Short-cut if all we care about is pending logs
|
|
||||||
if beginPending && endPending {
|
|
||||||
return f.pendingLogs(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
resolveSpecial := func(number int64) (int64, error) {
|
|
||||||
var hdr *types.Header
|
|
||||||
switch number {
|
|
||||||
case rpc.LatestBlockNumber.Int64(), rpc.PendingBlockNumber.Int64():
|
|
||||||
// we should return head here since we've already captured
|
|
||||||
// that we need to get the pending logs in the pending boolean above
|
|
||||||
hdr, _ = f.sys.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber)
|
|
||||||
if hdr == nil {
|
|
||||||
return 0, errors.New("latest header not found")
|
|
||||||
}
|
|
||||||
case rpc.FinalizedBlockNumber.Int64():
|
|
||||||
hdr, _ = f.sys.backend.HeaderByNumber(ctx, rpc.FinalizedBlockNumber)
|
|
||||||
if hdr == nil {
|
|
||||||
return 0, errors.New("finalized header not found")
|
|
||||||
}
|
|
||||||
case rpc.SafeBlockNumber.Int64():
|
|
||||||
hdr, _ = f.sys.backend.HeaderByNumber(ctx, rpc.SafeBlockNumber)
|
|
||||||
if hdr == nil {
|
|
||||||
return 0, errors.New("safe header not found")
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return number, nil
|
|
||||||
}
|
|
||||||
return hdr.Number.Int64(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
// range query need to resolve the special begin/end block number
|
|
||||||
if f.begin, err = resolveSpecial(f.begin); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if f.end, err = resolveSpecial(f.end); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
logChan, errChan := f.rangeLogsAsync(ctx)
|
|
||||||
var logs []*types.Log
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case log := <-logChan:
|
|
||||||
logs = append(logs, log)
|
|
||||||
case err := <-errChan:
|
|
||||||
if err != nil {
|
|
||||||
// if an error occurs during extraction, we do return the extracted data
|
|
||||||
return logs, err
|
|
||||||
}
|
|
||||||
// Append the pending ones
|
|
||||||
if endPending {
|
|
||||||
pendingLogs := f.pendingLogs()
|
|
||||||
logs = append(logs, pendingLogs...)
|
|
||||||
}
|
|
||||||
return logs, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// rangeLogsAsync retrieves block-range logs that match the filter criteria asynchronously,
|
|
||||||
// it creates and returns two channels: one for delivering log data, and one for reporting errors.
|
|
||||||
func (f *Filter) rangeLogsAsync(ctx context.Context) (chan *types.Log, chan error) {
|
|
||||||
var (
|
|
||||||
logChan = make(chan *types.Log)
|
|
||||||
errChan = make(chan error)
|
|
||||||
)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer func() {
|
|
||||||
close(errChan)
|
|
||||||
close(logChan)
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Gather all indexed logs, and finish with non indexed ones
|
|
||||||
var (
|
|
||||||
end = uint64(f.end)
|
|
||||||
size, sections = f.sys.backend.BloomStatus()
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
if indexed := sections * size; indexed > uint64(f.begin) {
|
|
||||||
if indexed > end {
|
|
||||||
indexed = end + 1
|
|
||||||
}
|
|
||||||
if err = f.indexedLogs(ctx, indexed-1, logChan); err != nil {
|
|
||||||
errChan <- err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := f.unindexedLogs(ctx, end, logChan); err != nil {
|
|
||||||
errChan <- err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
errChan <- nil
|
|
||||||
}()
|
|
||||||
|
|
||||||
return logChan, errChan
|
|
||||||
}
|
|
||||||
|
|
||||||
// indexedLogs returns the logs matching the filter criteria based on the bloom
|
|
||||||
// bits indexed available locally or via the network.
|
|
||||||
func (f *Filter) indexedLogs(ctx context.Context, end uint64, logChan chan *types.Log) error {
|
|
||||||
// Create a matcher session and request servicing from the backend
|
|
||||||
matches := make(chan uint64, 64)
|
|
||||||
|
|
||||||
session, err := f.matcher.Start(ctx, uint64(f.begin), end, matches)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer session.Close()
|
|
||||||
|
|
||||||
f.sys.backend.ServiceFilter(ctx, session)
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case number, ok := <-matches:
|
|
||||||
// Abort if all matches have been fulfilled
|
|
||||||
if !ok {
|
|
||||||
err := session.Error()
|
|
||||||
if err == nil {
|
|
||||||
f.begin = int64(end) + 1
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
f.begin = int64(number) + 1
|
|
||||||
|
|
||||||
// Retrieve the suggested block and pull any truly matching logs
|
|
||||||
header, err := f.sys.backend.HeaderByNumber(ctx, rpc.BlockNumber(number))
|
|
||||||
if header == nil || err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
found, err := f.checkMatches(ctx, header)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, log := range found {
|
|
||||||
logChan <- log
|
|
||||||
}
|
|
||||||
|
|
||||||
case <-ctx.Done():
|
|
||||||
return ctx.Err()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// unindexedLogs returns the logs matching the filter criteria based on raw block
|
|
||||||
// iteration and bloom matching.
|
|
||||||
func (f *Filter) unindexedLogs(ctx context.Context, end uint64, logChan chan *types.Log) error {
|
|
||||||
for ; f.begin <= int64(end); f.begin++ {
|
|
||||||
header, err := f.sys.backend.HeaderByNumber(ctx, rpc.BlockNumber(f.begin))
|
|
||||||
if header == nil || err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
found, err := f.blockLogs(ctx, header)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, log := range found {
|
|
||||||
select {
|
|
||||||
case logChan <- log:
|
|
||||||
case <-ctx.Done():
|
|
||||||
return ctx.Err()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// blockLogs returns the logs matching the filter criteria within a single block.
|
|
||||||
func (f *Filter) blockLogs(ctx context.Context, header *types.Header) ([]*types.Log, error) {
|
|
||||||
if bloomFilter(header.Bloom, f.addresses, f.topics) {
|
|
||||||
return f.checkMatches(ctx, header)
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkMatches checks if the receipts belonging to the given header contain any log events that
|
|
||||||
// match the filter criteria. This function is called when the bloom filter signals a potential match.
|
|
||||||
// skipFilter signals all logs of the given block are requested.
|
|
||||||
func (f *Filter) checkMatches(ctx context.Context, header *types.Header) ([]*types.Log, error) {
|
|
||||||
hash := header.Hash()
|
|
||||||
// Logs in cache are partially filled with context data
|
|
||||||
// such as tx index, block hash, etc.
|
|
||||||
// Notably tx hash is NOT filled in because it needs
|
|
||||||
// access to block body data.
|
|
||||||
cached, err := f.sys.cachedLogElem(ctx, hash, header.Number.Uint64())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
logs := filterLogs(cached.logs, nil, nil, f.addresses, f.topics)
|
|
||||||
if len(logs) == 0 {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
// Most backends will deliver un-derived logs, but check nevertheless.
|
|
||||||
if len(logs) > 0 && logs[0].TxHash != (common.Hash{}) {
|
|
||||||
return logs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
body, err := f.sys.cachedGetBody(ctx, cached, hash, header.Number.Uint64())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for i, log := range logs {
|
|
||||||
// Copy log not to modify cache elements
|
|
||||||
logcopy := *log
|
|
||||||
logcopy.TxHash = body.Transactions[logcopy.TxIndex].Hash()
|
|
||||||
logs[i] = &logcopy
|
|
||||||
}
|
|
||||||
return logs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// pendingLogs returns the logs matching the filter criteria within the pending block.
|
|
||||||
func (f *Filter) pendingLogs() []*types.Log {
|
|
||||||
block, receipts := f.sys.backend.PendingBlockAndReceipts()
|
|
||||||
if block == nil || receipts == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if bloomFilter(block.Bloom(), f.addresses, f.topics) {
|
|
||||||
var unfiltered []*types.Log
|
|
||||||
for _, r := range receipts {
|
|
||||||
unfiltered = append(unfiltered, r.Logs...)
|
|
||||||
}
|
|
||||||
return filterLogs(unfiltered, nil, nil, f.addresses, f.topics)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// includes returns true if the element is present in the list.
|
|
||||||
func includes[T comparable](things []T, element T) bool {
|
|
||||||
for _, thing := range things {
|
|
||||||
if thing == element {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// filterLogs creates a slice of logs matching the given criteria.
|
|
||||||
func filterLogs(logs []*types.Log, fromBlock, toBlock *big.Int, addresses []common.Address, topics [][]common.Hash) []*types.Log {
|
|
||||||
var check = func(log *types.Log) bool {
|
|
||||||
if fromBlock != nil && fromBlock.Int64() >= 0 && fromBlock.Uint64() > log.BlockNumber {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if toBlock != nil && toBlock.Int64() >= 0 && toBlock.Uint64() < log.BlockNumber {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if len(addresses) > 0 && !includes(addresses, log.Address) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// If the to filtered topics is greater than the amount of topics in logs, skip.
|
|
||||||
if len(topics) > len(log.Topics) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for i, sub := range topics {
|
|
||||||
if len(sub) == 0 {
|
|
||||||
continue // empty rule set == wildcard
|
|
||||||
}
|
|
||||||
if !includes(sub, log.Topics[i]) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
var ret []*types.Log
|
|
||||||
for _, log := range logs {
|
|
||||||
if check(log) {
|
|
||||||
ret = append(ret, log)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func bloomFilter(bloom types.Bloom, addresses []common.Address, topics [][]common.Hash) bool {
|
|
||||||
if len(addresses) > 0 {
|
|
||||||
var included bool
|
|
||||||
for _, addr := range addresses {
|
|
||||||
if types.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 types.BloomLookup(bloom, topic) {
|
|
||||||
included = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !included {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
@ -1,606 +0,0 @@
|
||||||
// Copyright 2015 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
// Package filters implements an ethereum filtering system for block,
|
|
||||||
// transactions and log events.
|
|
||||||
package filters
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum"
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/common/lru"
|
|
||||||
"github.com/ethereum/go-ethereum/core"
|
|
||||||
"github.com/ethereum/go-ethereum/core/bloombits"
|
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
|
||||||
"github.com/ethereum/go-ethereum/event"
|
|
||||||
"github.com/ethereum/go-ethereum/log"
|
|
||||||
"github.com/ethereum/go-ethereum/params"
|
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Config represents the configuration of the filter system.
|
|
||||||
type Config struct {
|
|
||||||
LogCacheSize int // maximum number of cached blocks (default: 32)
|
|
||||||
Timeout time.Duration // how long filters stay active (default: 5min)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg Config) withDefaults() Config {
|
|
||||||
if cfg.Timeout == 0 {
|
|
||||||
cfg.Timeout = 5 * time.Minute
|
|
||||||
}
|
|
||||||
if cfg.LogCacheSize == 0 {
|
|
||||||
cfg.LogCacheSize = 32
|
|
||||||
}
|
|
||||||
return cfg
|
|
||||||
}
|
|
||||||
|
|
||||||
type Backend interface {
|
|
||||||
ChainDb() ethdb.Database
|
|
||||||
HeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Header, error)
|
|
||||||
HeaderByHash(ctx context.Context, blockHash common.Hash) (*types.Header, error)
|
|
||||||
GetBody(ctx context.Context, hash common.Hash, number rpc.BlockNumber) (*types.Body, error)
|
|
||||||
GetReceipts(ctx context.Context, blockHash common.Hash) (types.Receipts, error)
|
|
||||||
GetLogs(ctx context.Context, blockHash common.Hash, number uint64) ([][]*types.Log, error)
|
|
||||||
PendingBlockAndReceipts() (*types.Block, types.Receipts)
|
|
||||||
|
|
||||||
CurrentHeader() *types.Header
|
|
||||||
ChainConfig() *params.ChainConfig
|
|
||||||
SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription
|
|
||||||
SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription
|
|
||||||
SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription
|
|
||||||
SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription
|
|
||||||
SubscribePendingLogsEvent(ch chan<- []*types.Log) event.Subscription
|
|
||||||
|
|
||||||
BloomStatus() (uint64, uint64)
|
|
||||||
ServiceFilter(ctx context.Context, session *bloombits.MatcherSession)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FilterSystem holds resources shared by all filters.
|
|
||||||
type FilterSystem struct {
|
|
||||||
backend Backend
|
|
||||||
logsCache *lru.Cache[common.Hash, *logCacheElem]
|
|
||||||
cfg *Config
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewFilterSystem creates a filter system.
|
|
||||||
func NewFilterSystem(backend Backend, config Config) *FilterSystem {
|
|
||||||
config = config.withDefaults()
|
|
||||||
return &FilterSystem{
|
|
||||||
backend: backend,
|
|
||||||
logsCache: lru.NewCache[common.Hash, *logCacheElem](config.LogCacheSize),
|
|
||||||
cfg: &config,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type logCacheElem struct {
|
|
||||||
logs []*types.Log
|
|
||||||
body atomic.Value
|
|
||||||
}
|
|
||||||
|
|
||||||
// cachedLogElem loads block logs from the backend and caches the result.
|
|
||||||
func (sys *FilterSystem) cachedLogElem(ctx context.Context, blockHash common.Hash, number uint64) (*logCacheElem, error) {
|
|
||||||
cached, ok := sys.logsCache.Get(blockHash)
|
|
||||||
if ok {
|
|
||||||
return cached, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
logs, err := sys.backend.GetLogs(ctx, blockHash, number)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if logs == nil {
|
|
||||||
return nil, fmt.Errorf("failed to get logs for block #%d (0x%s)", number, blockHash.TerminalString())
|
|
||||||
}
|
|
||||||
// Database logs are un-derived.
|
|
||||||
// Fill in whatever we can (txHash is inaccessible at this point).
|
|
||||||
flattened := make([]*types.Log, 0)
|
|
||||||
var logIdx uint
|
|
||||||
for i, txLogs := range logs {
|
|
||||||
for _, log := range txLogs {
|
|
||||||
log.BlockHash = blockHash
|
|
||||||
log.BlockNumber = number
|
|
||||||
log.TxIndex = uint(i)
|
|
||||||
log.Index = logIdx
|
|
||||||
logIdx++
|
|
||||||
flattened = append(flattened, log)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
elem := &logCacheElem{logs: flattened}
|
|
||||||
sys.logsCache.Add(blockHash, elem)
|
|
||||||
return elem, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sys *FilterSystem) cachedGetBody(ctx context.Context, elem *logCacheElem, hash common.Hash, number uint64) (*types.Body, error) {
|
|
||||||
if body := elem.body.Load(); body != nil {
|
|
||||||
return body.(*types.Body), nil
|
|
||||||
}
|
|
||||||
body, err := sys.backend.GetBody(ctx, hash, rpc.BlockNumber(number))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
elem.body.Store(body)
|
|
||||||
return body, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Type determines the kind of filter and is used to put the filter in to
|
|
||||||
// the correct bucket when added.
|
|
||||||
type Type byte
|
|
||||||
|
|
||||||
const (
|
|
||||||
// UnknownSubscription indicates an unknown subscription type
|
|
||||||
UnknownSubscription Type = iota
|
|
||||||
// LogsSubscription queries for new or removed (chain reorg) logs
|
|
||||||
LogsSubscription
|
|
||||||
// PendingLogsSubscription queries for logs in pending blocks
|
|
||||||
PendingLogsSubscription
|
|
||||||
// MinedAndPendingLogsSubscription queries for logs in mined and pending blocks.
|
|
||||||
MinedAndPendingLogsSubscription
|
|
||||||
// PendingTransactionsSubscription queries for pending transactions entering
|
|
||||||
// the pending state
|
|
||||||
PendingTransactionsSubscription
|
|
||||||
// BlocksSubscription queries hashes for blocks that are imported
|
|
||||||
BlocksSubscription
|
|
||||||
// LastIndexSubscription keeps track of the last index
|
|
||||||
LastIndexSubscription
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// txChanSize is the size of channel listening to NewTxsEvent.
|
|
||||||
// The number is referenced from the size of tx pool.
|
|
||||||
txChanSize = 4096
|
|
||||||
// rmLogsChanSize is the size of channel listening to RemovedLogsEvent.
|
|
||||||
rmLogsChanSize = 10
|
|
||||||
// logsChanSize is the size of channel listening to LogsEvent.
|
|
||||||
logsChanSize = 10
|
|
||||||
// chainEvChanSize is the size of channel listening to ChainEvent.
|
|
||||||
chainEvChanSize = 10
|
|
||||||
)
|
|
||||||
|
|
||||||
type subscription struct {
|
|
||||||
id rpc.ID
|
|
||||||
typ Type
|
|
||||||
created time.Time
|
|
||||||
logsCrit ethereum.FilterQuery
|
|
||||||
logs chan []*types.Log
|
|
||||||
txs chan []*types.Transaction
|
|
||||||
headers chan *types.Header
|
|
||||||
installed chan struct{} // closed when the filter is installed
|
|
||||||
err chan error // closed when the filter is uninstalled
|
|
||||||
}
|
|
||||||
|
|
||||||
// EventSystem creates subscriptions, processes events and broadcasts them to the
|
|
||||||
// subscription which match the subscription criteria.
|
|
||||||
type EventSystem struct {
|
|
||||||
backend Backend
|
|
||||||
sys *FilterSystem
|
|
||||||
lightMode bool
|
|
||||||
lastHead *types.Header
|
|
||||||
|
|
||||||
// Subscriptions
|
|
||||||
txsSub event.Subscription // Subscription for new transaction event
|
|
||||||
logsSub event.Subscription // Subscription for new log event
|
|
||||||
rmLogsSub event.Subscription // Subscription for removed log event
|
|
||||||
pendingLogsSub event.Subscription // Subscription for pending log event
|
|
||||||
chainSub event.Subscription // Subscription for new chain event
|
|
||||||
|
|
||||||
// Channels
|
|
||||||
install chan *subscription // install filter for event notification
|
|
||||||
uninstall chan *subscription // remove filter for event notification
|
|
||||||
txsCh chan core.NewTxsEvent // Channel to receive new transactions event
|
|
||||||
logsCh chan []*types.Log // Channel to receive new log event
|
|
||||||
pendingLogsCh chan []*types.Log // Channel to receive new log event
|
|
||||||
rmLogsCh chan core.RemovedLogsEvent // Channel to receive removed log event
|
|
||||||
chainCh chan core.ChainEvent // Channel to receive new chain event
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 NewEventSystem(sys *FilterSystem, lightMode bool) *EventSystem {
|
|
||||||
m := &EventSystem{
|
|
||||||
sys: sys,
|
|
||||||
backend: sys.backend,
|
|
||||||
lightMode: lightMode,
|
|
||||||
install: make(chan *subscription),
|
|
||||||
uninstall: make(chan *subscription),
|
|
||||||
txsCh: make(chan core.NewTxsEvent, txChanSize),
|
|
||||||
logsCh: make(chan []*types.Log, logsChanSize),
|
|
||||||
rmLogsCh: make(chan core.RemovedLogsEvent, rmLogsChanSize),
|
|
||||||
pendingLogsCh: make(chan []*types.Log, logsChanSize),
|
|
||||||
chainCh: make(chan core.ChainEvent, chainEvChanSize),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Subscribe events
|
|
||||||
m.txsSub = m.backend.SubscribeNewTxsEvent(m.txsCh)
|
|
||||||
m.logsSub = m.backend.SubscribeLogsEvent(m.logsCh)
|
|
||||||
m.rmLogsSub = m.backend.SubscribeRemovedLogsEvent(m.rmLogsCh)
|
|
||||||
m.chainSub = m.backend.SubscribeChainEvent(m.chainCh)
|
|
||||||
m.pendingLogsSub = m.backend.SubscribePendingLogsEvent(m.pendingLogsCh)
|
|
||||||
|
|
||||||
// Make sure none of the subscriptions are empty
|
|
||||||
if m.txsSub == nil || m.logsSub == nil || m.rmLogsSub == nil || m.chainSub == nil || m.pendingLogsSub == nil {
|
|
||||||
log.Crit("Subscribe for event system failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
go m.eventLoop()
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// Subscription is created when the client registers itself for a particular event.
|
|
||||||
type Subscription struct {
|
|
||||||
ID rpc.ID
|
|
||||||
f *subscription
|
|
||||||
es *EventSystem
|
|
||||||
unsubOnce sync.Once
|
|
||||||
}
|
|
||||||
|
|
||||||
// Err returns a channel that is closed when unsubscribed.
|
|
||||||
func (sub *Subscription) Err() <-chan error {
|
|
||||||
return sub.f.err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unsubscribe uninstalls the subscription from the event broadcast loop.
|
|
||||||
func (sub *Subscription) Unsubscribe() {
|
|
||||||
sub.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 sub.es.uninstall <- sub.f:
|
|
||||||
break uninstallLoop
|
|
||||||
case <-sub.f.logs:
|
|
||||||
case <-sub.f.txs:
|
|
||||||
case <-sub.f.headers:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// wait for filter to be uninstalled in work loop before returning
|
|
||||||
// this ensures that the manager won't use the event channel which
|
|
||||||
// will probably be closed by the client asap after this method returns.
|
|
||||||
<-sub.Err()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// subscribe installs the subscription in the event broadcast loop.
|
|
||||||
func (es *EventSystem) subscribe(sub *subscription) *Subscription {
|
|
||||||
es.install <- sub
|
|
||||||
<-sub.installed
|
|
||||||
return &Subscription{ID: sub.id, f: sub, es: es}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 ethereum.FilterQuery, logs chan []*types.Log) (*Subscription, error) {
|
|
||||||
if len(crit.Topics) > maxTopics {
|
|
||||||
return nil, errExceedMaxTopics
|
|
||||||
}
|
|
||||||
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())
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 es.subscribeLogs(crit, logs), nil
|
|
||||||
}
|
|
||||||
// only interested in mined logs within a specific block range
|
|
||||||
if from >= 0 && to >= 0 && to >= from {
|
|
||||||
return es.subscribeLogs(crit, logs), nil
|
|
||||||
}
|
|
||||||
// interested in mined logs from a specific block number, new logs and pending logs
|
|
||||||
if from >= rpc.LatestBlockNumber && to == rpc.PendingBlockNumber {
|
|
||||||
return es.subscribeMinedPendingLogs(crit, logs), nil
|
|
||||||
}
|
|
||||||
// interested in logs from a specific block number to new mined blocks
|
|
||||||
if from >= 0 && to == rpc.LatestBlockNumber {
|
|
||||||
return es.subscribeLogs(crit, logs), nil
|
|
||||||
}
|
|
||||||
return nil, errInvalidBlockRange
|
|
||||||
}
|
|
||||||
|
|
||||||
// subscribeMinedPendingLogs creates a subscription that returned mined and
|
|
||||||
// pending logs that match the given criteria.
|
|
||||||
func (es *EventSystem) subscribeMinedPendingLogs(crit ethereum.FilterQuery, logs chan []*types.Log) *Subscription {
|
|
||||||
sub := &subscription{
|
|
||||||
id: rpc.NewID(),
|
|
||||||
typ: MinedAndPendingLogsSubscription,
|
|
||||||
logsCrit: crit,
|
|
||||||
created: time.Now(),
|
|
||||||
logs: logs,
|
|
||||||
txs: make(chan []*types.Transaction),
|
|
||||||
headers: make(chan *types.Header),
|
|
||||||
installed: make(chan struct{}),
|
|
||||||
err: make(chan error),
|
|
||||||
}
|
|
||||||
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 ethereum.FilterQuery, logs chan []*types.Log) *Subscription {
|
|
||||||
sub := &subscription{
|
|
||||||
id: rpc.NewID(),
|
|
||||||
typ: LogsSubscription,
|
|
||||||
logsCrit: crit,
|
|
||||||
created: time.Now(),
|
|
||||||
logs: logs,
|
|
||||||
txs: make(chan []*types.Transaction),
|
|
||||||
headers: make(chan *types.Header),
|
|
||||||
installed: make(chan struct{}),
|
|
||||||
err: make(chan error),
|
|
||||||
}
|
|
||||||
return es.subscribe(sub)
|
|
||||||
}
|
|
||||||
|
|
||||||
// subscribePendingLogs creates a subscription that writes contract event logs for
|
|
||||||
// transactions that enter the transaction pool.
|
|
||||||
func (es *EventSystem) subscribePendingLogs(crit ethereum.FilterQuery, logs chan []*types.Log) *Subscription {
|
|
||||||
sub := &subscription{
|
|
||||||
id: rpc.NewID(),
|
|
||||||
typ: PendingLogsSubscription,
|
|
||||||
logsCrit: crit,
|
|
||||||
created: time.Now(),
|
|
||||||
logs: logs,
|
|
||||||
txs: make(chan []*types.Transaction),
|
|
||||||
headers: make(chan *types.Header),
|
|
||||||
installed: make(chan struct{}),
|
|
||||||
err: make(chan error),
|
|
||||||
}
|
|
||||||
return es.subscribe(sub)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SubscribeNewHeads creates a subscription that writes the header of a block that is
|
|
||||||
// imported in the chain.
|
|
||||||
func (es *EventSystem) SubscribeNewHeads(headers chan *types.Header) *Subscription {
|
|
||||||
sub := &subscription{
|
|
||||||
id: rpc.NewID(),
|
|
||||||
typ: BlocksSubscription,
|
|
||||||
created: time.Now(),
|
|
||||||
logs: make(chan []*types.Log),
|
|
||||||
txs: make(chan []*types.Transaction),
|
|
||||||
headers: headers,
|
|
||||||
installed: make(chan struct{}),
|
|
||||||
err: make(chan error),
|
|
||||||
}
|
|
||||||
return es.subscribe(sub)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SubscribePendingTxs creates a subscription that writes transactions for
|
|
||||||
// transactions that enter the transaction pool.
|
|
||||||
func (es *EventSystem) SubscribePendingTxs(txs chan []*types.Transaction) *Subscription {
|
|
||||||
sub := &subscription{
|
|
||||||
id: rpc.NewID(),
|
|
||||||
typ: PendingTransactionsSubscription,
|
|
||||||
created: time.Now(),
|
|
||||||
logs: make(chan []*types.Log),
|
|
||||||
txs: txs,
|
|
||||||
headers: make(chan *types.Header),
|
|
||||||
installed: make(chan struct{}),
|
|
||||||
err: make(chan error),
|
|
||||||
}
|
|
||||||
return es.subscribe(sub)
|
|
||||||
}
|
|
||||||
|
|
||||||
type filterIndex map[Type]map[rpc.ID]*subscription
|
|
||||||
|
|
||||||
func (es *EventSystem) handleLogs(filters filterIndex, ev []*types.Log) {
|
|
||||||
if len(ev) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, f := range filters[LogsSubscription] {
|
|
||||||
matchedLogs := filterLogs(ev, f.logsCrit.FromBlock, f.logsCrit.ToBlock, f.logsCrit.Addresses, f.logsCrit.Topics)
|
|
||||||
if len(matchedLogs) > 0 {
|
|
||||||
f.logs <- matchedLogs
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (es *EventSystem) handlePendingLogs(filters filterIndex, ev []*types.Log) {
|
|
||||||
if len(ev) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, f := range filters[PendingLogsSubscription] {
|
|
||||||
matchedLogs := filterLogs(ev, nil, f.logsCrit.ToBlock, f.logsCrit.Addresses, f.logsCrit.Topics)
|
|
||||||
if len(matchedLogs) > 0 {
|
|
||||||
f.logs <- matchedLogs
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (es *EventSystem) handleTxsEvent(filters filterIndex, ev core.NewTxsEvent) {
|
|
||||||
for _, f := range filters[PendingTransactionsSubscription] {
|
|
||||||
f.txs <- ev.Txs
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (es *EventSystem) handleChainEvent(filters filterIndex, ev core.ChainEvent) {
|
|
||||||
for _, f := range filters[BlocksSubscription] {
|
|
||||||
f.headers <- ev.Block.Header()
|
|
||||||
}
|
|
||||||
if es.lightMode && len(filters[LogsSubscription]) > 0 {
|
|
||||||
es.lightFilterNewHead(ev.Block.Header(), func(header *types.Header, remove bool) {
|
|
||||||
for _, f := range filters[LogsSubscription] {
|
|
||||||
if f.logsCrit.FromBlock != nil && header.Number.Cmp(f.logsCrit.FromBlock) < 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if f.logsCrit.ToBlock != nil && header.Number.Cmp(f.logsCrit.ToBlock) > 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if matchedLogs := es.lightFilterLogs(header, f.logsCrit.Addresses, f.logsCrit.Topics, remove); len(matchedLogs) > 0 {
|
|
||||||
f.logs <- matchedLogs
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (es *EventSystem) lightFilterNewHead(newHeader *types.Header, callBack func(*types.Header, bool)) {
|
|
||||||
oldh := es.lastHead
|
|
||||||
es.lastHead = newHeader
|
|
||||||
if oldh == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
newh := newHeader
|
|
||||||
// find common ancestor, create list of rolled back and new block hashes
|
|
||||||
var oldHeaders, newHeaders []*types.Header
|
|
||||||
for oldh.Hash() != newh.Hash() {
|
|
||||||
if oldh.Number.Uint64() >= newh.Number.Uint64() {
|
|
||||||
oldHeaders = append(oldHeaders, oldh)
|
|
||||||
oldh = rawdb.ReadHeader(es.backend.ChainDb(), oldh.ParentHash, oldh.Number.Uint64()-1)
|
|
||||||
}
|
|
||||||
if oldh.Number.Uint64() < newh.Number.Uint64() {
|
|
||||||
newHeaders = append(newHeaders, newh)
|
|
||||||
newh = rawdb.ReadHeader(es.backend.ChainDb(), newh.ParentHash, newh.Number.Uint64()-1)
|
|
||||||
if newh == nil {
|
|
||||||
// happens when CHT syncing, nothing to do
|
|
||||||
newh = oldh
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// roll back old blocks
|
|
||||||
for _, h := range oldHeaders {
|
|
||||||
callBack(h, true)
|
|
||||||
}
|
|
||||||
// check new blocks (array is in reverse order)
|
|
||||||
for i := len(newHeaders) - 1; i >= 0; i-- {
|
|
||||||
callBack(newHeaders[i], false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// filter logs of a single header in light client mode
|
|
||||||
func (es *EventSystem) lightFilterLogs(header *types.Header, addresses []common.Address, topics [][]common.Hash, remove bool) []*types.Log {
|
|
||||||
if !bloomFilter(header.Bloom, addresses, topics) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// Get the logs of the block
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
|
||||||
defer cancel()
|
|
||||||
cached, err := es.sys.cachedLogElem(ctx, header.Hash(), header.Number.Uint64())
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
unfiltered := append([]*types.Log{}, cached.logs...)
|
|
||||||
for i, log := range unfiltered {
|
|
||||||
// Don't modify in-cache elements
|
|
||||||
logcopy := *log
|
|
||||||
logcopy.Removed = remove
|
|
||||||
// Swap copy in-place
|
|
||||||
unfiltered[i] = &logcopy
|
|
||||||
}
|
|
||||||
logs := filterLogs(unfiltered, nil, nil, addresses, topics)
|
|
||||||
// Txhash is already resolved
|
|
||||||
if len(logs) > 0 && logs[0].TxHash != (common.Hash{}) {
|
|
||||||
return logs
|
|
||||||
}
|
|
||||||
// Resolve txhash
|
|
||||||
body, err := es.sys.cachedGetBody(ctx, cached, header.Hash(), header.Number.Uint64())
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
for _, log := range logs {
|
|
||||||
// logs are already copied, safe to modify
|
|
||||||
log.TxHash = body.Transactions[log.TxIndex].Hash()
|
|
||||||
}
|
|
||||||
return logs
|
|
||||||
}
|
|
||||||
|
|
||||||
// eventLoop (un)installs filters and processes mux events.
|
|
||||||
func (es *EventSystem) eventLoop() {
|
|
||||||
// Ensure all subscriptions get cleaned up
|
|
||||||
defer func() {
|
|
||||||
es.txsSub.Unsubscribe()
|
|
||||||
es.logsSub.Unsubscribe()
|
|
||||||
es.rmLogsSub.Unsubscribe()
|
|
||||||
es.pendingLogsSub.Unsubscribe()
|
|
||||||
es.chainSub.Unsubscribe()
|
|
||||||
}()
|
|
||||||
|
|
||||||
index := make(filterIndex)
|
|
||||||
for i := UnknownSubscription; i < LastIndexSubscription; i++ {
|
|
||||||
index[i] = make(map[rpc.ID]*subscription)
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case ev := <-es.txsCh:
|
|
||||||
es.handleTxsEvent(index, ev)
|
|
||||||
case ev := <-es.logsCh:
|
|
||||||
es.handleLogs(index, ev)
|
|
||||||
case ev := <-es.rmLogsCh:
|
|
||||||
es.handleLogs(index, ev.Logs)
|
|
||||||
case ev := <-es.pendingLogsCh:
|
|
||||||
es.handlePendingLogs(index, ev)
|
|
||||||
case ev := <-es.chainCh:
|
|
||||||
es.handleChainEvent(index, ev)
|
|
||||||
|
|
||||||
case f := <-es.install:
|
|
||||||
if f.typ == MinedAndPendingLogsSubscription {
|
|
||||||
// the type are logs and pending logs subscriptions
|
|
||||||
index[LogsSubscription][f.id] = f
|
|
||||||
index[PendingLogsSubscription][f.id] = f
|
|
||||||
} else {
|
|
||||||
index[f.typ][f.id] = f
|
|
||||||
}
|
|
||||||
close(f.installed)
|
|
||||||
|
|
||||||
case f := <-es.uninstall:
|
|
||||||
if f.typ == MinedAndPendingLogsSubscription {
|
|
||||||
// the type are logs and pending logs subscriptions
|
|
||||||
delete(index[LogsSubscription], f.id)
|
|
||||||
delete(index[PendingLogsSubscription], f.id)
|
|
||||||
} else {
|
|
||||||
delete(index[f.typ], f.id)
|
|
||||||
}
|
|
||||||
close(f.err)
|
|
||||||
|
|
||||||
// System stopped
|
|
||||||
case <-es.txsSub.Err():
|
|
||||||
return
|
|
||||||
case <-es.logsSub.Err():
|
|
||||||
return
|
|
||||||
case <-es.rmLogsSub.Err():
|
|
||||||
return
|
|
||||||
case <-es.chainSub.Err():
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,977 +0,0 @@
|
||||||
// Copyright 2016 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package filters
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"math/big"
|
|
||||||
"math/rand"
|
|
||||||
"reflect"
|
|
||||||
"runtime"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum"
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
|
||||||
"github.com/ethereum/go-ethereum/core"
|
|
||||||
"github.com/ethereum/go-ethereum/core/bloombits"
|
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
|
||||||
"github.com/ethereum/go-ethereum/event"
|
|
||||||
"github.com/ethereum/go-ethereum/internal/ethapi"
|
|
||||||
"github.com/ethereum/go-ethereum/params"
|
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
|
||||||
)
|
|
||||||
|
|
||||||
type testBackend struct {
|
|
||||||
db ethdb.Database
|
|
||||||
sections uint64
|
|
||||||
txFeed event.Feed
|
|
||||||
logsFeed event.Feed
|
|
||||||
rmLogsFeed event.Feed
|
|
||||||
pendingLogsFeed event.Feed
|
|
||||||
chainFeed event.Feed
|
|
||||||
pendingBlock *types.Block
|
|
||||||
pendingReceipts types.Receipts
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *testBackend) ChainConfig() *params.ChainConfig {
|
|
||||||
return params.TestChainConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *testBackend) CurrentHeader() *types.Header {
|
|
||||||
hdr, _ := b.HeaderByNumber(context.TODO(), rpc.LatestBlockNumber)
|
|
||||||
return hdr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *testBackend) ChainDb() ethdb.Database {
|
|
||||||
return b.db
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *testBackend) HeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Header, error) {
|
|
||||||
var (
|
|
||||||
hash common.Hash
|
|
||||||
num uint64
|
|
||||||
)
|
|
||||||
switch blockNr {
|
|
||||||
case rpc.LatestBlockNumber:
|
|
||||||
hash = rawdb.ReadHeadBlockHash(b.db)
|
|
||||||
number := rawdb.ReadHeaderNumber(b.db, hash)
|
|
||||||
if number == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
num = *number
|
|
||||||
case rpc.FinalizedBlockNumber:
|
|
||||||
hash = rawdb.ReadFinalizedBlockHash(b.db)
|
|
||||||
number := rawdb.ReadHeaderNumber(b.db, hash)
|
|
||||||
if number == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
num = *number
|
|
||||||
case rpc.SafeBlockNumber:
|
|
||||||
return nil, errors.New("safe block not found")
|
|
||||||
default:
|
|
||||||
num = uint64(blockNr)
|
|
||||||
hash = rawdb.ReadCanonicalHash(b.db, num)
|
|
||||||
}
|
|
||||||
return rawdb.ReadHeader(b.db, hash, num), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *testBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) {
|
|
||||||
number := rawdb.ReadHeaderNumber(b.db, hash)
|
|
||||||
if number == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
return rawdb.ReadHeader(b.db, hash, *number), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *testBackend) GetBody(ctx context.Context, hash common.Hash, number rpc.BlockNumber) (*types.Body, error) {
|
|
||||||
if body := rawdb.ReadBody(b.db, hash, uint64(number)); body != nil {
|
|
||||||
return body, nil
|
|
||||||
}
|
|
||||||
return nil, errors.New("block body not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *testBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) {
|
|
||||||
if number := rawdb.ReadHeaderNumber(b.db, hash); number != nil {
|
|
||||||
if header := rawdb.ReadHeader(b.db, hash, *number); header != nil {
|
|
||||||
return rawdb.ReadReceipts(b.db, hash, *number, header.Time, params.TestChainConfig), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *testBackend) GetLogs(ctx context.Context, hash common.Hash, number uint64) ([][]*types.Log, error) {
|
|
||||||
logs := rawdb.ReadLogs(b.db, hash, number)
|
|
||||||
return logs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *testBackend) PendingBlockAndReceipts() (*types.Block, types.Receipts) {
|
|
||||||
return b.pendingBlock, b.pendingReceipts
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *testBackend) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription {
|
|
||||||
return b.txFeed.Subscribe(ch)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *testBackend) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription {
|
|
||||||
return b.rmLogsFeed.Subscribe(ch)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *testBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription {
|
|
||||||
return b.logsFeed.Subscribe(ch)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *testBackend) SubscribePendingLogsEvent(ch chan<- []*types.Log) event.Subscription {
|
|
||||||
return b.pendingLogsFeed.Subscribe(ch)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *testBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription {
|
|
||||||
return b.chainFeed.Subscribe(ch)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *testBackend) BloomStatus() (uint64, uint64) {
|
|
||||||
return params.BloomBitsBlocks, b.sections
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *testBackend) ServiceFilter(ctx context.Context, session *bloombits.MatcherSession) {
|
|
||||||
requests := make(chan chan *bloombits.Retrieval)
|
|
||||||
|
|
||||||
go session.Multiplex(16, 0, requests)
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
// Wait for a service request or a shutdown
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return
|
|
||||||
|
|
||||||
case request := <-requests:
|
|
||||||
task := <-request
|
|
||||||
|
|
||||||
task.Bitsets = make([][]byte, len(task.Sections))
|
|
||||||
for i, section := range task.Sections {
|
|
||||||
if rand.Int()%4 != 0 { // Handle occasional missing deliveries
|
|
||||||
head := rawdb.ReadCanonicalHash(b.db, (section+1)*params.BloomBitsBlocks-1)
|
|
||||||
task.Bitsets[i], _ = rawdb.ReadBloomBits(b.db, task.Bit, section, head)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
request <- task
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
func newTestFilterSystem(t testing.TB, db ethdb.Database, cfg Config) (*testBackend, *FilterSystem) {
|
|
||||||
backend := &testBackend{db: db}
|
|
||||||
sys := NewFilterSystem(backend, cfg)
|
|
||||||
return backend, sys
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestBlockSubscription tests if a block subscription returns block hashes for posted chain events.
|
|
||||||
// It creates multiple subscriptions:
|
|
||||||
// - one at the start and should receive all posted chain events and a second (blockHashes)
|
|
||||||
// - one that is created after a cutoff moment and uninstalled after a second cutoff moment (blockHashes[cutoff1:cutoff2])
|
|
||||||
// - one that is created after the second cutoff moment (blockHashes[cutoff2:])
|
|
||||||
func TestBlockSubscription(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
var (
|
|
||||||
db = rawdb.NewMemoryDatabase()
|
|
||||||
backend, sys = newTestFilterSystem(t, db, Config{})
|
|
||||||
api = NewFilterAPI(sys, false)
|
|
||||||
genesis = &core.Genesis{
|
|
||||||
Config: params.TestChainConfig,
|
|
||||||
BaseFee: big.NewInt(params.InitialBaseFee),
|
|
||||||
}
|
|
||||||
_, chain, _ = core.GenerateChainWithGenesis(genesis, ethash.NewFaker(), 10, func(i int, gen *core.BlockGen) {})
|
|
||||||
chainEvents = []core.ChainEvent{}
|
|
||||||
)
|
|
||||||
|
|
||||||
for _, blk := range chain {
|
|
||||||
chainEvents = append(chainEvents, core.ChainEvent{Hash: blk.Hash(), Block: blk})
|
|
||||||
}
|
|
||||||
|
|
||||||
chan0 := make(chan *types.Header)
|
|
||||||
sub0 := api.events.SubscribeNewHeads(chan0)
|
|
||||||
chan1 := make(chan *types.Header)
|
|
||||||
sub1 := api.events.SubscribeNewHeads(chan1)
|
|
||||||
|
|
||||||
go func() { // simulate client
|
|
||||||
i1, i2 := 0, 0
|
|
||||||
for i1 != len(chainEvents) || i2 != len(chainEvents) {
|
|
||||||
select {
|
|
||||||
case header := <-chan0:
|
|
||||||
if chainEvents[i1].Hash != header.Hash() {
|
|
||||||
t.Errorf("sub0 received invalid hash on index %d, want %x, got %x", i1, chainEvents[i1].Hash, header.Hash())
|
|
||||||
}
|
|
||||||
i1++
|
|
||||||
case header := <-chan1:
|
|
||||||
if chainEvents[i2].Hash != header.Hash() {
|
|
||||||
t.Errorf("sub1 received invalid hash on index %d, want %x, got %x", i2, chainEvents[i2].Hash, header.Hash())
|
|
||||||
}
|
|
||||||
i2++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sub0.Unsubscribe()
|
|
||||||
sub1.Unsubscribe()
|
|
||||||
}()
|
|
||||||
|
|
||||||
time.Sleep(1 * time.Second)
|
|
||||||
for _, e := range chainEvents {
|
|
||||||
backend.chainFeed.Send(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
<-sub0.Err()
|
|
||||||
<-sub1.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestPendingTxFilter tests whether pending tx filters retrieve all pending transactions that are posted to the event mux.
|
|
||||||
func TestPendingTxFilter(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
var (
|
|
||||||
db = rawdb.NewMemoryDatabase()
|
|
||||||
backend, sys = newTestFilterSystem(t, db, Config{})
|
|
||||||
api = NewFilterAPI(sys, false)
|
|
||||||
|
|
||||||
transactions = []*types.Transaction{
|
|
||||||
types.NewTransaction(0, common.HexToAddress("0xb794f5ea0ba39494ce83a213fffba74279579268"), new(big.Int), 0, new(big.Int), nil),
|
|
||||||
types.NewTransaction(1, common.HexToAddress("0xb794f5ea0ba39494ce83a213fffba74279579268"), new(big.Int), 0, new(big.Int), nil),
|
|
||||||
types.NewTransaction(2, common.HexToAddress("0xb794f5ea0ba39494ce83a213fffba74279579268"), new(big.Int), 0, new(big.Int), nil),
|
|
||||||
types.NewTransaction(3, common.HexToAddress("0xb794f5ea0ba39494ce83a213fffba74279579268"), new(big.Int), 0, new(big.Int), nil),
|
|
||||||
types.NewTransaction(4, common.HexToAddress("0xb794f5ea0ba39494ce83a213fffba74279579268"), new(big.Int), 0, new(big.Int), nil),
|
|
||||||
}
|
|
||||||
|
|
||||||
hashes []common.Hash
|
|
||||||
)
|
|
||||||
|
|
||||||
fid0 := api.NewPendingTransactionFilter(nil)
|
|
||||||
|
|
||||||
time.Sleep(1 * time.Second)
|
|
||||||
backend.txFeed.Send(core.NewTxsEvent{Txs: transactions})
|
|
||||||
|
|
||||||
timeout := time.Now().Add(1 * time.Second)
|
|
||||||
for {
|
|
||||||
results, err := api.GetFilterChanges(fid0)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Unable to retrieve logs: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
h := results.([]common.Hash)
|
|
||||||
hashes = append(hashes, h...)
|
|
||||||
if len(hashes) >= len(transactions) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
// check timeout
|
|
||||||
if time.Now().After(timeout) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(hashes) != len(transactions) {
|
|
||||||
t.Errorf("invalid number of transactions, want %d transactions(s), got %d", len(transactions), len(hashes))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for i := range hashes {
|
|
||||||
if hashes[i] != transactions[i].Hash() {
|
|
||||||
t.Errorf("hashes[%d] invalid, want %x, got %x", i, transactions[i].Hash(), hashes[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestPendingTxFilterFullTx tests whether pending tx filters retrieve all pending transactions that are posted to the event mux.
|
|
||||||
func TestPendingTxFilterFullTx(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
var (
|
|
||||||
db = rawdb.NewMemoryDatabase()
|
|
||||||
backend, sys = newTestFilterSystem(t, db, Config{})
|
|
||||||
api = NewFilterAPI(sys, false)
|
|
||||||
|
|
||||||
transactions = []*types.Transaction{
|
|
||||||
types.NewTransaction(0, common.HexToAddress("0xb794f5ea0ba39494ce83a213fffba74279579268"), new(big.Int), 0, new(big.Int), nil),
|
|
||||||
types.NewTransaction(1, common.HexToAddress("0xb794f5ea0ba39494ce83a213fffba74279579268"), new(big.Int), 0, new(big.Int), nil),
|
|
||||||
types.NewTransaction(2, common.HexToAddress("0xb794f5ea0ba39494ce83a213fffba74279579268"), new(big.Int), 0, new(big.Int), nil),
|
|
||||||
types.NewTransaction(3, common.HexToAddress("0xb794f5ea0ba39494ce83a213fffba74279579268"), new(big.Int), 0, new(big.Int), nil),
|
|
||||||
types.NewTransaction(4, common.HexToAddress("0xb794f5ea0ba39494ce83a213fffba74279579268"), new(big.Int), 0, new(big.Int), nil),
|
|
||||||
}
|
|
||||||
|
|
||||||
txs []*ethapi.RPCTransaction
|
|
||||||
)
|
|
||||||
|
|
||||||
fullTx := true
|
|
||||||
fid0 := api.NewPendingTransactionFilter(&fullTx)
|
|
||||||
|
|
||||||
time.Sleep(1 * time.Second)
|
|
||||||
backend.txFeed.Send(core.NewTxsEvent{Txs: transactions})
|
|
||||||
|
|
||||||
timeout := time.Now().Add(1 * time.Second)
|
|
||||||
for {
|
|
||||||
results, err := api.GetFilterChanges(fid0)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Unable to retrieve logs: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tx := results.([]*ethapi.RPCTransaction)
|
|
||||||
txs = append(txs, tx...)
|
|
||||||
if len(txs) >= len(transactions) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
// check timeout
|
|
||||||
if time.Now().After(timeout) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(txs) != len(transactions) {
|
|
||||||
t.Errorf("invalid number of transactions, want %d transactions(s), got %d", len(transactions), len(txs))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for i := range txs {
|
|
||||||
if txs[i].Hash != transactions[i].Hash() {
|
|
||||||
t.Errorf("hashes[%d] invalid, want %x, got %x", i, transactions[i].Hash(), txs[i].Hash)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestLogFilterCreation test whether a given filter criteria makes sense.
|
|
||||||
// If not it must return an error.
|
|
||||||
func TestLogFilterCreation(t *testing.T) {
|
|
||||||
var (
|
|
||||||
db = rawdb.NewMemoryDatabase()
|
|
||||||
_, sys = newTestFilterSystem(t, db, Config{})
|
|
||||||
api = NewFilterAPI(sys, false)
|
|
||||||
|
|
||||||
testCases = []struct {
|
|
||||||
crit FilterCriteria
|
|
||||||
success bool
|
|
||||||
}{
|
|
||||||
// defaults
|
|
||||||
{FilterCriteria{}, true},
|
|
||||||
// valid block number range
|
|
||||||
{FilterCriteria{FromBlock: big.NewInt(1), ToBlock: big.NewInt(2)}, true},
|
|
||||||
// "mined" block range to pending
|
|
||||||
{FilterCriteria{FromBlock: big.NewInt(1), ToBlock: big.NewInt(rpc.LatestBlockNumber.Int64())}, true},
|
|
||||||
// new mined and pending blocks
|
|
||||||
{FilterCriteria{FromBlock: big.NewInt(rpc.LatestBlockNumber.Int64()), ToBlock: big.NewInt(rpc.PendingBlockNumber.Int64())}, true},
|
|
||||||
// from block "higher" than to block
|
|
||||||
{FilterCriteria{FromBlock: big.NewInt(2), ToBlock: big.NewInt(1)}, false},
|
|
||||||
// from block "higher" than to block
|
|
||||||
{FilterCriteria{FromBlock: big.NewInt(rpc.LatestBlockNumber.Int64()), ToBlock: big.NewInt(100)}, false},
|
|
||||||
// from block "higher" than to block
|
|
||||||
{FilterCriteria{FromBlock: big.NewInt(rpc.PendingBlockNumber.Int64()), ToBlock: big.NewInt(100)}, false},
|
|
||||||
// from block "higher" than to block
|
|
||||||
{FilterCriteria{FromBlock: big.NewInt(rpc.PendingBlockNumber.Int64()), ToBlock: big.NewInt(rpc.LatestBlockNumber.Int64())}, false},
|
|
||||||
// topics more then 4
|
|
||||||
{FilterCriteria{Topics: [][]common.Hash{{}, {}, {}, {}, {}}}, false},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
for i, test := range testCases {
|
|
||||||
id, err := api.NewFilter(test.crit)
|
|
||||||
if err != nil && test.success {
|
|
||||||
t.Errorf("expected filter creation for case %d to success, got %v", i, err)
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
api.UninstallFilter(id)
|
|
||||||
if !test.success {
|
|
||||||
t.Errorf("expected testcase %d to fail with an error", i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestInvalidLogFilterCreation tests whether invalid filter log criteria results in an error
|
|
||||||
// when the filter is created.
|
|
||||||
func TestInvalidLogFilterCreation(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
var (
|
|
||||||
db = rawdb.NewMemoryDatabase()
|
|
||||||
_, sys = newTestFilterSystem(t, db, Config{})
|
|
||||||
api = NewFilterAPI(sys, false)
|
|
||||||
)
|
|
||||||
|
|
||||||
// different situations where log filter creation should fail.
|
|
||||||
// Reason: fromBlock > toBlock
|
|
||||||
testCases := []FilterCriteria{
|
|
||||||
0: {FromBlock: big.NewInt(rpc.PendingBlockNumber.Int64()), ToBlock: big.NewInt(rpc.LatestBlockNumber.Int64())},
|
|
||||||
1: {FromBlock: big.NewInt(rpc.PendingBlockNumber.Int64()), ToBlock: big.NewInt(100)},
|
|
||||||
2: {FromBlock: big.NewInt(rpc.LatestBlockNumber.Int64()), ToBlock: big.NewInt(100)},
|
|
||||||
3: {Topics: [][]common.Hash{{}, {}, {}, {}, {}}},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, test := range testCases {
|
|
||||||
if _, err := api.NewFilter(test); err == nil {
|
|
||||||
t.Errorf("Expected NewFilter for case #%d to fail", i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestLogFilterUninstall tests invalid getLogs requests
|
|
||||||
func TestInvalidGetLogsRequest(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
var (
|
|
||||||
db = rawdb.NewMemoryDatabase()
|
|
||||||
_, sys = newTestFilterSystem(t, db, Config{})
|
|
||||||
api = NewFilterAPI(sys, false)
|
|
||||||
blockHash = common.HexToHash("0x1111111111111111111111111111111111111111111111111111111111111111")
|
|
||||||
)
|
|
||||||
|
|
||||||
// Reason: Cannot specify both BlockHash and FromBlock/ToBlock)
|
|
||||||
testCases := []FilterCriteria{
|
|
||||||
0: {BlockHash: &blockHash, FromBlock: big.NewInt(100)},
|
|
||||||
1: {BlockHash: &blockHash, ToBlock: big.NewInt(500)},
|
|
||||||
2: {BlockHash: &blockHash, FromBlock: big.NewInt(rpc.LatestBlockNumber.Int64())},
|
|
||||||
3: {BlockHash: &blockHash, Topics: [][]common.Hash{{}, {}, {}, {}, {}}},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, test := range testCases {
|
|
||||||
if _, err := api.GetLogs(context.Background(), test); err == nil {
|
|
||||||
t.Errorf("Expected Logs for case #%d to fail", i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestInvalidGetRangeLogsRequest tests getLogs with invalid block range
|
|
||||||
func TestInvalidGetRangeLogsRequest(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
var (
|
|
||||||
db = rawdb.NewMemoryDatabase()
|
|
||||||
_, sys = newTestFilterSystem(t, db, Config{})
|
|
||||||
api = NewFilterAPI(sys, false)
|
|
||||||
)
|
|
||||||
|
|
||||||
if _, err := api.GetLogs(context.Background(), FilterCriteria{FromBlock: big.NewInt(2), ToBlock: big.NewInt(1)}); err != errInvalidBlockRange {
|
|
||||||
t.Errorf("Expected Logs for invalid range return error, but got: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestLogFilter tests whether log filters match the correct logs that are posted to the event feed.
|
|
||||||
func TestLogFilter(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
var (
|
|
||||||
db = rawdb.NewMemoryDatabase()
|
|
||||||
backend, sys = newTestFilterSystem(t, db, Config{})
|
|
||||||
api = NewFilterAPI(sys, false)
|
|
||||||
|
|
||||||
firstAddr = common.HexToAddress("0x1111111111111111111111111111111111111111")
|
|
||||||
secondAddr = common.HexToAddress("0x2222222222222222222222222222222222222222")
|
|
||||||
thirdAddress = common.HexToAddress("0x3333333333333333333333333333333333333333")
|
|
||||||
notUsedAddress = common.HexToAddress("0x9999999999999999999999999999999999999999")
|
|
||||||
firstTopic = common.HexToHash("0x1111111111111111111111111111111111111111111111111111111111111111")
|
|
||||||
secondTopic = common.HexToHash("0x2222222222222222222222222222222222222222222222222222222222222222")
|
|
||||||
notUsedTopic = common.HexToHash("0x9999999999999999999999999999999999999999999999999999999999999999")
|
|
||||||
|
|
||||||
// posted twice, once as regular logs and once as pending logs.
|
|
||||||
allLogs = []*types.Log{
|
|
||||||
{Address: firstAddr},
|
|
||||||
{Address: firstAddr, Topics: []common.Hash{firstTopic}, BlockNumber: 1},
|
|
||||||
{Address: secondAddr, Topics: []common.Hash{firstTopic}, BlockNumber: 1},
|
|
||||||
{Address: thirdAddress, Topics: []common.Hash{secondTopic}, BlockNumber: 2},
|
|
||||||
{Address: thirdAddress, Topics: []common.Hash{secondTopic}, BlockNumber: 3},
|
|
||||||
}
|
|
||||||
|
|
||||||
expectedCase7 = []*types.Log{allLogs[3], allLogs[4], allLogs[0], allLogs[1], allLogs[2], allLogs[3], allLogs[4]}
|
|
||||||
expectedCase11 = []*types.Log{allLogs[1], allLogs[2], allLogs[1], allLogs[2]}
|
|
||||||
|
|
||||||
testCases = []struct {
|
|
||||||
crit FilterCriteria
|
|
||||||
expected []*types.Log
|
|
||||||
id rpc.ID
|
|
||||||
}{
|
|
||||||
// match all
|
|
||||||
0: {FilterCriteria{}, allLogs, ""},
|
|
||||||
// match none due to no matching addresses
|
|
||||||
1: {FilterCriteria{Addresses: []common.Address{{}, notUsedAddress}, Topics: [][]common.Hash{nil}}, []*types.Log{}, ""},
|
|
||||||
// match logs based on addresses, ignore topics
|
|
||||||
2: {FilterCriteria{Addresses: []common.Address{firstAddr}}, allLogs[:2], ""},
|
|
||||||
// match none due to no matching topics (match with address)
|
|
||||||
3: {FilterCriteria{Addresses: []common.Address{secondAddr}, Topics: [][]common.Hash{{notUsedTopic}}}, []*types.Log{}, ""},
|
|
||||||
// match logs based on addresses and topics
|
|
||||||
4: {FilterCriteria{Addresses: []common.Address{thirdAddress}, Topics: [][]common.Hash{{firstTopic, secondTopic}}}, allLogs[3:5], ""},
|
|
||||||
// match logs based on multiple addresses and "or" topics
|
|
||||||
5: {FilterCriteria{Addresses: []common.Address{secondAddr, thirdAddress}, Topics: [][]common.Hash{{firstTopic, secondTopic}}}, allLogs[2:5], ""},
|
|
||||||
// logs in the pending block
|
|
||||||
6: {FilterCriteria{Addresses: []common.Address{firstAddr}, FromBlock: big.NewInt(rpc.PendingBlockNumber.Int64()), ToBlock: big.NewInt(rpc.PendingBlockNumber.Int64())}, allLogs[:2], ""},
|
|
||||||
// mined logs with block num >= 2 or pending logs
|
|
||||||
7: {FilterCriteria{FromBlock: big.NewInt(2), ToBlock: big.NewInt(rpc.PendingBlockNumber.Int64())}, expectedCase7, ""},
|
|
||||||
// all "mined" logs with block num >= 2
|
|
||||||
8: {FilterCriteria{FromBlock: big.NewInt(2), ToBlock: big.NewInt(rpc.LatestBlockNumber.Int64())}, allLogs[3:], ""},
|
|
||||||
// all "mined" logs
|
|
||||||
9: {FilterCriteria{ToBlock: big.NewInt(rpc.LatestBlockNumber.Int64())}, allLogs, ""},
|
|
||||||
// all "mined" logs with 1>= block num <=2 and topic secondTopic
|
|
||||||
10: {FilterCriteria{FromBlock: big.NewInt(1), ToBlock: big.NewInt(2), Topics: [][]common.Hash{{secondTopic}}}, allLogs[3:4], ""},
|
|
||||||
// all "mined" and pending logs with topic firstTopic
|
|
||||||
11: {FilterCriteria{FromBlock: big.NewInt(rpc.LatestBlockNumber.Int64()), ToBlock: big.NewInt(rpc.PendingBlockNumber.Int64()), Topics: [][]common.Hash{{firstTopic}}}, expectedCase11, ""},
|
|
||||||
// match all logs due to wildcard topic
|
|
||||||
12: {FilterCriteria{Topics: [][]common.Hash{nil}}, allLogs[1:], ""},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// create all filters
|
|
||||||
for i := range testCases {
|
|
||||||
testCases[i].id, _ = api.NewFilter(testCases[i].crit)
|
|
||||||
}
|
|
||||||
|
|
||||||
// raise events
|
|
||||||
time.Sleep(1 * time.Second)
|
|
||||||
if nsend := backend.logsFeed.Send(allLogs); nsend == 0 {
|
|
||||||
t.Fatal("Logs event not delivered")
|
|
||||||
}
|
|
||||||
if nsend := backend.pendingLogsFeed.Send(allLogs); nsend == 0 {
|
|
||||||
t.Fatal("Pending logs event not delivered")
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, tt := range testCases {
|
|
||||||
var fetched []*types.Log
|
|
||||||
timeout := time.Now().Add(1 * time.Second)
|
|
||||||
for { // fetch all expected logs
|
|
||||||
results, err := api.GetFilterChanges(tt.id)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Unable to fetch logs: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fetched = append(fetched, results.([]*types.Log)...)
|
|
||||||
if len(fetched) >= len(tt.expected) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
// check timeout
|
|
||||||
if time.Now().After(timeout) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(fetched) != len(tt.expected) {
|
|
||||||
t.Errorf("invalid number of logs for case %d, want %d log(s), got %d", i, len(tt.expected), len(fetched))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for l := range fetched {
|
|
||||||
if fetched[l].Removed {
|
|
||||||
t.Errorf("expected log not to be removed for log %d in case %d", l, i)
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(fetched[l], tt.expected[l]) {
|
|
||||||
t.Errorf("invalid log on index %d for case %d", l, i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestPendingLogsSubscription tests if a subscription receives the correct pending logs that are posted to the event feed.
|
|
||||||
func TestPendingLogsSubscription(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
var (
|
|
||||||
db = rawdb.NewMemoryDatabase()
|
|
||||||
backend, sys = newTestFilterSystem(t, db, Config{})
|
|
||||||
api = NewFilterAPI(sys, false)
|
|
||||||
|
|
||||||
firstAddr = common.HexToAddress("0x1111111111111111111111111111111111111111")
|
|
||||||
secondAddr = common.HexToAddress("0x2222222222222222222222222222222222222222")
|
|
||||||
thirdAddress = common.HexToAddress("0x3333333333333333333333333333333333333333")
|
|
||||||
notUsedAddress = common.HexToAddress("0x9999999999999999999999999999999999999999")
|
|
||||||
firstTopic = common.HexToHash("0x1111111111111111111111111111111111111111111111111111111111111111")
|
|
||||||
secondTopic = common.HexToHash("0x2222222222222222222222222222222222222222222222222222222222222222")
|
|
||||||
thirdTopic = common.HexToHash("0x3333333333333333333333333333333333333333333333333333333333333333")
|
|
||||||
fourthTopic = common.HexToHash("0x4444444444444444444444444444444444444444444444444444444444444444")
|
|
||||||
notUsedTopic = common.HexToHash("0x9999999999999999999999999999999999999999999999999999999999999999")
|
|
||||||
|
|
||||||
allLogs = [][]*types.Log{
|
|
||||||
{{Address: firstAddr, Topics: []common.Hash{}, BlockNumber: 0}},
|
|
||||||
{{Address: firstAddr, Topics: []common.Hash{firstTopic}, BlockNumber: 1}},
|
|
||||||
{{Address: secondAddr, Topics: []common.Hash{firstTopic}, BlockNumber: 2}},
|
|
||||||
{{Address: thirdAddress, Topics: []common.Hash{secondTopic}, BlockNumber: 3}},
|
|
||||||
{{Address: thirdAddress, Topics: []common.Hash{secondTopic}, BlockNumber: 4}},
|
|
||||||
{
|
|
||||||
{Address: thirdAddress, Topics: []common.Hash{firstTopic}, BlockNumber: 5},
|
|
||||||
{Address: thirdAddress, Topics: []common.Hash{thirdTopic}, BlockNumber: 5},
|
|
||||||
{Address: thirdAddress, Topics: []common.Hash{fourthTopic}, BlockNumber: 5},
|
|
||||||
{Address: firstAddr, Topics: []common.Hash{firstTopic}, BlockNumber: 5},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
pendingBlockNumber = big.NewInt(rpc.PendingBlockNumber.Int64())
|
|
||||||
|
|
||||||
testCases = []struct {
|
|
||||||
crit ethereum.FilterQuery
|
|
||||||
expected []*types.Log
|
|
||||||
c chan []*types.Log
|
|
||||||
sub *Subscription
|
|
||||||
err chan error
|
|
||||||
}{
|
|
||||||
// match all
|
|
||||||
{
|
|
||||||
ethereum.FilterQuery{FromBlock: pendingBlockNumber, ToBlock: pendingBlockNumber},
|
|
||||||
flattenLogs(allLogs),
|
|
||||||
nil, nil, nil,
|
|
||||||
},
|
|
||||||
// match none due to no matching addresses
|
|
||||||
{
|
|
||||||
ethereum.FilterQuery{Addresses: []common.Address{{}, notUsedAddress}, Topics: [][]common.Hash{nil}, FromBlock: pendingBlockNumber, ToBlock: pendingBlockNumber},
|
|
||||||
nil,
|
|
||||||
nil, nil, nil,
|
|
||||||
},
|
|
||||||
// match logs based on addresses, ignore topics
|
|
||||||
{
|
|
||||||
ethereum.FilterQuery{Addresses: []common.Address{firstAddr}, FromBlock: pendingBlockNumber, ToBlock: pendingBlockNumber},
|
|
||||||
append(flattenLogs(allLogs[:2]), allLogs[5][3]),
|
|
||||||
nil, nil, nil,
|
|
||||||
},
|
|
||||||
// match none due to no matching topics (match with address)
|
|
||||||
{
|
|
||||||
ethereum.FilterQuery{Addresses: []common.Address{secondAddr}, Topics: [][]common.Hash{{notUsedTopic}}, FromBlock: pendingBlockNumber, ToBlock: pendingBlockNumber},
|
|
||||||
nil,
|
|
||||||
nil, nil, nil,
|
|
||||||
},
|
|
||||||
// match logs based on addresses and topics
|
|
||||||
{
|
|
||||||
ethereum.FilterQuery{Addresses: []common.Address{thirdAddress}, Topics: [][]common.Hash{{firstTopic, secondTopic}}, FromBlock: pendingBlockNumber, ToBlock: pendingBlockNumber},
|
|
||||||
append(flattenLogs(allLogs[3:5]), allLogs[5][0]),
|
|
||||||
nil, nil, nil,
|
|
||||||
},
|
|
||||||
// match logs based on multiple addresses and "or" topics
|
|
||||||
{
|
|
||||||
ethereum.FilterQuery{Addresses: []common.Address{secondAddr, thirdAddress}, Topics: [][]common.Hash{{firstTopic, secondTopic}}, FromBlock: pendingBlockNumber, ToBlock: pendingBlockNumber},
|
|
||||||
append(flattenLogs(allLogs[2:5]), allLogs[5][0]),
|
|
||||||
nil, nil, nil,
|
|
||||||
},
|
|
||||||
// multiple pending logs, should match only 2 topics from the logs in block 5
|
|
||||||
{
|
|
||||||
ethereum.FilterQuery{Addresses: []common.Address{thirdAddress}, Topics: [][]common.Hash{{firstTopic, fourthTopic}}, FromBlock: pendingBlockNumber, ToBlock: pendingBlockNumber},
|
|
||||||
[]*types.Log{allLogs[5][0], allLogs[5][2]},
|
|
||||||
nil, nil, nil,
|
|
||||||
},
|
|
||||||
// match none due to only matching new mined logs
|
|
||||||
{
|
|
||||||
ethereum.FilterQuery{},
|
|
||||||
nil,
|
|
||||||
nil, nil, nil,
|
|
||||||
},
|
|
||||||
// match none due to only matching mined logs within a specific block range
|
|
||||||
{
|
|
||||||
ethereum.FilterQuery{FromBlock: big.NewInt(1), ToBlock: big.NewInt(2)},
|
|
||||||
nil,
|
|
||||||
nil, nil, nil,
|
|
||||||
},
|
|
||||||
// match all due to matching mined and pending logs
|
|
||||||
{
|
|
||||||
ethereum.FilterQuery{FromBlock: big.NewInt(rpc.LatestBlockNumber.Int64()), ToBlock: big.NewInt(rpc.PendingBlockNumber.Int64())},
|
|
||||||
flattenLogs(allLogs),
|
|
||||||
nil, nil, nil,
|
|
||||||
},
|
|
||||||
// match none due to matching logs from a specific block number to new mined blocks
|
|
||||||
{
|
|
||||||
ethereum.FilterQuery{FromBlock: big.NewInt(1), ToBlock: big.NewInt(rpc.LatestBlockNumber.Int64())},
|
|
||||||
nil,
|
|
||||||
nil, nil, nil,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// create all subscriptions, this ensures all subscriptions are created before the events are posted.
|
|
||||||
// on slow machines this could otherwise lead to missing events when the subscription is created after
|
|
||||||
// (some) events are posted.
|
|
||||||
for i := range testCases {
|
|
||||||
testCases[i].c = make(chan []*types.Log)
|
|
||||||
testCases[i].err = make(chan error, 1)
|
|
||||||
|
|
||||||
var err error
|
|
||||||
testCases[i].sub, err = api.events.SubscribeLogs(testCases[i].crit, testCases[i].c)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("SubscribeLogs %d failed: %v\n", i, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for n, test := range testCases {
|
|
||||||
i := n
|
|
||||||
tt := test
|
|
||||||
go func() {
|
|
||||||
defer tt.sub.Unsubscribe()
|
|
||||||
|
|
||||||
var fetched []*types.Log
|
|
||||||
|
|
||||||
timeout := time.After(1 * time.Second)
|
|
||||||
fetchLoop:
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case logs := <-tt.c:
|
|
||||||
// Do not break early if we've fetched greater, or equal,
|
|
||||||
// to the number of logs expected. This ensures we do not
|
|
||||||
// deadlock the filter system because it will do a blocking
|
|
||||||
// send on this channel if another log arrives.
|
|
||||||
fetched = append(fetched, logs...)
|
|
||||||
case <-timeout:
|
|
||||||
break fetchLoop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(fetched) != len(tt.expected) {
|
|
||||||
tt.err <- fmt.Errorf("invalid number of logs for case %d, want %d log(s), got %d", i, len(tt.expected), len(fetched))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for l := range fetched {
|
|
||||||
if fetched[l].Removed {
|
|
||||||
tt.err <- fmt.Errorf("expected log not to be removed for log %d in case %d", l, i)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(fetched[l], tt.expected[l]) {
|
|
||||||
tt.err <- fmt.Errorf("invalid log on index %d for case %d\n", l, i)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tt.err <- nil
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
// raise events
|
|
||||||
for _, ev := range allLogs {
|
|
||||||
backend.pendingLogsFeed.Send(ev)
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range testCases {
|
|
||||||
err := <-testCases[i].err
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("test %d failed: %v", i, err)
|
|
||||||
}
|
|
||||||
<-testCases[i].sub.Err()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLightFilterLogs(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
var (
|
|
||||||
db = rawdb.NewMemoryDatabase()
|
|
||||||
backend, sys = newTestFilterSystem(t, db, Config{})
|
|
||||||
api = NewFilterAPI(sys, true)
|
|
||||||
signer = types.HomesteadSigner{}
|
|
||||||
|
|
||||||
firstAddr = common.HexToAddress("0x1111111111111111111111111111111111111111")
|
|
||||||
secondAddr = common.HexToAddress("0x2222222222222222222222222222222222222222")
|
|
||||||
thirdAddress = common.HexToAddress("0x3333333333333333333333333333333333333333")
|
|
||||||
notUsedAddress = common.HexToAddress("0x9999999999999999999999999999999999999999")
|
|
||||||
firstTopic = common.HexToHash("0x1111111111111111111111111111111111111111111111111111111111111111")
|
|
||||||
secondTopic = common.HexToHash("0x2222222222222222222222222222222222222222222222222222222222222222")
|
|
||||||
|
|
||||||
// posted twice, once as regular logs and once as pending logs.
|
|
||||||
allLogs = []*types.Log{
|
|
||||||
// Block 1
|
|
||||||
{Address: firstAddr, Topics: []common.Hash{}, Data: []byte{}, BlockNumber: 2, Index: 0},
|
|
||||||
// Block 2
|
|
||||||
{Address: firstAddr, Topics: []common.Hash{firstTopic}, Data: []byte{}, BlockNumber: 3, Index: 0},
|
|
||||||
{Address: secondAddr, Topics: []common.Hash{firstTopic}, Data: []byte{}, BlockNumber: 3, Index: 1},
|
|
||||||
{Address: thirdAddress, Topics: []common.Hash{secondTopic}, Data: []byte{}, BlockNumber: 3, Index: 2},
|
|
||||||
// Block 3
|
|
||||||
{Address: thirdAddress, Topics: []common.Hash{secondTopic}, Data: []byte{}, BlockNumber: 4, Index: 0},
|
|
||||||
}
|
|
||||||
|
|
||||||
testCases = []struct {
|
|
||||||
crit FilterCriteria
|
|
||||||
expected []*types.Log
|
|
||||||
id rpc.ID
|
|
||||||
}{
|
|
||||||
// match all
|
|
||||||
0: {FilterCriteria{}, allLogs, ""},
|
|
||||||
// match none due to no matching addresses
|
|
||||||
1: {FilterCriteria{Addresses: []common.Address{{}, notUsedAddress}, Topics: [][]common.Hash{nil}}, []*types.Log{}, ""},
|
|
||||||
// match logs based on addresses, ignore topics
|
|
||||||
2: {FilterCriteria{Addresses: []common.Address{firstAddr}}, allLogs[:2], ""},
|
|
||||||
// match logs based on addresses and topics
|
|
||||||
3: {FilterCriteria{Addresses: []common.Address{thirdAddress}, Topics: [][]common.Hash{{firstTopic, secondTopic}}}, allLogs[3:5], ""},
|
|
||||||
// all logs with block num >= 3
|
|
||||||
4: {FilterCriteria{FromBlock: big.NewInt(3), ToBlock: big.NewInt(5)}, allLogs[1:], ""},
|
|
||||||
// all logs
|
|
||||||
5: {FilterCriteria{FromBlock: big.NewInt(0), ToBlock: big.NewInt(5)}, allLogs, ""},
|
|
||||||
// all logs with 1>= block num <=2 and topic secondTopic
|
|
||||||
6: {FilterCriteria{FromBlock: big.NewInt(2), ToBlock: big.NewInt(3), Topics: [][]common.Hash{{secondTopic}}}, allLogs[3:4], ""},
|
|
||||||
}
|
|
||||||
|
|
||||||
key, _ = crypto.GenerateKey()
|
|
||||||
addr = crypto.PubkeyToAddress(key.PublicKey)
|
|
||||||
genesis = &core.Genesis{Config: params.TestChainConfig,
|
|
||||||
Alloc: core.GenesisAlloc{
|
|
||||||
addr: {Balance: big.NewInt(params.Ether)},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
receipts = []*types.Receipt{{
|
|
||||||
Logs: []*types.Log{allLogs[0]},
|
|
||||||
}, {
|
|
||||||
Logs: []*types.Log{allLogs[1], allLogs[2], allLogs[3]},
|
|
||||||
}, {
|
|
||||||
Logs: []*types.Log{allLogs[4]},
|
|
||||||
}}
|
|
||||||
)
|
|
||||||
|
|
||||||
_, blocks, _ := core.GenerateChainWithGenesis(genesis, ethash.NewFaker(), 4, func(i int, b *core.BlockGen) {
|
|
||||||
if i == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
receipts[i-1].Bloom = types.CreateBloom(types.Receipts{receipts[i-1]})
|
|
||||||
b.AddUncheckedReceipt(receipts[i-1])
|
|
||||||
tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{Nonce: uint64(i - 1), To: &common.Address{}, Value: big.NewInt(1000), Gas: params.TxGas, GasPrice: b.BaseFee(), Data: nil}), signer, key)
|
|
||||||
b.AddTx(tx)
|
|
||||||
})
|
|
||||||
for i, block := range blocks {
|
|
||||||
rawdb.WriteBlock(db, block)
|
|
||||||
rawdb.WriteCanonicalHash(db, block.Hash(), block.NumberU64())
|
|
||||||
rawdb.WriteHeadBlockHash(db, block.Hash())
|
|
||||||
if i > 0 {
|
|
||||||
rawdb.WriteReceipts(db, block.Hash(), block.NumberU64(), []*types.Receipt{receipts[i-1]})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// create all filters
|
|
||||||
for i := range testCases {
|
|
||||||
id, err := api.NewFilter(testCases[i].crit)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
testCases[i].id = id
|
|
||||||
}
|
|
||||||
|
|
||||||
// raise events
|
|
||||||
time.Sleep(1 * time.Second)
|
|
||||||
for _, block := range blocks {
|
|
||||||
backend.chainFeed.Send(core.ChainEvent{Block: block, Hash: common.Hash{}, Logs: allLogs})
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, tt := range testCases {
|
|
||||||
var fetched []*types.Log
|
|
||||||
timeout := time.Now().Add(1 * time.Second)
|
|
||||||
for { // fetch all expected logs
|
|
||||||
results, err := api.GetFilterChanges(tt.id)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Unable to fetch logs: %v", err)
|
|
||||||
}
|
|
||||||
fetched = append(fetched, results.([]*types.Log)...)
|
|
||||||
if len(fetched) >= len(tt.expected) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
// check timeout
|
|
||||||
if time.Now().After(timeout) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(fetched) != len(tt.expected) {
|
|
||||||
t.Errorf("invalid number of logs for case %d, want %d log(s), got %d", i, len(tt.expected), len(fetched))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for l := range fetched {
|
|
||||||
if fetched[l].Removed {
|
|
||||||
t.Errorf("expected log not to be removed for log %d in case %d", l, i)
|
|
||||||
}
|
|
||||||
expected := *tt.expected[l]
|
|
||||||
blockNum := expected.BlockNumber - 1
|
|
||||||
expected.BlockHash = blocks[blockNum].Hash()
|
|
||||||
expected.TxHash = blocks[blockNum].Transactions()[0].Hash()
|
|
||||||
if !reflect.DeepEqual(fetched[l], &expected) {
|
|
||||||
t.Errorf("invalid log on index %d for case %d", l, i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestPendingTxFilterDeadlock tests if the event loop hangs when pending
|
|
||||||
// txes arrive at the same time that one of multiple filters is timing out.
|
|
||||||
// Please refer to #22131 for more details.
|
|
||||||
func TestPendingTxFilterDeadlock(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
timeout := 100 * time.Millisecond
|
|
||||||
|
|
||||||
var (
|
|
||||||
db = rawdb.NewMemoryDatabase()
|
|
||||||
backend, sys = newTestFilterSystem(t, db, Config{Timeout: timeout})
|
|
||||||
api = NewFilterAPI(sys, false)
|
|
||||||
done = make(chan struct{})
|
|
||||||
)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
// Bombard feed with txes until signal was received to stop
|
|
||||||
i := uint64(0)
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-done:
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
tx := types.NewTransaction(i, common.HexToAddress("0xb794f5ea0ba39494ce83a213fffba74279579268"), new(big.Int), 0, new(big.Int), nil)
|
|
||||||
backend.txFeed.Send(core.NewTxsEvent{Txs: []*types.Transaction{tx}})
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Create a bunch of filters that will
|
|
||||||
// timeout either in 100ms or 200ms
|
|
||||||
subs := make([]*Subscription, 20)
|
|
||||||
for i := 0; i < len(subs); i++ {
|
|
||||||
fid := api.NewPendingTransactionFilter(nil)
|
|
||||||
f, ok := api.filters[fid]
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("Filter %s should exist", fid)
|
|
||||||
}
|
|
||||||
subs[i] = f.s
|
|
||||||
// Wait for at least one tx to arrive in filter
|
|
||||||
for {
|
|
||||||
hashes, err := api.GetFilterChanges(fid)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Filter should exist: %v\n", err)
|
|
||||||
}
|
|
||||||
if len(hashes.([]common.Hash)) > 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
runtime.Gosched()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait until filters have timed out and have been uninstalled.
|
|
||||||
for _, sub := range subs {
|
|
||||||
select {
|
|
||||||
case <-sub.Err():
|
|
||||||
case <-time.After(1 * time.Second):
|
|
||||||
t.Fatalf("Filter timeout is hanging")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func flattenLogs(pl [][]*types.Log) []*types.Log {
|
|
||||||
var logs []*types.Log
|
|
||||||
for _, l := range pl {
|
|
||||||
logs = append(logs, l...)
|
|
||||||
}
|
|
||||||
return logs
|
|
||||||
}
|
|
||||||
|
|
@ -1,389 +0,0 @@
|
||||||
// Copyright 2015 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package filters
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"math/big"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi"
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
|
||||||
"github.com/ethereum/go-ethereum/core"
|
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
|
||||||
"github.com/ethereum/go-ethereum/core/vm"
|
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
|
||||||
"github.com/ethereum/go-ethereum/params"
|
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
|
||||||
"github.com/ethereum/go-ethereum/trie"
|
|
||||||
)
|
|
||||||
|
|
||||||
func makeReceipt(addr common.Address) *types.Receipt {
|
|
||||||
receipt := types.NewReceipt(nil, false, 0)
|
|
||||||
receipt.Logs = []*types.Log{
|
|
||||||
{Address: addr},
|
|
||||||
}
|
|
||||||
receipt.Bloom = types.CreateBloom(types.Receipts{receipt})
|
|
||||||
return receipt
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkFilters(b *testing.B) {
|
|
||||||
var (
|
|
||||||
db, _ = rawdb.NewLevelDBDatabase(b.TempDir(), 0, 0, "", false)
|
|
||||||
_, sys = newTestFilterSystem(b, db, Config{})
|
|
||||||
key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
|
||||||
addr1 = crypto.PubkeyToAddress(key1.PublicKey)
|
|
||||||
addr2 = common.BytesToAddress([]byte("jeff"))
|
|
||||||
addr3 = common.BytesToAddress([]byte("ethereum"))
|
|
||||||
addr4 = common.BytesToAddress([]byte("random addresses please"))
|
|
||||||
|
|
||||||
gspec = &core.Genesis{
|
|
||||||
Alloc: core.GenesisAlloc{addr1: {Balance: big.NewInt(1000000)}},
|
|
||||||
BaseFee: big.NewInt(params.InitialBaseFee),
|
|
||||||
Config: params.TestChainConfig,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
defer db.Close()
|
|
||||||
_, chain, receipts := core.GenerateChainWithGenesis(gspec, ethash.NewFaker(), 100010, func(i int, gen *core.BlockGen) {
|
|
||||||
switch i {
|
|
||||||
case 2403:
|
|
||||||
receipt := makeReceipt(addr1)
|
|
||||||
gen.AddUncheckedReceipt(receipt)
|
|
||||||
gen.AddUncheckedTx(types.NewTransaction(999, common.HexToAddress("0x999"), big.NewInt(999), 999, gen.BaseFee(), nil))
|
|
||||||
case 1034:
|
|
||||||
receipt := makeReceipt(addr2)
|
|
||||||
gen.AddUncheckedReceipt(receipt)
|
|
||||||
gen.AddUncheckedTx(types.NewTransaction(999, common.HexToAddress("0x999"), big.NewInt(999), 999, gen.BaseFee(), nil))
|
|
||||||
case 34:
|
|
||||||
receipt := makeReceipt(addr3)
|
|
||||||
gen.AddUncheckedReceipt(receipt)
|
|
||||||
gen.AddUncheckedTx(types.NewTransaction(999, common.HexToAddress("0x999"), big.NewInt(999), 999, gen.BaseFee(), nil))
|
|
||||||
case 99999:
|
|
||||||
receipt := makeReceipt(addr4)
|
|
||||||
gen.AddUncheckedReceipt(receipt)
|
|
||||||
gen.AddUncheckedTx(types.NewTransaction(999, common.HexToAddress("0x999"), big.NewInt(999), 999, gen.BaseFee(), nil))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
// The test txs are not properly signed, can't simply create a chain
|
|
||||||
// and then import blocks. TODO(rjl493456442) try to get rid of the
|
|
||||||
// manual database writes.
|
|
||||||
gspec.MustCommit(db, trie.NewDatabase(db, trie.HashDefaults))
|
|
||||||
|
|
||||||
for i, block := range chain {
|
|
||||||
rawdb.WriteBlock(db, block)
|
|
||||||
rawdb.WriteCanonicalHash(db, block.Hash(), block.NumberU64())
|
|
||||||
rawdb.WriteHeadBlockHash(db, block.Hash())
|
|
||||||
rawdb.WriteReceipts(db, block.Hash(), block.NumberU64(), receipts[i])
|
|
||||||
}
|
|
||||||
b.ResetTimer()
|
|
||||||
|
|
||||||
filter := sys.NewRangeFilter(0, -1, []common.Address{addr1, addr2, addr3, addr4}, nil)
|
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
logs, _ := filter.Logs(context.Background())
|
|
||||||
if len(logs) != 4 {
|
|
||||||
b.Fatal("expected 4 logs, got", len(logs))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFilters(t *testing.T) {
|
|
||||||
var (
|
|
||||||
db = rawdb.NewMemoryDatabase()
|
|
||||||
_, sys = newTestFilterSystem(t, db, Config{})
|
|
||||||
// Sender account
|
|
||||||
key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
|
||||||
addr = crypto.PubkeyToAddress(key1.PublicKey)
|
|
||||||
signer = types.NewLondonSigner(big.NewInt(1))
|
|
||||||
// Logging contract
|
|
||||||
contract = common.Address{0xfe}
|
|
||||||
contract2 = common.Address{0xff}
|
|
||||||
abiStr = `[{"inputs":[],"name":"log0","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"t1","type":"uint256"}],"name":"log1","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"t1","type":"uint256"},{"internalType":"uint256","name":"t2","type":"uint256"}],"name":"log2","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"t1","type":"uint256"},{"internalType":"uint256","name":"t2","type":"uint256"},{"internalType":"uint256","name":"t3","type":"uint256"}],"name":"log3","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"t1","type":"uint256"},{"internalType":"uint256","name":"t2","type":"uint256"},{"internalType":"uint256","name":"t3","type":"uint256"},{"internalType":"uint256","name":"t4","type":"uint256"}],"name":"log4","outputs":[],"stateMutability":"nonpayable","type":"function"}]`
|
|
||||||
/*
|
|
||||||
// SPDX-License-Identifier: GPL-3.0
|
|
||||||
pragma solidity >=0.7.0 <0.9.0;
|
|
||||||
|
|
||||||
contract Logger {
|
|
||||||
function log0() external {
|
|
||||||
assembly {
|
|
||||||
log0(0, 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function log1(uint t1) external {
|
|
||||||
assembly {
|
|
||||||
log1(0, 0, t1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function log2(uint t1, uint t2) external {
|
|
||||||
assembly {
|
|
||||||
log2(0, 0, t1, t2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function log3(uint t1, uint t2, uint t3) external {
|
|
||||||
assembly {
|
|
||||||
log3(0, 0, t1, t2, t3)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function log4(uint t1, uint t2, uint t3, uint t4) external {
|
|
||||||
assembly {
|
|
||||||
log4(0, 0, t1, t2, t3, t4)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
bytecode = common.FromHex("608060405234801561001057600080fd5b50600436106100575760003560e01c80630aa731851461005c5780632a4c08961461006657806378b9a1f314610082578063c670f8641461009e578063c683d6a3146100ba575b600080fd5b6100646100d6565b005b610080600480360381019061007b9190610143565b6100dc565b005b61009c60048036038101906100979190610196565b6100e8565b005b6100b860048036038101906100b391906101d6565b6100f2565b005b6100d460048036038101906100cf9190610203565b6100fa565b005b600080a0565b808284600080a3505050565b8082600080a25050565b80600080a150565b80828486600080a450505050565b600080fd5b6000819050919050565b6101208161010d565b811461012b57600080fd5b50565b60008135905061013d81610117565b92915050565b60008060006060848603121561015c5761015b610108565b5b600061016a8682870161012e565b935050602061017b8682870161012e565b925050604061018c8682870161012e565b9150509250925092565b600080604083850312156101ad576101ac610108565b5b60006101bb8582860161012e565b92505060206101cc8582860161012e565b9150509250929050565b6000602082840312156101ec576101eb610108565b5b60006101fa8482850161012e565b91505092915050565b6000806000806080858703121561021d5761021c610108565b5b600061022b8782880161012e565b945050602061023c8782880161012e565b935050604061024d8782880161012e565b925050606061025e8782880161012e565b9150509295919450925056fea264697066735822122073a4b156f487e59970dc1ef449cc0d51467268f676033a17188edafcee861f9864736f6c63430008110033")
|
|
||||||
|
|
||||||
hash1 = common.BytesToHash([]byte("topic1"))
|
|
||||||
hash2 = common.BytesToHash([]byte("topic2"))
|
|
||||||
hash3 = common.BytesToHash([]byte("topic3"))
|
|
||||||
hash4 = common.BytesToHash([]byte("topic4"))
|
|
||||||
hash5 = common.BytesToHash([]byte("topic5"))
|
|
||||||
|
|
||||||
gspec = &core.Genesis{
|
|
||||||
Config: params.TestChainConfig,
|
|
||||||
Alloc: core.GenesisAlloc{
|
|
||||||
addr: {Balance: big.NewInt(0).Mul(big.NewInt(100), big.NewInt(params.Ether))},
|
|
||||||
contract: {Balance: big.NewInt(0), Code: bytecode},
|
|
||||||
contract2: {Balance: big.NewInt(0), Code: bytecode},
|
|
||||||
},
|
|
||||||
BaseFee: big.NewInt(params.InitialBaseFee),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
contractABI, err := abi.JSON(strings.NewReader(abiStr))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hack: GenerateChainWithGenesis creates a new db.
|
|
||||||
// Commit the genesis manually and use GenerateChain.
|
|
||||||
_, err = gspec.Commit(db, trie.NewDatabase(db, nil))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
chain, _ := core.GenerateChain(gspec.Config, gspec.ToBlock(), ethash.NewFaker(), db, 1000, func(i int, gen *core.BlockGen) {
|
|
||||||
switch i {
|
|
||||||
case 1:
|
|
||||||
data, err := contractABI.Pack("log1", hash1.Big())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{
|
|
||||||
Nonce: 0,
|
|
||||||
GasPrice: gen.BaseFee(),
|
|
||||||
Gas: 30000,
|
|
||||||
To: &contract,
|
|
||||||
Data: data,
|
|
||||||
}), signer, key1)
|
|
||||||
gen.AddTx(tx)
|
|
||||||
tx2, _ := types.SignTx(types.NewTx(&types.LegacyTx{
|
|
||||||
Nonce: 1,
|
|
||||||
GasPrice: gen.BaseFee(),
|
|
||||||
Gas: 30000,
|
|
||||||
To: &contract2,
|
|
||||||
Data: data,
|
|
||||||
}), signer, key1)
|
|
||||||
gen.AddTx(tx2)
|
|
||||||
case 2:
|
|
||||||
data, err := contractABI.Pack("log2", hash2.Big(), hash1.Big())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{
|
|
||||||
Nonce: 2,
|
|
||||||
GasPrice: gen.BaseFee(),
|
|
||||||
Gas: 30000,
|
|
||||||
To: &contract,
|
|
||||||
Data: data,
|
|
||||||
}), signer, key1)
|
|
||||||
gen.AddTx(tx)
|
|
||||||
case 998:
|
|
||||||
data, err := contractABI.Pack("log1", hash3.Big())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{
|
|
||||||
Nonce: 3,
|
|
||||||
GasPrice: gen.BaseFee(),
|
|
||||||
Gas: 30000,
|
|
||||||
To: &contract2,
|
|
||||||
Data: data,
|
|
||||||
}), signer, key1)
|
|
||||||
gen.AddTx(tx)
|
|
||||||
case 999:
|
|
||||||
data, err := contractABI.Pack("log1", hash4.Big())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{
|
|
||||||
Nonce: 4,
|
|
||||||
GasPrice: gen.BaseFee(),
|
|
||||||
Gas: 30000,
|
|
||||||
To: &contract,
|
|
||||||
Data: data,
|
|
||||||
}), signer, key1)
|
|
||||||
gen.AddTx(tx)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
var l uint64
|
|
||||||
bc, err := core.NewBlockChain(db, nil, gspec, nil, ethash.NewFaker(), vm.Config{}, nil, &l)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
_, err = bc.InsertChain(chain)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set block 998 as Finalized (-3)
|
|
||||||
bc.SetFinalized(chain[998].Header())
|
|
||||||
|
|
||||||
// Generate pending block
|
|
||||||
pchain, preceipts := core.GenerateChain(gspec.Config, chain[len(chain)-1], ethash.NewFaker(), db, 1, func(i int, gen *core.BlockGen) {
|
|
||||||
data, err := contractABI.Pack("log1", hash5.Big())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{
|
|
||||||
Nonce: 5,
|
|
||||||
GasPrice: gen.BaseFee(),
|
|
||||||
Gas: 30000,
|
|
||||||
To: &contract,
|
|
||||||
Data: data,
|
|
||||||
}), signer, key1)
|
|
||||||
gen.AddTx(tx)
|
|
||||||
})
|
|
||||||
sys.backend.(*testBackend).pendingBlock = pchain[0]
|
|
||||||
sys.backend.(*testBackend).pendingReceipts = preceipts[0]
|
|
||||||
|
|
||||||
for i, tc := range []struct {
|
|
||||||
f *Filter
|
|
||||||
want string
|
|
||||||
err string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
f: sys.NewBlockFilter(chain[2].Hash(), []common.Address{contract}, nil),
|
|
||||||
want: `[{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696332","0x0000000000000000000000000000000000000000000000000000746f70696331"],"data":"0x","blockNumber":"0x3","transactionHash":"0xdefe471992a07a02acdfbe33edaae22fbb86d7d3cec3f1b8e4e77702fb3acc1d","transactionIndex":"0x0","blockHash":"0x7a7556792ca7d37882882e2b001fe14833eaf81c2c7f865c9c771ec37a024f6b","logIndex":"0x0","removed":false}]`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
f: sys.NewRangeFilter(0, int64(rpc.LatestBlockNumber), []common.Address{contract}, [][]common.Hash{{hash1, hash2, hash3, hash4}}),
|
|
||||||
want: `[{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696331"],"data":"0x","blockNumber":"0x2","transactionHash":"0xa8028c655b6423204c8edfbc339f57b042d6bec2b6a61145d76b7c08b4cccd42","transactionIndex":"0x0","blockHash":"0x24417bb49ce44cfad65da68f33b510bf2a129c0d89ccf06acb6958b8585ccf34","logIndex":"0x0","removed":false},{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696332","0x0000000000000000000000000000000000000000000000000000746f70696331"],"data":"0x","blockNumber":"0x3","transactionHash":"0xdefe471992a07a02acdfbe33edaae22fbb86d7d3cec3f1b8e4e77702fb3acc1d","transactionIndex":"0x0","blockHash":"0x7a7556792ca7d37882882e2b001fe14833eaf81c2c7f865c9c771ec37a024f6b","logIndex":"0x0","removed":false},{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696334"],"data":"0x","blockNumber":"0x3e8","transactionHash":"0x9a87842100a638dfa5da8842b4beda691d2fd77b0c84b57f24ecfa9fb208f747","transactionIndex":"0x0","blockHash":"0xb360bad5265261c075ece02d3bf0e39498a6a76310482cdfd90588748e6c5ee0","logIndex":"0x0","removed":false}]`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
f: sys.NewRangeFilter(900, 999, []common.Address{contract}, [][]common.Hash{{hash3}}),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
f: sys.NewRangeFilter(990, int64(rpc.LatestBlockNumber), []common.Address{contract2}, [][]common.Hash{{hash3}}),
|
|
||||||
want: `[{"address":"0xff00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696333"],"data":"0x","blockNumber":"0x3e7","transactionHash":"0x53e3675800c6908424b61b35a44e51ca4c73ca603e58a65b32c67968b4f42200","transactionIndex":"0x0","blockHash":"0x2e4620a2b426b0612ec6cad9603f466723edaed87f98c9137405dd4f7a2409ff","logIndex":"0x0","removed":false}]`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
f: sys.NewRangeFilter(1, 10, []common.Address{contract}, [][]common.Hash{{hash2}, {hash1}}),
|
|
||||||
want: `[{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696332","0x0000000000000000000000000000000000000000000000000000746f70696331"],"data":"0x","blockNumber":"0x3","transactionHash":"0xdefe471992a07a02acdfbe33edaae22fbb86d7d3cec3f1b8e4e77702fb3acc1d","transactionIndex":"0x0","blockHash":"0x7a7556792ca7d37882882e2b001fe14833eaf81c2c7f865c9c771ec37a024f6b","logIndex":"0x0","removed":false}]`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
f: sys.NewRangeFilter(1, 10, nil, [][]common.Hash{{hash1, hash2}}),
|
|
||||||
want: `[{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696331"],"data":"0x","blockNumber":"0x2","transactionHash":"0xa8028c655b6423204c8edfbc339f57b042d6bec2b6a61145d76b7c08b4cccd42","transactionIndex":"0x0","blockHash":"0x24417bb49ce44cfad65da68f33b510bf2a129c0d89ccf06acb6958b8585ccf34","logIndex":"0x0","removed":false},{"address":"0xff00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696331"],"data":"0x","blockNumber":"0x2","transactionHash":"0xdba3e2ea9a7d690b722d70ee605fd67ba4c00d1d3aecd5cf187a7b92ad8eb3df","transactionIndex":"0x1","blockHash":"0x24417bb49ce44cfad65da68f33b510bf2a129c0d89ccf06acb6958b8585ccf34","logIndex":"0x1","removed":false},{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696332","0x0000000000000000000000000000000000000000000000000000746f70696331"],"data":"0x","blockNumber":"0x3","transactionHash":"0xdefe471992a07a02acdfbe33edaae22fbb86d7d3cec3f1b8e4e77702fb3acc1d","transactionIndex":"0x0","blockHash":"0x7a7556792ca7d37882882e2b001fe14833eaf81c2c7f865c9c771ec37a024f6b","logIndex":"0x0","removed":false}]`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
f: sys.NewRangeFilter(0, int64(rpc.LatestBlockNumber), nil, [][]common.Hash{{common.BytesToHash([]byte("fail"))}}),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
f: sys.NewRangeFilter(0, int64(rpc.LatestBlockNumber), []common.Address{common.BytesToAddress([]byte("failmenow"))}, nil),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
f: sys.NewRangeFilter(0, int64(rpc.LatestBlockNumber), nil, [][]common.Hash{{common.BytesToHash([]byte("fail"))}, {hash1}}),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
f: sys.NewRangeFilter(int64(rpc.LatestBlockNumber), int64(rpc.LatestBlockNumber), nil, nil),
|
|
||||||
want: `[{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696334"],"data":"0x","blockNumber":"0x3e8","transactionHash":"0x9a87842100a638dfa5da8842b4beda691d2fd77b0c84b57f24ecfa9fb208f747","transactionIndex":"0x0","blockHash":"0xb360bad5265261c075ece02d3bf0e39498a6a76310482cdfd90588748e6c5ee0","logIndex":"0x0","removed":false}]`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
f: sys.NewRangeFilter(int64(rpc.FinalizedBlockNumber), int64(rpc.LatestBlockNumber), nil, nil),
|
|
||||||
want: `[{"address":"0xff00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696333"],"data":"0x","blockNumber":"0x3e7","transactionHash":"0x53e3675800c6908424b61b35a44e51ca4c73ca603e58a65b32c67968b4f42200","transactionIndex":"0x0","blockHash":"0x2e4620a2b426b0612ec6cad9603f466723edaed87f98c9137405dd4f7a2409ff","logIndex":"0x0","removed":false},{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696334"],"data":"0x","blockNumber":"0x3e8","transactionHash":"0x9a87842100a638dfa5da8842b4beda691d2fd77b0c84b57f24ecfa9fb208f747","transactionIndex":"0x0","blockHash":"0xb360bad5265261c075ece02d3bf0e39498a6a76310482cdfd90588748e6c5ee0","logIndex":"0x0","removed":false}]`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
f: sys.NewRangeFilter(int64(rpc.FinalizedBlockNumber), int64(rpc.FinalizedBlockNumber), nil, nil),
|
|
||||||
want: `[{"address":"0xff00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696333"],"data":"0x","blockNumber":"0x3e7","transactionHash":"0x53e3675800c6908424b61b35a44e51ca4c73ca603e58a65b32c67968b4f42200","transactionIndex":"0x0","blockHash":"0x2e4620a2b426b0612ec6cad9603f466723edaed87f98c9137405dd4f7a2409ff","logIndex":"0x0","removed":false}]`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
f: sys.NewRangeFilter(int64(rpc.LatestBlockNumber), int64(rpc.FinalizedBlockNumber), nil, nil),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
f: sys.NewRangeFilter(int64(rpc.SafeBlockNumber), int64(rpc.LatestBlockNumber), nil, nil),
|
|
||||||
err: "safe header not found",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
f: sys.NewRangeFilter(int64(rpc.SafeBlockNumber), int64(rpc.SafeBlockNumber), nil, nil),
|
|
||||||
err: "safe header not found",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
f: sys.NewRangeFilter(int64(rpc.LatestBlockNumber), int64(rpc.SafeBlockNumber), nil, nil),
|
|
||||||
err: "safe header not found",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
f: sys.NewRangeFilter(int64(rpc.PendingBlockNumber), int64(rpc.PendingBlockNumber), nil, nil),
|
|
||||||
want: `[{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696335"],"data":"0x","blockNumber":"0x3e9","transactionHash":"0x4110587c1b8d86edc85dce929a34127f1cb8809515a9f177c91c866de3eb0638","transactionIndex":"0x0","blockHash":"0xd5e8d4e4eb51a2a2a6ec20ef68a4c2801240743c8deb77a6a1d118ac3eefb725","logIndex":"0x0","removed":false}]`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
f: sys.NewRangeFilter(int64(rpc.LatestBlockNumber), int64(rpc.PendingBlockNumber), nil, nil),
|
|
||||||
want: `[{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696334"],"data":"0x","blockNumber":"0x3e8","transactionHash":"0x9a87842100a638dfa5da8842b4beda691d2fd77b0c84b57f24ecfa9fb208f747","transactionIndex":"0x0","blockHash":"0xb360bad5265261c075ece02d3bf0e39498a6a76310482cdfd90588748e6c5ee0","logIndex":"0x0","removed":false},{"address":"0xfe00000000000000000000000000000000000000","topics":["0x0000000000000000000000000000000000000000000000000000746f70696335"],"data":"0x","blockNumber":"0x3e9","transactionHash":"0x4110587c1b8d86edc85dce929a34127f1cb8809515a9f177c91c866de3eb0638","transactionIndex":"0x0","blockHash":"0xd5e8d4e4eb51a2a2a6ec20ef68a4c2801240743c8deb77a6a1d118ac3eefb725","logIndex":"0x0","removed":false}]`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
f: sys.NewRangeFilter(int64(rpc.PendingBlockNumber), int64(rpc.LatestBlockNumber), nil, nil),
|
|
||||||
err: errInvalidBlockRange.Error(),
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
logs, err := tc.f.Logs(context.Background())
|
|
||||||
if err == nil && tc.err != "" {
|
|
||||||
t.Fatalf("test %d, expected error %q, got nil", i, tc.err)
|
|
||||||
} else if err != nil && err.Error() != tc.err {
|
|
||||||
t.Fatalf("test %d, expected error %q, got %q", i, tc.err, err.Error())
|
|
||||||
}
|
|
||||||
if tc.want == "" && len(logs) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
have, err := json.Marshal(logs)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if string(have) != tc.want {
|
|
||||||
t.Fatalf("test %d, have:\n%s\nwant:\n%s", i, have, tc.want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("timeout", func(t *testing.T) {
|
|
||||||
f := sys.NewRangeFilter(0, -1, nil, nil)
|
|
||||||
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(-time.Hour))
|
|
||||||
defer cancel()
|
|
||||||
_, err := f.Logs(ctx)
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("expected error")
|
|
||||||
}
|
|
||||||
if err != context.DeadlineExceeded {
|
|
||||||
t.Fatalf("expected context.DeadlineExceeded, got %v", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Loading…
Reference in a new issue