Всем лучи добра, последнее время у меня очень много работы над @dobryj.kit. Времени писать статейки нет. Вот решил отвлечься и написать про один подводный камень, с которым мы столкнулись во время разработки.
В основе кита лежит не один прочитанный исходник. Как уже говорил в прошлых статьях - документации перелопачено тоже не мало.
@dobryj.kit переписывался с нуля несколько раз, вот и совсем недавно, мы перепиливали очередной механизм.
Для финансовой части, по началу, мы решили использовать функцию getAccountHistory
из библиотеки steem.js
. Функция идеально подходит для этого - ведь в ней отображаются все действий, когда либо произведенные с аккаунтом @dobryj.kit
Функция возвращает максимально 2000 записей и принимает 4 параметра
steem.api.getAccountHistory (login, from, limit, callback)
- login - соответственно login участника системы Голос, историю которого мы хотим получить
- from - номер записи, от которой мы будем получать историю
- limit - количество строк, которые будут в результате (но не более 2000)
- callback - функция, которая будет запущена, после получения данных
Важно понимать, что эта функция работает не совсем по стандартам логики. Мы указываем с какой записи мы будем отсчитывать limit записей к 0. То есть,
steem.api.getAccountHistory ('@dobryj.kit', 5, 5, function(err, history){})
означает, что мы будем с 5 строки получать 5 строк уменьшая шаг - итогом будет массив [0,1,2,3,4,5]. Опытный читатель заметил, что вернется 6 записей вместо 5. Но если limit указать 4.
steem.api.getAccountHistory ('@dobryj.kit', 5, 4, function(err, history){})
вернется 5 записей [1,2,3,4,5] - исчезнет 0 запись - это говорит о том, что в выборку не попадет запись о создании аккаунта, но она нам и не нужна.
Итак, когда мы определились с логикой, и знаем ограничения - как же получить всю историю?
Во первых, нам нужно определить максимальный номер транзакции из истории нашего аккаунта. Для этого необходимо указать в from блок, который больше текущего блока и в limit указать 0.
Мы делаем такой запрос:
steem.api.getAccountHistory ('@dobryj.kit', 800000000000, 0, function(err, history){})
Это вернет нам последнюю транзакцию из истории.
Теперь, нам необходимо - составить "очередь" для загрузки истории. Алгоритм такой
- получаем максимальный блок из истории
- если он меньше 2000, то получаем историю
- если больше 2000 будем получать по 2000 от максимального блока
var currentID = maxID;
var queue = []; //очередь
if (currentID > 2000) {
var i = 0;
while (currentID > 0) {
currentID -= 2000;
i += 2000;
if (currentID >= 0) {
ueue.push({
from: i,
limit: 1999
})
} else {
queue.push({
from: maxID,
limit: currentID + 1999
})
}
}
}
Итогом будет массив типа
[
[{from:2000, limit:1999}],
[{from:4000, limit 1999}],
[{from 4124, limit 123}]
]
Вот она наша "очередь" запросов.
Далее, простым обходом массива запускаем "получатор"истории
queue.forEach(function(element){
steem.api.getAccountHistory('@dobryj.kit', element.from, element.limit, function(err, history){
//обработка транзакций истории
})
});
Выглядит не сложно. Но тут есть подводные камни. Как многие из нас знают - блоки, генерируемые блокчейном бывают нескольких типов. До того как делегат подпишет блок, данные в нем могут измениться. Поэтому в работе кита, мы используем только подписанные блоки.
Предупреждение - все что будет изложено дальше, всего лишь догадки автора, т.е. меня, если я в чем то не прав, прошу меня поправить.
Вы когда нибудь использовали Golos Api Explorer? При изучении механики работы я частенько изучал блоки и натыкался на блоки, похожие на блок по ссылке выше.
Видите, он фактически пустой и не несет в себе никакой информации - неужели в момент простоя генерируются подобные блоки?
Если мы взглянем в инструмент от @ropox и @asuleymanov - Steem.js GUI то увидим подобную картину - результат блока - пустой массив.
Итак, откуда эти блоки? Я поразмыслил и понял, что в момент подписания блока - подписываемый блок затирается, а вся информация, находившаяся в нем, переходит в новый блок. То есть, история, которую я только что получил - может изменить номер блока через несколько минут.
Мои наблюдения подтвердились, когда у нас начала расходиться история, накопленная парсером и история, загруженная из истории (простите за тавтологию).
Данную особенность можно контролировать с помощью функции getDynamicGlobalProperties
, а точнее ее параметром last_irreversible_block_num
, который возвращает номер максимального не редактируемого блока.
Поэтому, мы используем другой подход в получении истории, о котором я расскажу в дальнейшем, если, конечно, вам будет интересно.
Спасибо за внимание
Подписывайтесь на мой блог и да прибудет с вами сила!
Ваш пост поддержали следующие Инвесторы Сообщества "Добрый кит":
t3ran13, rbrown, cryptokom, zoss, dr-boo, lenarius, pioner777888, ropox, ladyzarulem, gryph0n, exan, osincevata, boltyn, kvg, aivanouski, oksana0407, vika-teplo, sva-lana, ani.vartanova, m0rte
Поэтому я тоже проголосовал за него!
Если Вы проголосуете за этот комментарий, то поможете сделать "Доброго Кита" сильнее!
@exan, Поздравляю!
Ваш пост был упомянут в моем хит-параде в следующей категории:
да, и не говори) как минное поле)
Интересно! Жгите дальше -)
большинство из написанного не поняла, но уж больно мне нравится мордашка Китенка! ))
То есть если из истории брать только с элементы с блоком <= last_irreversible_block_num, то все будет нормально! То-то у добробота иногда небольшие расхождения получались. Блин, а я грешил на код, а оно оказалось.
Классно, пойду поправлю тогда добробота.
Эх, а сколько накопленного хронического недосыпа стоили нам с @exan эти знания :)
Вот нашел подобное на стимите https://steemit.com/steem/@dantheman/how-to-monitor-an-account-for-deposits
Так что да, что бы быть уверенным, надо проверять последний irreversible блок, а не просто последний. Буду так и делать в доброботе. Должно добавить стабильности.
Кстати и @vik должен по идее своего робота поправить. Он тоже завязан на head block, хотя там и не так критично. Иногда @robot кажется по два раза информирует о подписках и тому подобному и возможно от туда ноги ростут.
А я откупался все время. Если обнаруживал недостачу на балансе у добробота, закидывал монет на балансы пользователей, которые в минус ушли.)) Там обычно разброс в копейках измеряется.
Я не использую
api.getAccountHistory
, у меня слушаются last_blockХотя раньше я использовал именно
last_irreversible_block_num
, но потом отказался по причине того, что он крадет десятки секунд, а мне нужна моментальная реакция ботов на действия кураторов и т.п.:)Не помню, как в @robot, но в телеграм ботах примерно так:
getDynamicGlobalProperties > head_block_number
Потом
getBlock, который head_block_number
С интервалом 3e3 +1 к getBlock и если блок не удалось получить следующий getBlock не увеличивается, а повторные попытки идут, пока блок не будет получен, таким образом блоки не дублируются и не пропускаются.
Так вот тут и как раз засада. Если читать head_block, то есть вероятность, что содержимое блока поменяется.
Кто то оставил комментарий. Блок создал один из делегатов, подписал, но этот блок может еще поменяться. Вот в чем фишка. Я так понял эту проблему.
Как только достаточное количество делегатов заапрувит этот блок, тогда он становится irreversible и уже больше не поменяется.
Я конечно думаю, что пустые блоки тут не причем, но все таки имеет смысл читать не head_block_number, а last_irreversible_block_num. Он отстает примерно на 45 секунд от головного блока и увеличивается рывками, по нескольку номеров сразу.
Да, я понимаю, что у робота, это не проблема, но где монеты, лучше перестаховаться.
Сорри, про last_irreversible_block_num просмотрел у тебя в посте.
С нашим "Банком" откупаться можно, но скоро без штанов остаться можно будет :)
@zoss , это да - я до сих пор спать хочу=)