👨🏽‍🎓 Проблема бедной документации Golos API на примере set_block_applied_callback - правильная WebSocket подписка без интервалов-костылей

Пост оформлен в виде урока для начинающих, но будет так же полезен опытным пользователям голоса, поскольку даже в wiki set_block_applied_callback обозначен как таинственный и неиследованный метод :)

Некоторые разработчики писали в чатах - "что это за вебсокеты такие у голоса, которые не могут сами отправлять данные о блоке?", но как выяснилось опытным путем, вебсокеты голоса работают как положено и проблема только в отсутствии документации к главному API методу:
set_block_applied_callback


Я долгое время использовал в корне неправильный подход к способу "слушать блоки" применяя в скриптах интервалы. Как известно, блоки генерируются каждые 3 секунды и было традицией ставить такой же интервал в скриптах для получения актуального блока. Что бы нивелировать разные проблемы транспорта запросов я написал нагромождение таймеров в прошлой версии BlockSnobbery, и хоть это работает отлично, исключены пропуски блоков вашим приложением, такой мой механизм контроля актуального блока использовал слишком много лишних запросов к ноде. Да и сама концепция интервалов походит на какое-то гадание с постоянной проверкой на опоздание или опережение. После очередного недоумения в коментариях "неужели нет способа получать блоки без интервала?" я решил покопаться в исходниках и найти то, что мне нужно.


Как использовать set_block_applied_callback

Этот метод якобы присутствует в golos-js, но там он не работает( issue), возможно просто по причине отсутствия документации к синтаксису. Но поскольку это запрос на чтение из блокчейна- golos-js можно не использовать вообще, будет достаточно пары строк на JS.

Никаких библиотек не нужно и вы можете проексперементировать в консоли своего браузера.
Откройте пустую вкладку и нажмите клавишу F12, затем перейдите на вкладку console

Теперь мы можем вставлять сюда JS, который я предварительно опишу ниже

Так будут выглядеть скрипт на получение операций из блоков:

1.JPG

Он состоит из нескольких функций, а начинается все с подключения к ноде. Подержка websockets встроена в любой современный браузер и нам достаточно лишь указать переменную с паблик нодой
И добавить конструкцию внутри которой происходят события при открытии ноды:
socket.onopen

Внутри конструкции мы добавим функцию подписания на определененное событие из блокчейна,
а именно set_block_applied_callback - уведомление о появлении каждого нового блока на голосе
Делаем это мы функцией socket.send c id:1

После такой "подписки" на события, необходимо добавить функцию, которая и будет принимать сообщения о новых блоках
socket.onmessage

Теперь внутри socket.onmessage мы получаем все сообщения от ноды голоса.
Что бы вывести их добавим внутри функцию
console.log(raw)

После запуска этого скрипта просто в консоли браузера мы станем получать строки, где будут заголовки блоков с голоса, начиная со второго сообщения. Первой строкой был ответ о том, что мы подписались на обновления. У него id 1. У заголовков блоков id нет, но есть method:notice - это понадобится позднее для фильтрации.

Давайте разберемся, какие данные нам отдает блокчейн при использовании set_block_applied_callback. На скрине выше видно, что это сущий мизер, а именно

{"method":"notice","params":[0,[{"previous":"00a20…9de2c5bbe485fc0051df26bbf0cfa6669c6f3d01f5e2"}]]}

Мы получаем только закодированную строку previous.
Что бы получить из этого пользу, нам нужно получить номер блока. Именно он закодирован в этой строке. А если вернее - в первых 8 символах строки в формате heх.

Раскодировать это можно простыми функциями и разбором строк

Распарсим сырые данные в переменную data
var data = JSON.parse(raw.data)

Создадим условие if (если) которое поможет офильтровать другие сообщения и работать толко с теми, метод которых "notice" и в которых есть необходимые данные "data.params"
if (data.method === "notice" && data.params) {

Сохраним первые 8 символов previous в переменню hex
var hex = data.params[1][0].previous.slice(0, 8)

Наконец переведем hex в обычные числа и сохраним в переменную height
Теперь в height номер прошлого блока из блокчейн.
var height = parseInt(hex, 16)

Теперь, зная номер блока, мы можем получить из него данные. Для этого отправим сообщение ноде с запросом get_ops_in_block (получить операции из блока) и присвоим этому сообщению id 2. Ответ на него будет тоже с id 2.

Там же, внутри функции socket.onmessage мы добавим еще одно условие фильтр
"если id сообщения = 2" и выведем на экран функцией console.log(data.result)

} else if (data.id === 2) {
            console.log(data.result)
}

Вставим весь код в консоль браузера

Нажмем ввод и начнем получать стрим операций из блоков

Именно это и есть основа многих ботов, которые работает с данными блокчейна в реальном времени!
Вы можете отфильтровать операции по типу и работать с ними как вам захочется
Например вместо функции console.log(data.result) мы вызовем функцию opfilter(data.result)

Ниже мы создадим функцию opfilter и в ней будем фильровать операции из блоков по типу

Операции группированы по типу, например голоса и флаги - это операция vote c положительным числом для голос, отрицательным для флага и нулевым для отмены голоса.

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

