code: func (f *Filter) Logs(ctx context.Context) ([]*types.Log, error) {
  	// If we're doing singleton block filtering, execute and return
  	if f.block != (common.Hash{}) {
  		header, err := f.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)
  	}
  	// Figure out the limits of the filter range
  	header, _ := f.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber)
  	if header == nil {
  		return nil, nil
  	}
  	head := header.Number.Uint64()
  
  	if f.begin == -1 {
  		f.begin = int64(head)
  	}
  	end := uint64(f.end)
  	if f.end == -1 {
  		end = head
  	}
  	// Gather all indexed logs, and finish with non indexed ones
  	var (
  		logs []*types.Log
  		err  error
  	)
  	size, sections := f.backend.BloomStatus()
  	if indexed := sections * size; indexed > uint64(f.begin) {
  		if indexed > end {
  			logs, err = f.indexedLogs(ctx, end)
  		} else {
  			logs, err = f.indexedLogs(ctx, indexed-1)
  		}
  		if err != nil {
  			return logs, err
  		}
  	}
  	rest, err := f.unindexedLogs(ctx, end)
  	logs = append(logs, rest...)
  	return logs, err
  }
docs: // Logs searches the blockchain for matching log entries, returning all from the
  // first block that contains matches, updating the start of the filter accordingly.