🎓 Golos JS - Формирование транзакций с несколькими операциями соблюдая TaPoS, вычисление ref_block_prefix в браузере без NodeJS, сервера и модуля Buffer

2 года назад
73 в голос

Недавно я обновил форму постинга 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 где в одной транзакции совмещены две операции: комментарий и опции комментария.

Порядок сортировки:  Популярное

Все правильно и хорошо. Я поддерживаю.
Но боюсь есть маленькая загвоздка. Если вдруг кто-то попробует включить в транзакцию операции которые требуют разные подписи. То боюсь при этом его ждет фиаско.

·

Вовсе нет.

В посте есть ссылка на мой прошлый пост, там описано решение.

Вы либо подписываете аккаунт несколькими ключами либо используете мультисиг и подписываете операции разных пользователей своим ключом.

79
  ·  2 года назад

Благодарю. Задумался: как же это всё понять...

  1. Для чего это всё нужно?
  2. Как искать блок, важен ли тип его (Апвот, коммент или что-то ещё), как вычлинить из номера и хеша блока данные...
  3. Что требуется для создания транзакции, что для этого необходимо.

Очень интересует тема блокчейна. JS знаю на уровне начальном: изучил основы, Jquery, ajax.
Благодарю.

·
  1. Делать broadcast операций comment и comment_options нужно в одной транзакции иначе comment_options могут не пройти.

  2. Тип блока абсолютно не важен. В каждой новой транзакции нужно привязаться к прежним блокам. В примере привязка на 3 блока ранее последнего. Можно еще привязываться на минус 21 блок от текущего. Привязка нужна для целостности цепи, принцип блокчейна - связанные друг с другом последовательные блоки.

  3. Адрес ноды, публичной или локальной, аккаунт с ключом, скрипт который сформирует и отправит транзакцию.

Сама транзакция выглядит так:
https://golos.cf/tx/?=16b914989b1b3c0bec170426372a316d1533e311


zoom

1 ,2 и 3 динамические данные - ref префикс и id описываемы в этом посте + срок действия транзакции.
4 - непосредственно операции
5 - подпись.

JS знаю на уровне начальном: изучил основы, Jquery

Можете открыть исходный код html страницы golos.cf/md там как раз JS и Jquery и формирование транзакции c несколькими операциями через golos-js

·
·
79
  ·  2 года назад

Благодарю за разъяснения. Посмотрю golos.cf/md

76
  ·  2 года назад

Ваш пост поддержали следующие Инвесторы Сообщества "Добрый кит":
sharker, litrbooh, boddhisattva, neo, narin, andrvik, max-max, dimarss, tinochka, vadbars, amikphoto, arsar, tom123, yourlastwinter, olga-olga, vict0r, semasping, kssenia, ladynazgool, tnam0rken, zivchakh, rubin, aiparnyuk, hyipov, amelina.elena, lushaya, talia, graff0x, bombo, manavendra, mr-nikola, dimas102, lengalenga, lokkie, bag, ksantoprotein, massatela, chirakovalsky, yakubovruslan, astramar, benken, leonid96, brainmechanic, sinilga, valen-tina, anykeycheg, delectat, yurij12
Поэтому я тоже проголосовал за него!


dobryj.kit теперь стал Делегатом! Ваш голос важен для всего сообщества!!!
Поддержите нас:

С вопросом "к какому блоку линковаться" думаю всё довольно просто. Если отправляешь транзакцию, которая "сама по себе", то линковать к last_irreversible (или чуть раньше).

А линковка к свежим блокам реализует следующую логику: "считать эту транзакцию действительной, только если такой-то блок попал в цепь". Элементарный use-case: мы "слушаем" блокчейн в режиме head block, и допустим нам надо принять некие монеты на наш аккаунт и сразу отправить их кому-то другому. Благодаря TaPoS мы можем принять монеты и сразу же совершить перевод. Если первая транзакция будет потеряна из-за форка цепи, то и наша транзакция автоматически "отменится".