На скрине ниже 3 условия для одной операции VOTE , в первом для апвота мы используем
type === "vote" && o.weight > 0

Для флага weight будет меньше 0
Ну и для снятия голоса или флага weight равен 0

Схожим образом обрабатывается и операция COMMENT которая общая для постов и комментов.
Отличить коммент от поста можно по автору-родителю. У комментария он есть. У постов - пуст.

Всего типов операций достаточно много и метод их фильтра тема отдельного поста.
Список операций:

fill_convert_request author_reward curation_reward comment_reward liquidity_reward interest fill_vesting_withdraw fill_order shutdown_witness fill_transfer_from_savings hardfork comment_payout_update vote comment transfer transfer_to_vesting withdraw_vesting limit_order_create limit_order_cancel feed_publish convert account_create account_update witness_update account_witness_vote account_witness_proxy pow custom report_over_production delete_comment custom_json comment_options set_withdraw_vesting_route limit_order_create2 challenge_authority prove_authority request_account_recovery recover_account change_recovery_account escrow_transfer escrow_dispute escrow_release pow2 escrow_approve transfer_to_savings transfer_from_savings cancel_transfer_from_savings custom_binary decline_voting_rights reset_account set_reset_account

На выше скрине есть функция ins (сокращение от insert) в которую мы передаем обработанные данные операции.

Функция выводит их в консоль

Теперь весь код выглядит так

var socket = new WebSocket('wss://api.golos.cf')

socket.onopen = function(event) {

    socket.send(JSON.stringify({
        id: 1,
        method: 'call',
        "params": ["database_api", "set_block_applied_callback", [0], ]
    }));

    socket.onmessage = function(raw) {

        var data = JSON.parse(raw.data)
        if (data.method === "notice" && data.params) {

            var hex = data.params[1][0].previous.slice(0, 8)
            var height = parseInt(hex, 16)
            
            socket.send(JSON.stringify({
                id: 2,
                method: 'call',
                params: ["database_api", "get_ops_in_block", [height, "false"]]
            }));

        } else if (data.id === 2) {
            opfilter(data.result)
        }
    }
}
    
    opfilter = function(d){
    for (var i = 0; i < d.length; ++i) {
        var b = d[i].block, tx = d[i].trx_id, t = d[i].timestamp, type = d[i].op[0],pre="", o = d[i].op[1];
            if(type === "limit_order_create")    ins(pre+"  Сделка  @"+o.owner+":  обмен  "+o.amount_to_sell+" на "+o.min_to_receive);
            if(type === "comment"&&!o.parent_author)    ins(pre+" Пост  @"+o.author+":   "+o.title);
            if(type === "comment"&&o.parent_author)    ins(pre+" Комментарий  @"+o.author+"  к посту @"+o.parent_author);
            if(type === "vote" && o.weight > 0)    ins("  Голос  "+o.weight/100 +"%  От @"+o.voter+"  для @"+o.author);
            if(type === "vote" && o.weight < 0)    ins(pre+"  Флаг -"+o.weight/100 +"%  От @"+o.voter+"  для @"+o.author);
            if(type === "vote" && o.weight === 0)    ins(pre+"⭕️ Отмена голоса от @"+o.voter+" за пост автора @"+o.author);  
            if(type === "pow")    ins(pre+"⛏   Майнер доказал работу "+o.miner);
            if(type === "transfer")    ins(pre+" Трансфер от @"+o.from+"   "+o.amount+"  для "+o.to);
            if(type === "transfer_to_vesting")    ins(pre+" Повышение Силы Голоса от @"+o.from+"   "+o.amount+"  для "+o.to);
            if(type === "account_create")    ins(pre+"  Новый аккаунт от "+o.creator+":   @"+o.new_account_name);
            if(type === "account_update")    ins(pre+"♻️  Аккаунт отредактирован @"+o.account+"  "+o.json_metadata);
            if(type === "account_witness_vote")    ins(pre+" Голос за делегата  от "+o.account+". Делегат  @"+o.witness);
            if(type === "feed_publish")    ins(pre+" Прайсфид   от "+o.publisher+"   "+o.exchange_rate.base+"/"+o.exchange_rate.quote);
            if(type === "curation_reward")    ins(pre+" Кураторские награды "+o.curator+"  "+o.reward+"  "+o.comment_author);
            if(type === "author_reward")    ins(pre+" Авторские награды "+o.author+" "+o.sbd_payout+" "+o.vesting_payout+" "+o.steem_payout+" " +o.permlink );  
    }
}

ins = function(x){
    console.log(x)
    }

Запустим его и увидим магию - события в блокчейне голоса в реальном времени

Это можно все офомить в виде вебстраницы, пример https://golos.cf/ops.html

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


Эта и еще несколько нароботок будут внедрены в обновление ботов для голосования. Пока боты работают на старой версии.

голосapiпрограмированиеобразованиеблокчейн
25%
11
533
672.995 GOLOS
0
В избранное
VIK
Обратная связь в телеграм чате @chain_cf
533
0

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

Авторы получают вознаграждение, когда пользователи голосуют за их посты. Голосующие читатели также получают вознаграждение за свои голоса.

Зарегистрироваться
Комментарии (9)
Сортировать по:
Сначала старые