15 января 2012 г.

Предлагаю уйти в отставку...

image


Обнаружение бага на предвыборном сайте Путина

В этом посте я расскажу о процессе и результатах тестирования сервиса предложений избирателей на восстановленном после сбоя сайте putin2012.ru и расскажу о найденной ошибке, из-за которой неправильно учитываются голоса за некоторые из предложений.

image


Предыстория


Два дня назад был запущен сайт кандидата в президенты России Владимира Путина — putin2012.ru. К сожалению, в тот же день часть предложений избирателей пропала. Например, пропало это предложение:

image

Часть предложений на сайте осталась, но не отображалась в общем списке.

Как разъяснил пресс-секретарь премьера, произошел сбой системы. К сожалению, господин Песков не рассказал про технические подробности этого сбоя, а именно, почему сбой коснулся только негативных предложений.

Впрочем, принимая во внимание заявления российских чиновников касательно недавних митингов или, например, падения «Фобоса», нельзя не предположить, что причиной сбоя сайта putin2012.ru могут быть действия американских спецслужб.

В любом случае, отечественные специалисты достойно справились с проблемой и вскоре исчезнувшие предложения были восстановлены. Практически без изменений:

image

Зная, что некоторые баги, возникшие в результате сбоя, могли быть не обнаружены, я решил проверить систему голосования.

Голосование за предложения избирателей


На сайте есть возможность добавлять предложения кандидату в президенты, просматривать их по дате публикации или по популярности и голосовать «за» или «против».

Механизм голосования достаточно тривиальный. Рассмотрим его на примере самого популярного предложения putin2012.ru/suggestions/203

203 — идентификатор, при нажатии на "+" или "-" отправляется POST запрос по адресу
putin2012.ru/suggestions/203/like
или
putin2012.ru/suggestions/203/dislike
соответственно. Ответом будет json вида:

{"status": "success", "message": "\u0412\u0430\u0448 \u0433\u043e\u043b\u043e\u0441 \u043f\u0440\u0438\u043d\u044f\u0442", "dislikes": "31%", "likes": "69%"}

status (success или fail) — учтен ли голос, dislikes и likes — новые значения показателей "-" и "+".

После успешного голосования ставится кука vote-hash, которая не позволит проголосовать еще раз за одно и то же предложение: получим status = fail и алерт о том, что мы уже голосовали.

Голосовать без куки можно несколько раз, после чего вновь status = fail и алерт о невозможности проголосовать с нашего ip в течение одной минуты.

Ограничений для ip по географии нет.

Тестирование


Т.к. сбой коснулся только части предложений, я решил проверить, учитываются ли голоса правильно и одинаково для всех предложений. Для этого я попытался зафиксировать процесс изменения рейтинга и количество успешных голосов, необходимых для его изменения.

В результате получился скрипт:

<?php
set_time_limit(0);

header('Content-Type: text/html; charset=utf-8');

$id = 203; // id предложения

$proxy = file('proxy.txt'); // Файл с проксями
$current_proxy = 0;
$last_proxy = count($proxy);

$success_votes = 0; // Счетчик успешных голосований

$like_changes = -1; // Количество зафиксированных изменений рейтинга. После первого успешного голосования он меняется на 0

$last_like = '';

while( $success_votes < 500 && $like_changes < 2 ) { // Работаем до тех пор пока мы не проголосовали успешно 500 раз или не зафиксировали 2 изменения рейтинга
    $data = vote($id, $proxy[$current_proxy]);

    if ($data != '') {
        $json = array();
        $json = json_decode($data, true);

        if (isset($json['status']) && isset($json['likes'])) {
            if ($json['status'] == 'success') {
                
                $success_votes++;
                
                if($last_like != $json['likes']) {
                    $last_like = $json['likes'];
                    $like_changes++;
                }
                
                echo $success_votes . ') Процент лайков: ' . $json['likes'] . '<br />';
                
            }
        }
    }

    $current_proxy++;
    if ($current_proxy == $last_proxy) $current_proxy = 0;
}

