diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index 16cde0b1929f..c930e3c7657c 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -527,6 +527,10 @@ func (fb *filterBackend) HeaderByNumber(ctx context.Context, block rpc.BlockNumb return fb.bc.GetHeaderByNumber(uint64(block.Int64())), nil } +func (fb *filterBackend) HeaderByHash(ctx context.Context, blockHash common.Hash) (*types.Header, error) { + return fb.bc.GetHeaderByHash(blockHash), nil +} + func (fb *filterBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) { return core.GetBlockReceipts(fb.db, hash, core.GetBlockNumber(fb.db, hash)), nil } diff --git a/eth/filters/api.go b/eth/filters/api.go index aef9adba103f..ac1330a18663 100644 --- a/eth/filters/api.go +++ b/eth/filters/api.go @@ -35,6 +35,13 @@ import ( "github.com/XinFinOrg/XDPoSChain/rpc" ) +var ( + errExceedMaxTopics = errors.New("exceed max topics") +) + +// The maximum number of topic criteria allowed, vm.LOG4 - vm.LOG0 +const maxTopics = 4 + var ( deadline = 5 * time.Minute // consider a filter inactive if it has not been polled for within deadline ) @@ -99,7 +106,7 @@ func (api *PublicFilterAPI) timeoutLoop() { // NewPendingTransactionFilter creates a filter that fetches pending transaction hashes // as transactions enter the pending state. // -// It is part of the filter package because this filter can be used throug the +// It is part of the filter package because this filter can be used through the // `eth_getFilterChanges` polling method that is also used for log filters. // // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newpendingtransactionfilter @@ -269,14 +276,8 @@ func (api *PublicFilterAPI) Logs(ctx context.Context, crit FilterCriteria) (*rpc } // FilterCriteria represents a request to create a new filter. -// -// TODO(karalabe): Kill this in favor of ethereum.FilterQuery. -type FilterCriteria struct { - FromBlock *big.Int - ToBlock *big.Int - Addresses []common.Address - Topics [][]common.Hash -} +// 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 @@ -327,15 +328,42 @@ func (api *PublicFilterAPI) NewFilter(crit FilterCriteria) (rpc.ID, error) { // // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getlogs func (api *PublicFilterAPI) GetLogs(ctx context.Context, crit FilterCriteria) ([]*types.Log, error) { - // Convert the RPC block numbers into internal representations - if crit.FromBlock == nil { - crit.FromBlock = big.NewInt(rpc.LatestBlockNumber.Int64()) + if len(crit.Topics) > maxTopics { + return nil, errExceedMaxTopics } - if crit.ToBlock == nil { - crit.ToBlock = big.NewInt(rpc.LatestBlockNumber.Int64()) + + var ( + fromBlock int64 + toBlock int64 + ) + + if crit.BlockHash != nil { + // look up block number from block hash + header, err := api.backend.HeaderByHash(ctx, *crit.BlockHash) + if err != nil { + return nil, err + } + if header == nil { + return nil, errors.New("unknown block") + } + fromBlock = int64(header.Number.Int64()) + toBlock = fromBlock + } else { + // Convert the RPC block numbers into internal representations + if crit.FromBlock == nil { + fromBlock = int64(rpc.LatestBlockNumber) + } else { + fromBlock = crit.FromBlock.Int64() + } + if crit.ToBlock == nil { + toBlock = int64(rpc.LatestBlockNumber) + } else { + toBlock = crit.ToBlock.Int64() + } } + // Create and run the filter to get all the logs - filter := New(api.backend, crit.FromBlock.Int64(), crit.ToBlock.Int64(), crit.Addresses, crit.Topics) + filter := New(api.backend, fromBlock, toBlock, crit.Addresses, crit.Topics) logs, err := filter.Logs(ctx) if err != nil { @@ -451,7 +479,8 @@ func returnLogs(logs []*types.Log) []*types.Log { // UnmarshalJSON sets *args fields with given data. func (args *FilterCriteria) UnmarshalJSON(data []byte) error { type input struct { - From *rpc.BlockNumber `json:"fromBlock"` + BlockHash *common.Hash `json:"blockHash"` + FromBlock *rpc.BlockNumber `json:"fromBlock"` ToBlock *rpc.BlockNumber `json:"toBlock"` Addresses interface{} `json:"address"` Topics []interface{} `json:"topics"` @@ -462,12 +491,20 @@ func (args *FilterCriteria) UnmarshalJSON(data []byte) error { return err } - if raw.From != nil { - args.FromBlock = big.NewInt(raw.From.Int64()) - } + if raw.BlockHash != nil { + if raw.FromBlock != nil || raw.ToBlock != nil { + // BlockHash is mutually exclusive with FromBlock/ToBlock criteria + return fmt.Errorf("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()) + if raw.ToBlock != nil { + args.ToBlock = big.NewInt(raw.ToBlock.Int64()) + } } args.Addresses = []common.Address{} diff --git a/eth/filters/filter.go b/eth/filters/filter.go index 0137e44cfa58..8780a4997d18 100644 --- a/eth/filters/filter.go +++ b/eth/filters/filter.go @@ -33,6 +33,7 @@ type Backend interface { ChainDb() ethdb.Database EventMux() *event.TypeMux HeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Header, error) + HeaderByHash(ctx context.Context, blockHash common.Hash) (*types.Header, error) GetReceipts(ctx context.Context, blockHash common.Hash) (types.Receipts, error) GetLogs(ctx context.Context, blockHash common.Hash) ([][]*types.Log, error) diff --git a/eth/filters/filter_system_test.go b/eth/filters/filter_system_test.go index edcba2e90ab8..6d7129a1d1ae 100644 --- a/eth/filters/filter_system_test.go +++ b/eth/filters/filter_system_test.go @@ -19,7 +19,6 @@ package filters import ( "context" "fmt" - "github.com/XinFinOrg/XDPoSChain/core/rawdb" "math/big" "math/rand" "reflect" @@ -31,6 +30,7 @@ import ( "github.com/XinFinOrg/XDPoSChain/consensus/ethash" "github.com/XinFinOrg/XDPoSChain/core" "github.com/XinFinOrg/XDPoSChain/core/bloombits" + "github.com/XinFinOrg/XDPoSChain/core/rawdb" "github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/ethdb" "github.com/XinFinOrg/XDPoSChain/event" @@ -69,6 +69,11 @@ func (b *testBackend) HeaderByNumber(ctx context.Context, blockNr rpc.BlockNumbe return core.GetHeader(b.db, hash, num), nil } +func (b *testBackend) HeaderByHash(ctx context.Context, blockHash common.Hash) (*types.Header, error) { + num := core.GetBlockNumber(b.db, blockHash) + return core.GetHeader(b.db, blockHash, num), nil +} + func (b *testBackend) GetReceipts(ctx context.Context, blockHash common.Hash) (types.Receipts, error) { number := core.GetBlockNumber(b.db, blockHash) return core.GetBlockReceipts(b.db, blockHash, number), nil @@ -335,6 +340,33 @@ func TestInvalidLogFilterCreation(t *testing.T) { } } +func TestInvalidGetLogsRequest(t *testing.T) { + var ( + mux = new(event.TypeMux) + db = rawdb.NewMemoryDatabase() + txFeed = new(event.Feed) + rmLogsFeed = new(event.Feed) + logsFeed = new(event.Feed) + chainFeed = new(event.Feed) + backend = &testBackend{mux, db, 0, txFeed, rmLogsFeed, logsFeed, chainFeed} + api = NewPublicFilterAPI(backend, 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())}, + } + + for i, test := range testCases { + if _, err := api.GetLogs(context.Background(), test); err == nil { + t.Errorf("Expected Logs for case #%d to fail", i) + } + } +} + // TestLogFilter tests whether log filters match the correct logs that are posted to the event feed. func TestLogFilter(t *testing.T) { t.Parallel() diff --git a/interfaces.go b/interfaces.go index 665a74d2325e..88cf2fcb0c37 100644 --- a/interfaces.go +++ b/interfaces.go @@ -132,6 +132,7 @@ type ContractCaller interface { // FilterQuery contains options for contract log filtering. type FilterQuery struct { + BlockHash *common.Hash // used by eth_getLogs, return logs only from block with this hash FromBlock *big.Int // beginning of the queried range, nil means genesis block ToBlock *big.Int // end of the range, nil means latest block Addresses []common.Address // restricts matches to events created by specific contracts