🎓 Golos JS - Формирование транзакций с несколькими операциями соблюдая TaPoS, вычисление ref_block_prefix в браузере без NodeJS, сервера и модуля Buffer
Недавно я обновил форму постинга golos.cf/md и добавил в нее возможность добавлять бенефициаров и другие comment_options
В библиотеке golos-js есть два отдельных метода, один для размещения самого поста golos.broadcast.comment()
, второй для настройки опций поста, в т.ч. бенефициаров golos.broadcast.commentOptions()
Такое разделение имеет ряд минусов: во-первых - нужно делать две транзакции, во-вторых - если после размещения поста чей-то бот проголосует за пост перед тем как долетит вторая транзакция с опциями поста - опции по-просту не примутся из-за того, что с постом уже есть взаимодействия.
Очевидное решение - это упаковать обе операции в одну транзакцию. Я уже описывал принцип ранее: 🎓 Экономим ресурсы совмещая 100 операций в одну транзакцию , но описанный скрипт работает только в nodeJS
А форма golos.cf/md предполагает возможность простого копирования html и работы локально на вашем устройстве, стало быть nodejs там нет и нужно работать с обычным JavaScript
Проблема возникла только с вычислением ref_block_num
и ref_block_prefix
- это данные предыдущих блоков которые должны содержаться в каждой транзакции на голосе. Каждая транзакция должна ссылаться на предыдущий блок и создавать целостность цепи.
Подробно можно прочесть тут: Transactions as Proof of Stake - TaPoS
В TaPoS все транзакции включают в себя хеш последнего блока и считаются недействительными, если этот блок отсутствует в истории цепи
Но на самом деле, привязка к последнему (предыдущему) блоку не вполне безопасна, поскольку если транзакция попадет в микрофорк cможет быть отброшена главной цепочкой.
Привязка к последнему неизменяемому блоку last_irreversible_block_num
тоже влечет ряд уязвимостей для свежей транзакции ну и линковка на такое количество блоков назад кажется противоречива алгоритму.
Примечательно, что golos-js использует -3
блока от последнего head_block_number
в то время как steem-js использует last_irreversible_block_num-1
линковка почти на 20 блоков старее.
Мне больше нравится подход golos, однако я буду рад услышать аргументы, так как у самого нет компетенции и представления теоретические.
Получение предварительных данных (номер и хеш блока)
Первым запросом будет golos.api.getDynamicGlobalProperties
В ответе нам нужен номер последнего блока head_block_number
(только мы отнимем от него еще - 2 блока)
Пример ответа:
head_block_number: 15523551
Далее нам нужен хеш блока 15523551 - 3 = 15523548
для вычисления ref_block_prefix
но запрос мы сделаем не минус - 3 от последнего блока, а минус 2 или блок 15523549
Запрос golos.api.getBlockHeader(15523549)
Пример ответа:
previous: '00ecdedcfd8699a358c4595f0233db938d6d5705'
Обратите внимание на previous (пер. предыдущий) - это хеш блока 15523548, он как раз и есть в данном случае head_block_number
минус 3.
Таким образом мы получили номер и хеш определенного блока, высота которого минус 3 от последнего.
Блок 15523548
Хеш 00ecdedcfd8699a358c4595f0233db938d6d5705
Теперь из этих двух переменных нам нужно получить две другие - ref_block_num
и ref_block_prefix
ref_block_num
получается очень легко в обоих случаях, работает и в nodeJS и в браузере:
var refblocknum = 15523548 & 0xFFFF;
Теперь переменная refblocknum равна 57052
, при построении транзакции мы поместим этот номер в параметр ref_block_num
С ref_block_prefix
было все несколько сложнее, признаться - я долгое время не знал как обойтись без модуля Buffer в браузере, но оказалось все очень просто. Сначала пример для nodeJS который можно найти в golos-js библиотеке и просто скопировать. Выглядит примерно так:
Как вычислить ref_block_num & ref_block_prefix
в NodeJS + модуль Buffer
const BlockPrefix = new Buffer('00ecdedcfd8699a358c4595f0233db938d6d5705', 'hex').readUInt32LE(4);
Теперь в переменной BlockPrefix число 2744747773 его и можно ставить в ref_block_prefix
Теперь транзакция содержит эти важные данные
"ref_block_num":57052,
"ref_block_prefix":2744747773
Как вычислить ref_block_num & ref_block_prefix
в браузере простым JS
Проблема в том, что модуль Buffer работает только в NodeJS и пришлось потратить некоторое время, чтобы выполнить обычным JS то, что выполняет этот модуль.
В nodeJS функция new Buffer('00ecdedcfd8699a358c4595f0233db938d6d5705', 'hex')
возвратит ответ вида:
<Buffer 00 ec de dc fd 86 99 a3 58 c4 59 5f 02 33 db 93 8d 6d 57 05>
А .readUInt32LE(4)
выделит определенные байты из этого массива и переведет их в нужное число.
Сделать это простым JS удалось так:
var blockid = '00ecdedcfd8699a358c4595f0233db938d6d5705';
n = [];
for (var i = 0; i < blockid.length; i += 2) {
n.push(blockid.substr(i, 2));
}
Выше был создан массив байт похожий на содержания буфера из примера про nodeJS
И теперь в переменной n
содержание вида:
[ '00', 'ec', 'de', 'dc', 'fd', '86', '99', 'a3', '58', 'c4', '59', '5f', '02', '33', 'db', '93', '8d', '6d', '57', '05' ]
Из nodeJS нам ясно, что в этой строке нужно как-то найти число 2744747773
И спрятано оно вот в этом куске ... 'fd', '86', '99', 'a3' ...
это 4,5,6 и 7 индекс массива байт.
Теперь эти 4 байта нужно инвертировать наоборот вот так a3 99 86 fd
Например просто наполнив переменную:
var hex = n[7] + n[6] + n[5] + n[4];
Теперь в переменной hex у нас значение a39986fd
и чтобы оно превратилось в искомое число 2744747773 нужно просто конвертировать hex в число:
var refBlockPrefix = parseInt(hex, 16)
Теперь refBlockPrefix
содержит нужные данные для ref_block_prefix
и автоматическое формирование транзакции с множеством операций возможно в обычном браузере, на html странице без применения сервера.
Вот так выглядит код:
В переменную operations
можно поместить сразу несколько операций, на самом деле очень много. В переменной trx
будет готовая транзакция со всеми важными данными и операциями, которую можно подписать и отправить в блокчейн. Подробнее описано в одной из моих прошлых статей.
Живой пример скрипта работает на странице https://golos.cf/md где в одной транзакции совмещены две операции: комментарий и опции комментария.