function vote($suggestion_id, $proxy = '') { // Запрос на голосование
    $ch = curl_init();
    $timeout = 3;

    curl_setopt($ch, CURLOPT_URL, 'http://putin2012.ru/suggestions/' . $suggestion_id . '/like');
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, '');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, '1');
    curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
    curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
    curl_setopt($ch, CURLOPT_HTTPHEADER, array(
        "Accept" => "application/json",
        "Accept-Charset" => "windows-1251,utf-8;q=0.7,*;q=0.7",
        "Accept-Encoding" => "gzip, deflate",
        "Accept-Language" => "ru-ru,ru;q=0.8,en-us;q=0.5,en;q=0.3",
        "Connection" => "keep-alive",
        "Content-Length" => "0",
        "Content-Type" => "application/x-www-form-urlencoded; charset=utf-8",
        "DNT" => "1",
        "Host" => "putin2012.ru",
        "Referer" => 'http://putin2012.ru/suggestions/' . $suggestion_id,
        "User-Agent" => "Mozilla/5.0 (Windows NT 6.1; rv:9.0.1) Gecko/20100101 Firefox/9.0.1",
        "X-Request" => "JSON",
        "X-Requested-With" => "XMLHttpRequest"
    ));
    if ($proxy != '') {
        curl_setopt($ch, CURLOPT_PROXY, trim($proxy));
    }

    $html = curl_exec($ch);
    curl_close($ch);

    return $html;
}

?>


Если в коде что-то не так, прошу сильно не бить, а указать на недочеты, чтобы я мог прокачивать скилл программирования.

В качестве подопытных предложений были выбраны самое популярное не негативное (1-е место в общем рейтинге) и самое популярное негативное (10-е место) предложения.

Рейтинг первого несколько часов до запуска скрипта был 67% и не менялся.
Рейтинг второго несколько часов до запуска скрипта был 31% и не менялся.

putin2012. Итоги.


Для предложения «Льготный кредит для многодетных на покупку жилья от государства» я получил следующие результаты:


7) Процент лайков: 67%
8) Процент лайков: 68%

84) Процент лайков: 68%
85) Процент лайков: 69%


Т.е. 76 успешных голосов понадобилось, чтобы увеличить рейтинг предложения с 67 до 69 процентов. На момент публикации этого поста его рейтинг не изменился — 69%, хотя предложение находится на самом кликабильном месте.

С предложением «Предлагаю уйти в отставку!» получилось иначе:

1) Процент лайков: 31%
2) Процент лайков: 31%

499) Процент лайков: 31%
500) Процент лайков: 31%


Т.е. 500 голосований «за» никак не изменили его рейтинг, хотя судя по работе с первым предложением, учитывая отсутствие изменений рейтинга до и после отработки, скрипт должен был увеличить рейтинг второго предложения.

Более того, пока я писал статью на Хабр, рейтинг второго предложения снизился до 30% и вновь замер, что опять же учитывая кол-во плюсов, отданных за него скриптом, невозможно при нормальной системе подсчета голосов.

Чтобы очистить карму за 500 голосов «накрученных» негативному предложению, я запустил скрипт для нескольких не негативных предложений и он достаточно быстро изменил их рейтинг.

Таким образом на сайте putin2012.ru обнаружен баг, из-за которого популярность негативных и не негативных предложений избирателей считается по разному.

Надеюсь, что люди, занимающиеся технической поддержкой сайта, прочитают статью и исправят это в скором времени.

Upd: Как справедливо отметили в комментариях здесь и здесь, изменения рейтинга второго предложения могло не быть из-за большого абсолютного кол-ва голосов. Не спорю, но я исхожу из предположения, что у находящегося на лучшем месте по кликабильности (первое по полулярности) предложения голосов будет больше, чем у предложения с худшей кликабельностью (негативное, вторая страница).


Комментариев нет:

Отправить комментарий