diff --git a/eth/filters/filter.go b/eth/filters/filter.go
index a695d7eb7d8012febf0a38ddf70464f87b54e102..76ca86524da801907523f37f322f7ee6c39889d2 100644
--- a/eth/filters/filter.go
+++ b/eth/filters/filter.go
@@ -54,6 +54,8 @@ type Filter struct {
 
 // New creates a new filter which uses a bloom filter on blocks to figure out whether
 // a particular block is interesting or not.
+// MipMaps allow past blocks to be searched much more efficiently, but are not available
+// to light clients.
 func New(backend Backend, useMipMap bool) *Filter {
 	return &Filter{
 		backend:   backend,
@@ -85,8 +87,11 @@ func (f *Filter) SetTopics(topics [][]common.Hash) {
 	f.topics = topics
 }
 
-// Run filters logs with the current parameters set
-func (f *Filter) Find(ctx context.Context) ([]*vm.Log, error) {
+// FindOnce searches the blockchain for matching log entries, returning
+// all matching entries from the first block that contains matches,
+// updating the start point of the filter accordingly. If no results are
+// found, a nil slice is returned.
+func (f *Filter) FindOnce(ctx context.Context) ([]*vm.Log, error) {
 	head, _ := f.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber)
 	if head == nil {
 		return nil, nil
@@ -106,47 +111,69 @@ func (f *Filter) Find(ctx context.Context) ([]*vm.Log, error) {
 	// uses the mipmap bloom filters to check for fast inclusion and uses
 	// higher range probability in order to ensure at least a false positive
 	if !f.useMipMap || len(f.addresses) == 0 {
-		return f.getLogs(ctx, beginBlockNo, endBlockNo)
+		logs, blockNumber, err := f.getLogs(ctx, beginBlockNo, endBlockNo)
+		f.begin = int64(blockNumber + 1)
+		return logs, err
 	}
-	return f.mipFind(beginBlockNo, endBlockNo, 0), nil
+
+	logs, blockNumber := f.mipFind(beginBlockNo, endBlockNo, 0)
+	f.begin = int64(blockNumber + 1)
+	return logs, nil
 }
 
-func (f *Filter) mipFind(start, end uint64, depth int) (logs []*vm.Log) {
+// Run filters logs with the current parameters set
+func (f *Filter) Find(ctx context.Context) (logs []*vm.Log, err error) {
+	for {
+		newLogs, err := f.FindOnce(ctx)
+		if len(newLogs) == 0 || err != nil {
+			return logs, err
+		}
+		logs = append(logs, newLogs...)
+	}
+}
+
+func (f *Filter) mipFind(start, end uint64, depth int) (logs []*vm.Log, blockNumber uint64) {
 	level := core.MIPMapLevels[depth]
 	// normalise numerator so we can work in level specific batches and
 	// work with the proper range checks
 	for num := start / level * level; num <= end; num += level {
 		// find addresses in bloom filters
 		bloom := core.GetMipmapBloom(f.db, num, level)
+		// Don't bother checking the first time through the loop - we're probably picking
+		// up where a previous run left off.
+		first := true
 		for _, addr := range f.addresses {
-			if bloom.TestBytes(addr[:]) {
+			if first || bloom.TestBytes(addr[:]) {
+				first = false
 				// range check normalised values and make sure that
 				// we're resolving the correct range instead of the
 				// normalised values.
 				start := uint64(math.Max(float64(num), float64(start)))
 				end := uint64(math.Min(float64(num+level-1), float64(end)))
 				if depth+1 == len(core.MIPMapLevels) {
-					l, _ := f.getLogs(context.Background(), start, end)
-					logs = append(logs, l...)
+					l, blockNumber, _ := f.getLogs(context.Background(), start, end)
+					if len(l) > 0 {
+						return l, blockNumber
+					}
 				} else {
-					logs = append(logs, f.mipFind(start, end, depth+1)...)
+					l, blockNumber := f.mipFind(start, end, depth+1)
+					if len(l) > 0 {
+						return l, blockNumber
+					}
 				}
-				// break so we don't check the same range for each
-				// possible address. Checks on multiple addresses
-				// are handled further down the stack.
-				break
 			}
 		}
 	}
 
-	return logs
+	return nil, end
 }
 
-func (f *Filter) getLogs(ctx context.Context, start, end uint64) (logs []*vm.Log, err error) {
+func (f *Filter) getLogs(ctx context.Context, start, end uint64) (logs []*vm.Log, blockNumber uint64, err error) {
 	for i := start; i <= end; i++ {
-		header, err := f.backend.HeaderByNumber(ctx, rpc.BlockNumber(i))
+		blockNumber := rpc.BlockNumber(i)
+		header, err := f.backend.HeaderByNumber(ctx, blockNumber)
 		if header == nil || err != nil {
-			return logs, err
+			return logs, end, err
 		}
 
 		// Use bloom filtering to see if this block is interesting given the
@@ -155,17 +182,20 @@ func (f *Filter) getLogs(ctx context.Context, start, end uint64) (logs []*vm.Log
 			// Get the logs of the block
 			receipts, err := f.backend.GetReceipts(ctx, header.Hash())
 			if err != nil {
-				return nil, err
+				return nil, end, err
 			}
 			var unfiltered []*vm.Log
 			for _, receipt := range receipts {
 				unfiltered = append(unfiltered, ([]*vm.Log)(receipt.Logs)...)
 			}
-			logs = append(logs, filterLogs(unfiltered, nil, nil, f.addresses, f.topics)...)
+			logs = filterLogs(unfiltered, nil, nil, f.addresses, f.topics)
+			if len(logs) > 0 {
+				return logs, uint64(blockNumber), nil
+			}
 		}
 	}
 
-	return logs, nil
+	return logs, end, nil
 }
 
 func includes(addresses []common.Address, a common.Address) bool {