Поваренная книга VIZ

Поваренная книга VIZ представляет разработчикам описание API/объектов/структур в блокчейне VIZ, примеры кода для популярных вариантов использования, гайд для низкоуровневого формирования транзакций.


Содержание:

Основные понятия

Блокчейн VIZ относится к семейству Graphene (блокчейн-технология написанная на C++, открытый код). Исходный код Graphene доступен во множестве вариаций, так как он форкался (копировался) и видоизменялся множество раз (например: BitShares, Steem, VIZ, служил основой для EOS).

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


Нода блокчейн-системы

Программное обеспечение, которое связывается с другими нодами сети, принимает и транслирует транзакции другим нодам, ведет учет и обработку блоков (подробнее в разделе Типы нод).

Аккаунты

В VIZ используется модель аккаунтов. Аккаунты входят в общее пространство имен, хранят в себе токены и публичные ключи доступа.

Ключи и типы полномочий

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

Аккаунт в блокчейне VIZ содержит три типа полномочий:

  • master — отвечает за право владения аккаунтом;
  • active — отвечает за управление токенами аккаунта;
  • regular — отвечает за обычные операции (например, награждение).

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

Обычно аккаунт содержит либо единый ключ для всех типов полномочий, либо по одному ключу на каждый тип полномочий. Аккаунт дополнительно содержит memo-ключ, который используют для кодирования сообщений между участниками сети.

Токены VIZ и доля сети SHARES

Токен VIZ является передаваемым (переносимым), он не участвует в управлении, но может быть переведен в социальный капитал (SHARES). Данную операцию в других блокчейнах часто называют стейкинг (staking). Аккаунт, владеющий социальным капиталом, может принимать участие в управлении.

Делегаты

Аккаунт может заявить о своем намерении быть делегатом. Делегат (witness) избирается участниками сети (через процедуру голосования) и участвует в очереди делегатов для подписания блоков.

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

Очередь делегатов состоит из 21 слота: 11 мест зафиксированы за делегатами, набравшими наибольшее количество голосов, остальные 10 мест занимают делегаты в плавающей конкурирующей очереди согласно набранным голосам.

Транзакции

Пользователи создают операции (действия аккаунта в сети, подробнее в разделе Операции и их типы), формируют из них транзакцию, которую подписывают приватным ключом аккаунта нужного типа полномочий.

Блоки

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

Консенсус

VIZ использует консенсус Fair DPoS (Delegated Proof of Stake). Ноды хранят два состояния системы: необратимое и обратимое. Необратимость (irreversible) наступает тогда, когда в самой длинной цепочке блоков подтвердят свое участие в ней примерно 15 делегатов (75% от количества делегатов в очереди). После этого нода не может откатить более ранние транзакции, таким образом наступает финальность состояния сети (finality).

В обычном состоянии необратимость наступает за 15 блоков (около 45 секунд). Поэтому все важные операции, требующие проверки, могут получить подтверждение только достигнув необратимости.

Плагины

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

Экономика

В блокчейне VIZ были созданы условия для так называемой прогнозируемой экономики. Если в других системах заложены принципы с затухающей инфляцией, что ставит в неравные условия участников, подключившихся к системе в разное время, то в VIZ запрограммирована фиксированная инфляция с раундами в 1 год (10512000 блоков).


Инфляционная модель

При запуске цепи были распределены 50 млн viz. Каждый раунд закладывается фиксированная инфляция в 10%. Через год после старта в сети было 55 млн viz, а значит, инфляция в следующий раунд считалась уже от 55 миллионов viz, что привело к эмитированию 5,5 млн viz за второй год.

За счет фиксированной инфляции появляется возможность прогнозировать эмиссию viz каждый блок. Во второй год, например, каждый блок эмитируется 5500000 / 10512000 = 0.523 viz. И это число не меняется, пока не начнется следующий раунд.

Распределение эмиссии токенов

Существующая модель VIZ предусматривает управление экономикой делегатами сети. Избранные участниками сети делегаты голосуют за параметры сети, среди которых есть параметры по распределению эмиссии токенов:

  • inflation_witness_percent — экономический параметр, задающий процент эмиссии, направляемый на вознаграждение делегатов за поддержание инфраструктуры блокчейн-системы;
  • inflation_ratio_committee_vs_reward_fund — экономический параметр, определяющий соотношение эмиссии, направленной в Фонд ДАО и Фонд наград.

Актуальные значения параметров можно узнать выполнив API запрос get_chain_properties к плагину database_api или через обозреватель сети на сайте control.viz.world

Фонд ДАО

Фонд ДАО копит токены viz для финансирования инициатив по развитию экосистемы. Если участник сети решил провести конкурс, разработать приложение, принести пользу всей сети — он может подать заявку на финансирование из Фонда. Любой участник сети может проголосовать как против, так и за полное или частичное исполнение заявки. Когда подойдет время рассмотрения заявки, будут подсчитаны доли всех участников сети, принявших участие в голосовании, и вынесено решение.

Фонд наград

У каждого участника сети есть возобновляемый со временем ресурс — энергия. Израсходованная энергия восполняется со скоростью 100 процентных пунктов за 5 суток (20 п.п. в сутки, 0,83 п.п. в час и т.д.). Энергия аккаунта не может превышать 100%.

Когда участник Виза решает наградить кого-то, он указывает процент энергии аккаунта, который желает потратить на награду. Блокчейн учитывает размер социального капитала участника и затраченную на награду энергию и соотносит их с суммой наград всех участников за последние 5 суток. В результате, целевой аккаунт получает награду в социальный капитал из эмиссии пропорционально социальному капиталу и потраченной энергии награждающего. Таким образом обеспечивается равноправный конкурентный доступ к Фонду наград. Участник сети сам решает, кого наградить и за что, открывая возможность свободного выбора и стимулирования любых действий и инициатив.

Например:

  • Автор рассказа и его читатели могут наградить иллюстратора, который опубликовал рисунки персонажей;
  • На сайте вопросов и ответов участники могут наградить того, кто помог решить и разобраться в вопросе;
  • Зрители могут наградить видео-блогера за интересную тему или обучающий урок.

Самоуправление

Вся экономика VIZ находится под управлением владельцев социального капитала. Именно они голосуют за делегатов, если согласны с их виденьем и моделью управления системой (если не за кого голосовать, каждый может сам стать делегатом и получить голоса других участников). Фонд наград и Фонд ДАО работают по справедливой равноправно долевой модели управления.

Типы нод

Нода VIZ — сердце блокчейна, программное обеспечение, которое обрабатывает блок за блоком, исполняет все операции и хранит состояние системы. Владелец настраивает ноду, выбирает используемые плагины. От включенных плагинов и их настроек зависит то, какие возможности может предоставлять нода.


Witness node (нода делегата)

За формирование блоков отвечают делегаты, поэтому важнейшими нодами являются как раз ноды делегатов. Они обмениваются данными с другими нодами, собирают транзакции и когда подходит очередь делегата (владельца сервера) сформировать блок — происходит подпись собранного блока и его трансляция другим узлам. Блок должен быть подписан и доставлен за 3 секунды, выделенные на это делегату. Пропуск блока приводит к задержке выполнения транзакций и штрафу делегата на какое-то время (штраф накладывается на суммарный вес голосов, отданных за делегата другими пользователями). Это позволяет системе временно понижать в очереди делегатов тех, у кого случились какие-то неполадки на сервере или дата-центре, защищая таким образом надежность сети.

Зачастую делегаты держат две ноды, основную и запасную (резерв). Часто они находятся в разных дата-центрах и не зависят друг от друга. Если с основной нодой происходит неполадка, то делегат меняет ключ подписи блоков на резервный и формированием блоков будет заниматься запасная нода.

Ключевые плагины: chain p2p json_rpc webserver witness network_broadcast_api database_api witness_api

Seed node (сид-нода)

Основа для функционирования любой блокчейн системы — peer-to-peer (p2p) соединение и обмен данными. Сид-ноды отличаются тем, что выполняют важную роль — принимают и раздают блоки, разгружая таким образом пропускную способность всей сети и снижая отклик для близких территориально подключений. Нет экономической выгоды держать сид-ноду, так как сервер стоит денег, но не приносит своему владельцу ничего. Поэтому большинство сид-нод запускают делегаты (witnesses), если они могут позволить себе это финансово.

Ключевые плагины: chain p2p

API node

Часть плагинов занимаются предоставляем API для разработчиков и их пользователей. Такие ноды часто называют полными, если у них включены все доступные плагины и хранят историю с первого блока. Так как плагины дают доступ не только к состоянию системы, но и формируют свои структуры данных, у них повышенные требования к ресурсам сервера (особенно к оперативной памяти, так как нода хранит базу данных ChainBase в RAM). Именно через API ноды происходят запросы на актуальную информацию, статус аккаунтов, историю операций или заявки в комитете.

Примеры таких плагинов:

  • network_broadcast_api — отправка транзакции в сеть;
  • database_api — основной плагин предоставляющий доступ к состоянию системы, получение данных об аккаунтах, получение информации о блоке, получение параметров сети;
  • custom_protocol_api — плагин для записи счетчика и высоту блока для последних custom операций для аккаунта (количество хранимых идентификаторов кастомных операций для аккаунта задается параметром custom-protocol-store-size в конфигурационном файле);
  • account_history — получение списка операций связанных с аккаунтом;
  • committee_api — получение списка заявок по статусу, получение информации о заявке, получение списка голосов по заявке;
  • invite_api — получение списка инвайтов по статусу, запрос информации о инвайте по идентификатору или ключу;
  • operation_history — получение информации об операциях в блоке;
  • paid_subscription_api — получение информации о платных подписках, заключенных соглашениях между аккаунтами;
  • witness_api — получение списка делегатов, очереди делегатов, информации о конкретном делегате и его голосуемых параметрах сети.

API ноды могут быть как приватными (когда в настройках указаны параметры доступа по логину и паролю), так и публичными (когда обращение к API доступно всем и публично известен адрес ноды). Часто публичные API ноды называют просто публичными нодами. Подробнее читайте в разделе Плагины и их API.

Операции и их типы

Операции в блокчейне VIZ делятся на обычные, устаревшие, виртуальные. Обычные операции попадают в блокчейн через подписанную транзакцию участника сети. Устаревшие операции отключены в следствии развития сети и изменения внутренних механик.

Часть операций также относятся к тяжелым (data операции), на них накладывается дополнительный коэфициент пропускной способности (голосуемый параметр сети data_operations_cost_additional_bandwidth).

Виртуальные операции генерируются нодой когда срабатывают определенные условия в коде и носят больше информационный характер для участников сети. Например, операция награждения (award) от участника сети содержит цель награждения и затрачиваемую аккаунтом энергию. Там не хранится объем награждения, который достанется получателю из фонда наград. Именно для таких случаев и нужны виртуальные операции. В случае награждения будет сформирована виртуальная операция receive_award, содержащая полученную награду в поле shares.


Нумерация операций

В протоколе VIZ есть нумерация операций (начинается с нуля), там находятся как обычные, так и виртуальные операции:

  • 0, vote, устаревшая операция;
  • 1, content, устаревшая операция;
  • 2, transfer, перевод токенов VIZ;
  • 3, transfer_to_vesting, перевод токенов VIZ в долю сети SHARES;
  • 4, withdraw_vesting, конвертация SHARES в VIZ (вывод 28 суток равными частями объемом 1/28 от всей суммы SHARES);
  • 5, account_update, обновление доступов аккаунта;
  • 6, witness_update, установка ключа для делегата (или заявление о намерении им быть);
  • 7, account_witness_vote, голосование за делегата;
  • 8, account_witness_proxy, передача права голосования своей долей за делегатов;
  • 9, delete_content, устаревшая операция;
  • 10, custom, отправить в блокчейн-систему VIZ публичную строку с содержимым в формате JSON;
  • 11, set_withdraw_vesting_route, установка направления вывода доли при конвертации SHARES в токены VIZ;
  • 12, request_account_recovery, запрос на восстановление доступов через доверенный аккаунт;
  • 13, recover_account, удовлетворение запроса на восстановление доступов доверенным аккаунтом;
  • 14, change_recovery_account, смена доверенного аккаунта для восстановления аккаунта при потере доступов;
  • 15, escrow_transfer, создание сделки через посредника;
  • 16, escrow_dispute, запрос разрешения спорной ситуации между сторонами сделки у посредника;
  • 17, escrow_release, отпустить токены из сделки;
  • 18, escrow_approve, подтверждение выполнения сделки;
  • 19, delegate_vesting_shares, делегирование доли другому участнику;
  • 20, account_create, создание нового аккаунта;
  • 21, account_metadata, обновление публичных мета-данных аккаунта;
  • 22, proposal_create, создание предложения на подпись;
  • 23, proposal_update, обновление предложения (предоставление подписи);
  • 24, proposal_delete, удаление предложения;
  • 25, chain_properties_update, устаревшая операция, заменена versioned_chain_properties_update;
  • 26, author_reward, устаревшая виртуальная операция;
  • 27, curation_reward, устаревшая виртуальная операция;
  • 28, content_reward, устаревшая виртуальная операция;
  • 29, fill_vesting_withdraw, виртуальная операция, содержит информацию о конвертации SHARES в VIZ;
  • 30, shutdown_witness, виртуальная операция, вызывается когда делегат отключается из-за большого количества пропущенных блоков;
  • 31, hardfork, виртуальная операция, вызывается когда происходит хардфорк (обновление версии блокчейна);
  • 32, content_payout_update, устаревшая виртуальная операция;
  • 33, content_benefactor_reward, устаревшая виртуальная операция;
  • 34, return_vesting_delegation, виртуальная операция, происходит при возвращении делегированной доли;
  • 35, committee_worker_create_request, создание заявки в комитет;
  • 36, committee_worker_cancel_request, отмена заявки в комитете;
  • 37, committee_vote_request, голосование по заявке в комитете;
  • 38, committee_cancel_request, виртуальная операция, заявка отклонена комитетом;
  • 39, committee_approve_request, виртуальная операция, заявка одобрена комитетом;
  • 40, committee_payout_request, виртуальная операция, заявка полностью получила выплату из комитета;
  • 41, committee_pay_request, виртуальная операция, заявка получила выплату из комитета;
  • 42, witness_reward, виртуальная операция, награда делегату за подпись блока;
  • 43, create_invite, создание инвайт-кода;
  • 44, claim_invite_balance, погашение инвайт-кода на баланс аккаунта;
  • 45, invite_registration, регистрация с помощью инвайт-кода;
  • 46, versioned_chain_properties_update, установка делегатом голосуемых параметров сети;
  • 47, award, награждение участника сети;
  • 48, receive_award, виртуальная операция, получение награды;
  • 49, benefactor_award, виртуальная операция, получение бенефициарской награды;
  • 50, set_paid_subscription, установка условия соглашения для периодических платежей;
  • 51, paid_subscribe, подписание условий соглашения;
  • 52, paid_subscription_action, виртуальная операция, оплата периодических платежей;
  • 53, cancel_paid_subscription, виртуальная операция, прекращение периодических платежей;
  • 54, set_account_price, установка цены за аккаунт;
  • 55, set_subaccount_price, установка цены за сабаккаунты;
  • 56, buy_account, покупка аккаунта;
  • 57, account_sale, виртуальная операция, запись о покупке аккаунта;
  • 58, use_invite_balance, использование баланса инвайт-кода для перевода в долю сети аккаунта;
  • 59, expire_escrow_ratification, виртуальная операция, экспирация ратификации сделки через посредника;

Номер операции нужен для низко-уровневого формирования транзакций и их подписи (подробнее в разделе Формирование транзакций).

Объекты и структуры в VIZ

Рассматривая VIZ необходимо разделять объекты и структуры протокола (операция, транзакция, блок, ассет, версия, полномочия) от объектов и структур которые существуют непосредственно в блокчейне (на которые влияют те или иные операции).


Список объектов и структур протокола

Все, что касается протокола находится в каталоге /libraries/protocol исходного кода C++ ноды блокчейна VIZ.

  • types / типы данных в протоколе
  • operations / proposal_operations / chain_operations / chain_virtual_operations / операция — все что связано с операциями и их обработкой;
  • transaction / транзакция — все что связано с транзакцией (id, список операций, к какому блоку она ссылается);
  • block_header / block / блок — содержит транзакции, ссылается на предыдущий блок, содержит extensions который может использовать делегат для инициации голосования за переход на новую версию хардфорка;
  • asset / ассет — структура токенов в VIZ (VIZ и SHARES, отношение ассетов разного разряда друг к другу);
  • base / version / версия — структура, описывающая версию протокола сети, голос и время за переход на новую версию;
  • authority / полномочия — структура, описывающая связку ключей для определенного типа доступа аккаунта;
  • sign_state / состояние подписи — помощник по проверке подписей (или наличия ключа, который может ее сгенерировать).

Объекты и структуры в блокчейне

Именно из объектов и структур самого блокчейна состоит состояния системы (стэйт). Каждый блок, содержащий операции, обрабатывается основным модулем database, который просчитывает все изменения и принимает решения по отложенным действиям. В каталоге /libraries/chain/include/graphene/chain содержатся как объекты и структуры данных, так и внутреннее устройство блокчейна (evaluator, block_log, dynamic_global_property_object, типы объектов).

Состояние системы состоит из объектов:

  • dynamic_global_property_object — основной объект, содержащий данные о текущем состоянии экономики и состоянии ноды (например, номер необратимого блока);
  • account_object — записи аккаунтов;
  • account_authority_object — записи полномочий для аккаунтов;
  • witness_object — записи делегатов;
  • transaction_object — используется для транзакций в очереди (это позволяет проверять отсутствии дублей у новых транзакций и удалять транзакции из очереди, если она не выполнилась до срока истечения expire);
  • block_summary_object — используется для индексации блоков и их hash, для проверки TaPoS (транзакция должна ссылаться на прошлый блок, проверка происходит как раз по индексу, построенному из объектов block_summary_object);
  • witness_schedule_object — состояние очереди делегатов;
  • witness_vote_object — записи голосов за делегатов;
  • hardfork_property_object — записи о текущем хардфорке сети;
  • withdraw_vesting_route_object — записи о маршруте распределения токенов при конвертации доли;
  • master_authority_history_object — записи изменений мастер полномочий;
  • account_recovery_request_object — запросы на восстановление аккаунта;
  • change_recovery_account_request_object — запросы на смену доверенного аккаунта для восстановления доступа;
  • escrow_object — записи о трехсторонних сделках;
  • vesting_delegation_object — записи о делегированной доли;
  • vesting_delegation_expiration_object — записи о возвращаемой делегированной доли после отмены делегирования;
  • account_metadata_object — отдельные записи с мета-данными аккаунта;
  • proposal_object — записи proposal операций;
  • required_approval_object — записи требуемых подтверждений для proposal операций;
  • committee_request_object — записи заявок в комитет;
  • committee_vote_object — записи голосов по заявкам в комитете;
  • invite_object — записи всех инвайтов;
  • award_shares_expire_object — записи наград, которые должны понизить конкуренцию по истечению срока;
  • paid_subscription_object — информация о платных подписках;
  • paid_subscribe_object — записи оформленных платных подписок;
  • witness_penalty_expire_object — записи штрафов делегатам, пропустившим блок.

Объекты и структуры в API плагинах

Плагины, предоставляющие API, могут возвращать объекты как из блокчейна, так и собственные. Простые запросы с получением объекта по id отдают данные как есть, часто пропуская объект из блокчейна через конструктор аналогичного для API, чтобы скопировать состояние и отдать пользователю его, например: плагин witness_api использует отдельный объект witness_api_object. А плагин database_api использует account_api_object, который дополняет стандартный объект блокчейна аккаунт типами доступа копируя туда актуальные полномочия из индекса.

Если плагин расширяет стандартные таблицы индексов и объекты, то он создает новую структуру, отдельно ведет учет операций и заполняет индекс. Например, так поступает плагин private_message, обрабатывая custom операции (создавая объекты message_object, наполняющие индекс message_index).

Голосуемые параметры сети

Делегаты транслируют свою позицию по голосуемым параметрам сети. Блокчейн система каждый цикл очереди делегатов (21 блок) вычисляет медианные значения голосуемых параметров и фиксирует их на этот цикл. Описание параметров (в скобках указаны медианные значения на момент написания данного раздела):

  • account_creation_fee — передаваемая комиссия при создании аккаунта (1.000 VIZ);
  • create_account_delegation_ratio — коэффициент наценки делегирования при создании аккаунта (x10);
  • create_account_delegation_time — время делегирования при создании аккаунта (2592000 секунд);
  • bandwidth_reserve_percent — доля сети, выделяемая для резервной пропускной способности (0.01%);
  • bandwidth_reserve_below — резервная пропускная способность действует для аккаунтов с долей сети до порога (1.000000 SHARES);
  • committee_request_approve_min_percent — минимальный процент доли сети голосующих, необходимый для принятия решения по заявке в комитете (10%);
  • min_delegation — минимальное количество токенов при делегировании (1.000 VIZ);
  • vote_accounting_min_rshares — минимальный вес голоса для учёта при награждении (5000000 rshares);
  • maximum_block_size — максимальный размер блока в сети (65536 байт);
  • inflation_witness_percent — доля инфляции для награды делегатам (20%);
  • inflation_ratio_committee_vs_reward_fund — соотношение разделения остатка инфляции между комитетом и фондом наград (75%);
  • inflation_recalc_period — количество блоков между пересчётом инфляционной модели (806400);
  • data_operations_cost_additional_bandwidth — дополнительная наценка пропускной способности за каждую data операцию в транзакции (0% от размера транзакции);
  • witness_miss_penalty_percent — штраф делегату за пропуск блока в процентах от суммарного веса голосов (1%);
  • witness_miss_penalty_duration — длительность штрафа делегату за пропуск блока в секундах (86400 секунд);
  • create_invite_min_balance — минимальная сумма чека (10.000 VIZ);
  • committee_create_request_fee — плата за создание заявки в Фонд ДАО (100.000 VIZ);
  • create_paid_subscription_fee — плата за создание платной подписки (100.000 VIZ);
  • account_on_sale_fee — плата за выставление аккаунта на продажу (10.000 VIZ);
  • subaccount_on_sale_fee — плата за выставление субаккаунтов на продажу (100.000 VIZ);
  • witness_declaration_fee — плата за объявление аккаунта делегатом (10.000 VIZ);
  • withdraw_intervals — количество периодов (дней) уменьшения капитала (28).

Служебные аккаунты

В VIZ существуют служебные аккаунты, которые имеют заложенные в конфигурационном файле ключи доступа к определенным полномочиям:

  • null — специализированный аккаунт, который сжигает полученные токены. Имеет пустые полномочия, механизм сжигания токенов заложен в исходный код блокчейна.
  • committee — специализированный аккаунт, который все полученные токены переводит в фонд комитета, позволяя таким образом жертвовать токены на развитие экосистемы. Имеет пустые полномочия. Также служит инициатором сети, для анонимного генезиса блокчейна (когда ключ подписи блоков от аккаунта committee доступен всем в конфигурационном файле). Ключ подписи сформирован из строки конкатенации строк committee, viz, sign, что соответствует 5Hw9YPABaFxa2LooiANLrhUK5TPryy8f7v9Y1rk923PuYqbYdfC. После запуска сети и подключения к ней других делегатов аноним может прекратить подпись аккаунтом committee и ключ подписи обнуляется до значения VIZ1111111111111111111111111111111114T1Anm.
  • anonymous — специализированный аккаунт, который при получении токенов с указанным ключом (и логином, при желании) создает анонимный аккаунт (анонимность обеспечивается возможным переводом с шлюзов - как биржевых, так и социальных, custody сервисов). Имеет пустые полномочия. Формат заметки memo для регистрации анонимного аккаунта: login:public_key, где login - это желаемый логин для нового аккаунта, а public_key — единый публичный ключ для всех типов полномочий. Если логин не указан, а в заметке только public_key, то создается анонимный сабаккаунт формата nX.anonymous, где X — инкрементация номера, указанного в json_metadata аккаунта anonymous. Внимание! Если заметка не указана, средства будут сожжены аналогично переводу на аккаунт null.
  • invite — специализированный аккаунт для возможности анонимно, не имея аккаунта в блокчейне, активировать инвайт коды. Активный ключ доступен всем в конфигурационном файле, сформирован из строки конкатенации строк invite, viz, active, что соответствует 5KcfoRuDfkhrLCxVcE9x51J6KN9aM9fpb78tLrvvFckxVV6FyFW. Изначально не имеет какой-либо доли для осуществления транзакций, что может быть исправлено путем делегирования или включения системы резервной пропускной способности делегатами.
  • viz — аккаунт инициатор цепочки в генезис блоке, приватный ключ 5JabcrvaLnBTCkCVFX5r4rmeGGfuJuVp4NAKRNLTey6pxhRQmf4, был сброшен на пустой после предустановки продажи субаккаунтов за 10 000 VIZ (получатель committee).

Состояние (стэйт) системы

Состояние системы — необходимый минимум данных, для функцонирования ноды. Каркас Graphene, на котором построен VIZ, исполняет операции из транзакций в каждом блоке, который соответствует консенсусу (очереди делегатов, соответствие подписей). Каждый блок происходит обработка данных, отложенных действий, что приводит к иттерационной сущности состояния системы. Часть данных хранятся там постоянно, что накладывает определенные требования на серверное оборудование, на котором запущена нода.

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


Dynamic global property object (dgpo)

Данный объект хранит основные свойства сети, содержит информацию о токенах в обращении и другую важную информацию. В исходном коде он часто представлен в виде переменной dgp, нода модифицирует его состояние с каждой иттерацией, поэтому dynamic global property можно с уверенностью считать самой важной частью состояния системы. Рассмотрим его публичные свойства, доступные через метод get_dynamic_global_properties при обращении к плагину database_api:

  • current_witness (пример значения: "solox") — делегат актуального блока;

  • head_block_number (пример значения: 10829669) — номер актуального блока;

  • head_block_id (пример значения: 00a53f658226b2a6f3f75fc8185d884d029f50bf) — идентификатор (он же хэш) актуального блока;

  • time (пример значения: "2019-10-11T08:49:21") — время генерации актуального блока;

  • last_irreversible_block_num (пример значения: 10829651) — номер последнего необратимого блока;

  • genesis_time (пример значения: "2018-09-29T10:23:24") — время генерации первого блока сети;

  • current_supply (пример значения: "55159321.957 VIZ") — общее количество токенов VIZ в системе;

  • total_vesting_fund (пример значения: "27924855.425 VIZ") — количество токенов VIZ переведенных в долю сети (SHARES);

  • total_vesting_shares (пример значения: "27924847.935329 SHARES") — общая количественная мера доли сети в SHARES;

  • committee_fund (пример значения: "1423813.837 VIZ") — баланс фонда комитета;

  • committee_requests (пример значения: 39) — общее количество заявок в комитет;

  • total_reward_fund (пример значения: "28216.906 VIZ") — баланс фонда наград;

  • total_reward_shares (пример значения: "6019776735774") — количественная мера конкуренции за фонд наград;

  • current_aslot (пример значения: 10855719) — текущий номер слота делегата на подпись (содержит в себе нумерацию слота от старта в сети, включая пропущенные делегатами блоки);

  • recent_slots_filled (пример значения: 340282366920938463463374607431768211455) — используется для вычисления процента делегатов участвующих в подписи блоков;

  • participation_count (пример значения: 128) — необходимо разделить на 128, чтобы получить процент делегатов участвующих в подписи блоков;

  • maximum_block_size (пример значения: 65536) — максимальный размер блока в байтах (голосуемый параметр сети);

  • average_block_size (пример значения: 114) — средний размер блока, рассчитывается по формуле average_block_size = (99 * average_block_size + new_block_size) / 100, используется для обновления current_reserve_ratio для поддержания около 50% или меньше в пропускной способности сети;

  • max_virtual_bandwidth (пример значения: 5986734968066277376) — максимальная пропускная способность сети рассчитывается по формуле maximum_block_size * CHAIN_BANDWIDTH_AVERAGE_WINDOW_SECONDS / CHAIN_BLOCK_INTERVAL, максимальная виртуальная пропускная способность сети по формуле max_bandwidth * current_reserve_ratio

  • current_reserve_ratio (пример значения: 20000) — Раз в 20 блоков (1 минута) происходит проверка average_block_size <= 25% maximum_block_size. Если оно выполняется, то данное значение увеличивается на 1 (линейно, каждый блок), но не более CHAIN_MAX_RESERVE_RATIO (20000). Если условие не выполнено, то current_reserve_ratio делится пополам, что должно сразу снизить нагрузку на сеть, защищая ее от участников, использующих объемные транзакции. Другими словами уменьшение вдвое резервного соотношения не уменьшит вдвое использование сети, но ограничит пользователей которые уже попытаются превысить более 50% от их пропускной способности. Когда резервное соотношение падает вдвое от максимального значения (10000 вместо 20000), восстановление общей виртуальной пропускной способности займет около 7 суток.

  • bandwidth_reserve_candidates (пример значения: 1) — количество кандидатов на резерв пропускной способности (плюс 1 кандидат по умолчанию);

  • inflation_calc_block_num (пример значения: 10315901) — номер блока последнего расчета распределения инфляции;

  • inflation_witness_percent (пример значения: 2000) — процент от эмиссии получаемый делегатами за подпись блоков;

  • inflation_ratio (пример значения: 5000) — процент соотношения от эмиссии направляемый в фонд комитета против фонда наград;

  • vote_regeneration_per_day (пример значения: 1) — устаревшее свойство.

Уникальность транзакций и TaPoS (Transactions as Proof of Stake)

Нода проверяет все входящие транзакции на уникальность в пуле транзакций. После того как наступает expiration (ограничено в настройках константой CHAIN_MAX_TIME_UNTIL_EXPIRATION в один час), транзакция удаляется из пула.

Все транзакции в VIZ должны соответствовать концепции TaPoS, то-есть ссылаться на один из прошлых блоков (ref_block_num в 2 байтовом представлении (бинарное «и» десятичного представления номера блока с hex ffff) и ref_block_prefix состоящий из десятичного представления 5, 6, 7, 8 байтов от бинарного состояния хэша в обратном порядке), что позволяет иницатору транзакции опираться на актуальное для него состояние системы не беспокоясь о необратимом блоке. В случае, если он опирался на состояние системы в случайном минорном форке, то транзакция не попадет в основную цепочку. Таким образом участники сети могут контролировать исполнение очереди транзакций и строить взаимодействие не дожидаясь необратимости блока. Это, в свою очередь, накладывает ограничение на финальный учет подобных действий, поэтому большинство важных транзакций должны находиться уже в необратимом состоянии для проверяющей стороны.

Нода выделяет пространство block_summary_object с размерностью в 2 байта (чтобы номер блока прошедший через операцию бинарного «и» с hex ffff умещался в диапазоне от 0 до 65536) и принимая новые блоки перезаписывает по кругу идентификаторы (хэши) в этом пространстве (и индексе block_summary_index). 65537 блоков охватывают временной промежуток 196611 секунд (примерно 2.27 суток). Соответственно новые транзакции могут ссылаться только на блоки из этого пространства, чтобы нода могла сверить соответствие идентификатора блока (из ref_block_num) с контрольной суммой из ref_block_prefix.

Переносимое состояние системы

Если в первом поколении блокчейн-систем основанных на Proof of Work требовалось хранить в состоянии системы все идентификаторы блоков и транзакций, то с ростом количества данных многие разработчики начали искать способ снизить издержки на объем хранимых данных. И основной объем данных хранится как раз в блоках и содержащихся в них транзакциях. В современных DLT уже решена эта проблема за счет согласования необратимого состояния и переносимого состояния системы. Часть блокчейн-систем только начинают двигаться в этом направлении, например в Steem предложено решение в виде Platform Independent State Files – PISF. Новые блокчейн-системы (и часть старых первопроходцев, например нода к XRP Ledger — rippled) уже созданы с учетом переносимого состояния системы, их архитектура позволяет запросить у доверенных нод актуальное состояние системы, пропуская длительную синхронизацию, скачивание всей истории блокчейна и самостоятельную обработку всех транзакций.

VIZ не вносил значительных изменений в архитектуру состояния системы Graphene, поэтому в индексе block_summary_index состоящий из структур block_summary_object хранится вся информация о блоках (а именно block_id_type конкретного блока, который уже содержит всю информацию). Это затрудняет создание переносимого состояния системы, так как объем данных для такого состояния будет значительным. Единственная возможность модернизировать это — перейти к консенсусу доверенных нод.

Плагины и их API

Плагины представляют собой универсальный инструмент расширения ноды и её возможностей. Часть из них лишь отдают данные, подготавливают индексы, отвечают на сложные запросы пользователей с фильтрацией данных, часть из них обрабатывают custom операции и могут предоставлять совершенно отдельный сервис. Например, можно написать плагин, который после платной подписки первого уровня будет формировать уведомления о важных действиях в блокчейне или предоставлять сервис личных сообщений. Публичный плагин не всегда означает «бесплатный». И «бесплатный» не всегда значит открытый (в плане исходного кода).

Если обратиться к логическому изучению цепочки нода-сервисы-API, то мы увидим неприятную ситуацию, когда публичные API могут создавать проблемы для функционирования самой ноды. Такое может возникнуть при большом потоке запросов к API сервиса, тем более в том случае, если плагин, предоставляющий сервис, работает как раз с нодой блокчейна. Пропускная способность от пользовательских запросов (или запросов злоумышленника при желании произвести DDOS сервиса) может помешать самой ноде обмениваться данными с другими узлами. Если API запросы нагружают CPU или используют большие выборки по индексам, долго подготавливают данные для ответа — возникает проблема не только с сервисом, который начинает тормозить, но и с функционированием ноды.

Именно поэтому рекомендуется избегать пересечения публичной API ноды с требовательными плагинами и работы делегата на том же сервере. Более правильной архитектурой сервиса будет являться отдельный независимый плагин, имеющий доступ к обработке блоков на приватной ноде, сам обрабатывает данные, хранит их в базе данных и позволяет кэшировать запросы. При росте нагрузки всегда можно расширить сервис применяя технологии кластеризации как данных, так и отвечающих узлов (с помощью load balancing).

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

  • Открыть основной заголовочный файл плагина (пример для database_api), изучить DEFINE_API_ARGS (название API метода, тип возвращаемого значения);
  • Открыть основной файл плагина (пример для database_api), изучить DEFINE_API (проверка параметров запроса, CHECK_ARGS_COUNT, формирование возвращаемого значения определенного типа);
  • Изучить plugin_initialize, который может обрабатывать boost::program_options::variables_map для более тонкой настройки плагина через конфигурационный файл ноды.

Протокол запросов

Все запросы должны быть сформированы в JSON и выполнены через RPC. Транспортный протокол зависит от настройки ноды, возможны варианты как JSON-RPC через стандартные HTTP запросы, так и через WebSocket.

Для этого в конфигурационном файле ноды должны быть подключены плагины: json_rpc, webserver. Чтобы принимать транзакции от пользователей также должен быть включен плагин network_broadcast_api. Настройки для портов:

# Количество потоков для клиентов rpc. Оптимальное значение *количество ядер минус 1*
webserver-thread-pool-size = 2

# IP:PORT для HTTP подключений
webserver-http-endpoint = 0.0.0.0:8090

# IP:PORT для WebSocket подключений
webserver-ws-endpoint = 0.0.0.0:8091

# IP:PORT для HTTP и WebSocket соединений (одновременная обработка двух типов подключений)
rpc-endpoint = 0.0.0.0:8081

Чтобы обрабатывать запросы с поддержкой SSL, необходимо пробросить используемые порты через проксирующий сервер (например, nginx или apache), тогда станут возможными запросы через https/wss.

Формирование API запроса

Правила формирования запросов к публичной ноде довольно простые:

{"id":REQUEST_ID,"jsonrpc":"2.0","method":"call","params":["PLUGIN_NAME","PLUGIN_API_METHOD",[ARGS]]}
  • REQUEST_ID — номер запроса, носит необязательный характер (можно все запросы нумеровать единицей), но при соединении через web sockets (ws) позволяет ассоциировать ответы по запросам с аналогичным id;
  • PLUGIN_NAME — название плагина, к которому выполняется запрос (например: database_api, committee_api);
  • PLUGIN_API_METHOD — название метода, обрабатывающего запрос (например: get_accounts из database_api);
  • ARGS — массив упорядоченных параметров, передаваемый методу плагина.

network_broadcast_api

Плагин, отвечающий за прием и рассылку между узлами сети подписанных блоков и транзакций

  • broadcast_block — передача подписанного блока (signed_block) другим узлам сети;
  • broadcast_transaction — передача подписанной транзакции (signed_transaction) другим узлам сети;
  • broadcast_transaction_synchronous — передача подписанной транзакции (signed_transaction) другим узлам сети (ждет вхождения в блок и возвращает хэш транзакции, номер блока и номер транзакции в блоке, или возвращает false в случае истечения срока действия транзакции);
  • broadcast_transaction_with_callback — то же, что и broadcast_transaction_synchronous, кроме проверки транзакции на валидность перед передачей в пул транзакций и регистрации метода обратного вызова (callback).

custom_protocol_api

  • get_account — возвращает аккаунт по логину с опциональной возможностью запросить custom_protocol_id (опционально, перезаписывает custom_sequence и custom_sequence_block_num если запрошенный custom protocol id найден в истории ноды);

Пример:

{"id":1,"method":"call","jsonrpc":"2.0","params":["custom_protocol_api","get_account",[["readdle","V"]]]}

Ответ:

{
  "id": 116,
  "name": "readdle",
  "master_authority": {
    "weight_threshold": 1,
    "account_auths": [],
    "key_auths": [
      [
        "VIZ7PZqJj3UvV3kymCWHnwn9PxRGgt9z6MDzyEeXCFVr6X9XmoBCY",
        1
      ]
    ]
  },
  "active_authority": {
    "weight_threshold": 1,
    "account_auths": [],
    "key_auths": [
      [
        "VIZ6CmEqBAdWu1MGrSeTPwSG9yKwLwBPEuVBhzbKBMFP2aYTomCCN",
        1
      ]
    ]
  },
  "regular_authority": {
    "weight_threshold": 1,
    "account_auths": [],
    "key_auths": [
      [
        "VIZ8PqkhFxxifidH6688oSZHYPGsXMhRVE9xhvh6N65XkYRg74pMR",
        1
      ]
    ]
  },
  "memo_key": "VIZ7Lo593wA3SwFwpiHYfm3kWw4BwgrcBsYt1UDyZW4J3dd5GWuab",
  "json_metadata": "{\"profile\":{\"nickname\":\"Readdle.me\",\"about\":\"RU: Децентрализованная Социальная Сеть, где пользователь выступает в роли Оракула, обрабатывающего социальную активность интересных ему аккаунтов. Работает на блокчейне VIZ с использованием системы награждений Социальным Капиталом VIZ (Ƶ).\",\"avatar\":\"https://readdle.me/readdle-avatar.png\",\"services\":{\"telegram\":\"readdle_me\"},\"interests\":[\"ru\",\"welcome\"],\"pinned\":\"viz://@readdle/22099872/\"}}",
  "proxy": "",
  "referrer": "",
  "last_master_update": "1970-01-01T00:00:00",
  "last_account_update": "2020-11-05T19:23:33",
  "created": "2018-09-29T18:25:27",
  "recovery_account": "in",
  "last_account_recovery": "1970-01-01T00:00:00",
  "subcontent_count": 0,
  "vote_count": 0,
  "content_count": 0,
  "awarded_rshares": 0,
  "custom_sequence": 4,
  "custom_sequence_block_num": 22897215,
  "energy": 10000,
  "last_vote_time": "2018-09-29T18:25:27",
  "balance": "0.000 VIZ",
  "vesting_shares": "24.102958 SHARES",
  "delegated_vesting_shares": "0.000000 SHARES",
  "received_vesting_shares": "10.056015 SHARES",
  "vesting_withdraw_rate": "0.000000 SHARES",
  "next_vesting_withdrawal": "1969-12-31T23:59:59",
  "withdrawn": 0,
  "to_withdraw": 0,
  "withdraw_routes": 0,
  "curation_rewards": 0,
  "posting_rewards": 0,
  "receiver_awards": 24103,
  "benefactor_awards": 0,
  "proxied_vsf_votes": [
    0,
    0,
    0,
    0
  ],
  "witnesses_voted_for": 0,
  "witnesses_vote_weight": 0,
  "last_post": "1970-01-01T00:00:00",
  "last_root_post": "1970-01-01T00:00:00",
  "average_bandwidth": "16919020937",
  "lifetime_bandwidth": "52767000000",
  "last_bandwidth_update": "2020-12-03T11:55:45",
  "witness_votes": [],
  "valid": true,
  "account_seller": "",
  "account_offer_price": "0.000 VIZ",
  "account_on_sale": false,
  "account_on_sale_start_time": "1970-01-01T00:00:00",
  "subaccount_seller": "",
  "subaccount_offer_price": "0.000 VIZ",
  "subaccount_on_sale": false
}

database_api

  • get_account_count — возвращает количество аккаунтов в сети;
  • get_accounts — возвращает массив аккаунтов по запрошенным логинам (отличается от lookup_account_names дополнительной информацией);

Пример:

{"id":1,"method":"call","jsonrpc":"2.0","params":["database_api","get_accounts",[["wildviz","zozo"]]]}

Ответ:

[
  {
    "id": 3276,
    "name": "wildviz",
    "master_authority": {
      "weight_threshold": 1,
      "account_auths": [],
      "key_auths": [
        [
          "VIZ7TAJxC1ibwYEgWon3YWXJdjKaTPS3eVy9zSKq2cRFECMuJvHcq",
          1
        ]
      ]
    },
    "active_authority": {
      "weight_threshold": 1,
      "account_auths": [],
      "key_auths": [
        [
          "VIZ8dJxeKPXe5wf6bnqaMsj3EW8EbMURoKxR4RqPCQH8xA89b6RpC",
          1
        ]
      ]
    },
    "regular_authority": {
      "weight_threshold": 1,
      "account_auths": [],
      "key_auths": [
        [
          "VIZ8iKb38H1JD7PZqEYpoLX3nMt1mgfoh2LwngexvywusE7fgrd5G",
          1
        ]
      ]
    },
    "memo_key": "VIZ8mvAu9beH6gDtBrdy3oh8eTGP4tKpNXHvY7wtGm7EXLuwFaRi7",
    "json_metadata": "",
    "proxy": "",
    "referrer": "",
    "last_master_update": "1970-01-01T00:00:00",
    "last_account_update": "1970-01-01T00:00:00",
    "created": "2019-03-14T08:33:21",
    "recovery_account": "xchng",
    "last_account_recovery": "1970-01-01T00:00:00",
    "subcontent_count": 0,
    "vote_count": 10,
    "content_count": 0,
    "awarded_rshares": 0,
    "custom_sequence": 2,
    "custom_sequence_block_num": 10319967,
    "energy": 9995,
    "last_vote_time": "2019-10-14T08:08:48",
    "balance": "0.000 VIZ",
    "vesting_shares": "19159.343040 SHARES",
    "delegated_vesting_shares": "250.000000 SHARES",
    "received_vesting_shares": "0.000000 SHARES",
    "vesting_withdraw_rate": "0.000000 SHARES",
    "next_vesting_withdrawal": "1969-12-31T23:59:59",
    "withdrawn": 1500000000,
    "to_withdraw": 1500000000,
    "withdraw_routes": 0,
    "curation_rewards": 0,
    "posting_rewards": 0,
    "receiver_awards": 255786,
    "benefactor_awards": 2958047,
    "proxied_vsf_votes": [
      0,
      0,
      0,
      0
    ],
    "witnesses_voted_for": 1,
    "witnesses_vote_weight": "19159343040",
    "last_post": "1970-01-01T00:00:00",
    "last_root_post": "1970-01-01T00:00:00",
    "average_bandwidth": 1089649559,
    "lifetime_bandwidth": "24196000000",
    "last_bandwidth_update": "2019-10-14T08:08:48",
    "witness_votes": [
      "wildviz"
    ],
    "valid": true,
    "account_seller": "",
    "account_offer_price": "0.000 VIZ",
    "account_on_sale": false,
    "account_on_sale_start_time": "1970-01-01T00:00:00",
    "subaccount_seller": "",
    "subaccount_offer_price": "0.000 VIZ",
    "subaccount_on_sale": false
  }
]
  • get_accounts_on_sale — возвращает список аккаунтов выставленных на продажу, имеет два параметра: from (смещение в результирующем списке) и limit (количество записей, не может быть больше 1000);

Пример:

{"id":1,"method":"call","jsonrpc":"2.0","params":["database_api","get_accounts_on_sale",[0,1000]]}

Ответ:

[
  {"account":"btc","account_seller":"ae","account_offer_price":"5000.000 VIZ"},
  {"account":"press","account_seller":"on1x","account_offer_price":"10000.000 VIZ"}
]
  • get_subaccounts_on_sale — возвращает список сабаккаунтов выставленных на продажу, имеет два параметра: from (смещение в результирующем списке) и limit (количество записей, не может быть больше 1000);

Пример:

{"id":1,"method":"call","jsonrpc":"2.0","params":["database_api","get_subaccounts_on_sale",[0,1000]]}

Ответ:

[
  {"account":"com","subaccount_seller":"ae","subaccount_offer_price":"10.000 VIZ"},
  {"account":"digital","subaccount_seller":"on1x","subaccount_offer_price":"100.000 VIZ"},
  {"account":"blog","subaccount_seller":"on1x","subaccount_offer_price":"100.000 VIZ"},
  {"account":"new.romankr","subaccount_seller":"romankr","subaccount_offer_price":"10.000 VIZ"},
  {"account":"romankr1","subaccount_seller":"romankr","subaccount_offer_price":"10.000 VIZ"},
  {"account":"viz","subaccount_seller":"committee","subaccount_offer_price":"10000.000 VIZ"}
]
  • get_block — возвращает информацию о блоке по его номеру;

Пример:

{"id":1,"method":"call","jsonrpc":"2.0","params":["database_api","get_block",["1"]]}

Ответ:

{
  "previous": "0000000000000000000000000000000000000000",
  "timestamp": "2018-09-29T10:23:27",
  "witness": "committee",
  "transaction_merkle_root": "0000000000000000000000000000000000000000",
  "extensions": [
    [
      1,
      "1.0.0"
    ]
  ],
  "witness_signature": "2003120d1f1d8bb8e8325036838e5269332fbec7c88cd3cc76e4517d27772856ce43ef6bf0a7fde9987cec9467b944e7626db100b70867e7ec82308879a021e97a",
  "transactions": []
}
  • get_block_header — возвращает заголовок блока по его номеру;

Пример:

{"id":1,"method":"call","jsonrpc":"2.0","params":["database_api","get_block_header",["2"]]}

Ответ:

{
  "previous": "000000010496d4414ddcee5b76f9a6b950da6fe9",
  "timestamp": "2018-09-29T10:23:30",
  "witness": "committee",
  "transaction_merkle_root": "0000000000000000000000000000000000000000",
  "extensions": []
}
  • get_chain_properties — возвращает медианные значения голосуемых параметров сети;

Пример:

{"id":1,"method":"call","jsonrpc":"2.0","params":["database_api","get_chain_properties",[]]}

Ответ:

{
  "account_creation_fee": "1.000 VIZ",
  "maximum_block_size": 65536,
  "create_account_delegation_ratio": 10,
  "create_account_delegation_time": 2592000,
  "min_delegation": "1.000 VIZ",
  "min_curation_percent": 0,
  "max_curation_percent": 10000,
  "bandwidth_reserve_percent": 0,
  "bandwidth_reserve_below": "0.000000 SHARES",
  "flag_energy_additional_cost": 0,
  "vote_accounting_min_rshares": 50000,
  "committee_request_approve_min_percent": 1000,
  "inflation_witness_percent": 2000,
  "inflation_ratio_committee_vs_reward_fund": 7500,
  "inflation_recalc_period": 806400,
  "data_operations_cost_additional_bandwidth": 0,
  "witness_miss_penalty_percent": 100,
  "witness_miss_penalty_duration": 86400
}
  • get_config — возвращает предустановки в конфигурационном файле протокола VIZ;

Пример:

{"id":1,"method":"call","jsonrpc":"2.0","params":["database_api","get_config",[]]}

Ответ:

{
  "CHAIN_100_PERCENT": 10000,
  "CHAIN_1_PERCENT": 100,
  "CHAIN_ADDRESS_PREFIX": "VIZ",
  "CHAIN_BANDWIDTH_AVERAGE_WINDOW_SECONDS": 604800,
  "CHAIN_BANDWIDTH_PRECISION": 1000000,
  "CONSENSUS_BANDWIDTH_RESERVE_PERCENT": 1000,
  "CONSENSUS_BANDWIDTH_RESERVE_BELOW": 500000000,
  "CHAIN_HARDFORK_VERSION": "2.4.0",
  "CHAIN_VERSION": "2.4.0",
  "CHAIN_BLOCK_INTERVAL": 3,
  "CHAIN_BLOCKS_PER_DAY": 28800,
  "CHAIN_BLOCKS_PER_YEAR": 10512000,
  "CHAIN_CASHOUT_WINDOW_SECONDS": 86400,
  "CHAIN_ID": "2040effda178d4fffff5eab7a915d4019879f5205cc5392e4bcced2b6edda0cd",
  "CHAIN_HARDFORK_REQUIRED_WITNESSES": 17,
  "CHAIN_INITIATOR_NAME": "viz",
  "CHAIN_INITIATOR_PUBLIC_KEY_STR": "VIZ6MyX5QiXAXRZk7SYCiqpi6Mtm8UbHWDFSV8HPpt7FJyahCnc2T",
  "CHAIN_INIT_SUPPLY": "50000000000",
  "CHAIN_COMMITTEE_ACCOUNT": "committee",
  "CHAIN_COMMITTEE_PUBLIC_KEY_STR": "VIZ6Yt7d6LsngBoXQr47aLv97bJVs7jyr7esZTM4UUSpLUf3nbRKS",
  "CHAIN_IRREVERSIBLE_THRESHOLD": 7500,
  "CHAIN_IRREVERSIBLE_SUPPORT_MIN_RUN": 2,
  "CHAIN_MAX_ACCOUNT_NAME_LENGTH": 25,
  "CHAIN_MAX_ACCOUNT_WITNESS_VOTES": 100,
  "CHAIN_BLOCK_SIZE": 6291456,
  "CHAIN_MAX_COMMENT_DEPTH": 65520,
  "CHAIN_MAX_MEMO_LENGTH": 2048,
  "CHAIN_MAX_WITNESSES": 21,
  "CHAIN_MAX_PROXY_RECURSION_DEPTH": 4,
  "CHAIN_MAX_RESERVE_RATIO": 20000,
  "CHAIN_MAX_SUPPORT_WITNESSES": 10,
  "CHAIN_MAX_SHARE_SUPPLY": "1000000000000000",
  "CHAIN_MAX_SIG_CHECK_DEPTH": 2,
  "CHAIN_MAX_TIME_UNTIL_EXPIRATION": 3600,
  "CHAIN_MAX_TRANSACTION_SIZE": 65536,
  "CHAIN_MAX_UNDO_HISTORY": 10000,
  "CHAIN_MAX_VOTE_CHANGES": 5,
  "CHAIN_MAX_TOP_WITNESSES": 11,
  "CHAIN_MAX_WITHDRAW_ROUTES": 10,
  "CHAIN_MAX_WITNESS_URL_LENGTH": 2048,
  "CHAIN_MIN_ACCOUNT_CREATION_FEE": 1000,
  "CHAIN_MIN_ACCOUNT_NAME_LENGTH": 2,
  "CHAIN_MIN_BLOCK_SIZE_LIMIT": 65536,
  "CHAIN_MAX_BLOCK_SIZE_LIMIT": 2097152,
  "CHAIN_NULL_ACCOUNT": "null",
  "CHAIN_NUM_INITIATORS": 0,
  "CHAIN_PROXY_TO_SELF_ACCOUNT": "",
  "CHAIN_SECONDS_PER_YEAR": 31536000,
  "CHAIN_VESTING_WITHDRAW_INTERVALS": 28,
  "CHAIN_VESTING_WITHDRAW_INTERVAL_SECONDS": 86400,
  "CHAIN_ENERGY_REGENERATION_SECONDS": 432000,
  "TOKEN_SYMBOL": 1514755587,
  "SHARES_SYMBOL": "23438642651878150",
  "CHAIN_NAME": "VIZ",
  "CHAIN_BLOCK_GENERATION_POSTPONED_TX_LIMIT": 5,
  "CHAIN_PENDING_TRANSACTION_EXECUTION_LIMIT": 200000
}
  • get_database_info — возвращает информацию по использованию базы данных (по типу объектов);

Пример:

{"id":1,"method":"call","jsonrpc":"2.0","params":["database_api","get_database_info",[]]}

Ответ:

{
  "total_size": "6442450944",
  "free_size": 1828375424,
  "reserved_size": 0,
  "used_size": "4614075520",
  "index_list": [
    {
      "name": "graphene::chain::dynamic_global_property_object",
      "record_count": 1
    },
    {
      "name": "graphene::chain::account_object",
      "record_count": 3700
    },
    {
      "name": "graphene::chain::account_authority_object",
      "record_count": 3700
    },
    {
      "name": "graphene::chain::witness_object",
      "record_count": 59
    },
    {
      "name": "graphene::chain::transaction_object",
      "record_count": 4
    },
    {
      "name": "graphene::chain::block_summary_object",
      "record_count": 65536
    },
    {
      "name": "graphene::chain::witness_schedule_object",
      "record_count": 1
    },
    {
      "name": "graphene::chain::content_object",
      "record_count": 0
    },
    {
      "name": "graphene::chain::content_type_object",
      "record_count": 0
    },
    {
      "name": "graphene::chain::content_vote_object",
      "record_count": 0
    },
    {
      "name": "graphene::chain::witness_vote_object",
      "record_count": 239
    },
    {
      "name": "graphene::chain::hardfork_property_object",
      "record_count": 1
    },
    {
      "name": "graphene::chain::withdraw_vesting_route_object",
      "record_count": 7
    },
    {
      "name": "graphene::chain::master_authority_history_object",
      "record_count": 4
    },
    {
      "name": "graphene::chain::account_recovery_request_object",
      "record_count": 0
    },
    {
      "name": "graphene::chain::change_recovery_account_request_object",
      "record_count": 0
    },
    {
      "name": "graphene::chain::escrow_object",
      "record_count": 0
    },
    {
      "name": "graphene::chain::vesting_delegation_object",
      "record_count": 286
    },
    {
      "name": "graphene::chain::fix_vesting_delegation_object",
      "record_count": 0
    },
    {
      "name": "graphene::chain::vesting_delegation_expiration_object",
      "record_count": 12
    },
    {
      "name": "graphene::chain::account_metadata_object",
      "record_count": 3700
    },
    {
      "name": "graphene::chain::proposal_object",
      "record_count": 0
    },
    {
      "name": "graphene::chain::required_approval_object",
      "record_count": 0
    },
    {
      "name": "graphene::chain::committee_request_object",
      "record_count": 39
    },
    {
      "name": "graphene::chain::committee_vote_object",
      "record_count": 453
    },
    {
      "name": "graphene::chain::invite_object",
      "record_count": 364
    },
    {
      "name": "graphene::chain::award_shares_expire_object",
      "record_count": 2463
    },
    {
      "name": "graphene::chain::paid_subscription_object",
      "record_count": 4
    },
    {
      "name": "graphene::chain::paid_subscribe_object",
      "record_count": 26
    },
    {
      "name": "graphene::chain::witness_penalty_expire_object",
      "record_count": 4
    },
    {
      "name": "graphene::plugins::private_message::message_object",
      "record_count": 0
    },
    {
      "name": "graphene::plugins::operation_history::operation_object",
      "record_count": 11967735
    },
    {
      "name": "graphene::plugins::account_history::account_history_object",
      "record_count": 12014504
    }
  ]
}
  • get_dynamic_global_properties — возвращает данные о динамических глобальных свойствах сети;

Пример:

{"id":1,"method":"call","jsonrpc":"2.0","params":["database_api","get_dynamic_global_properties",[]]}

Ответ:

{
  "id": 0,
  "head_block_number": 10920301,
  "head_block_id": "00a6a16d8a63db35fa727c62a49c00bf9d963819",
  "genesis_time": "2018-09-29T10:23:24",
  "time": "2019-10-14T12:22:54",
  "current_witness": "ae.witness",
  "committee_fund": "1442755.925 VIZ",
  "committee_requests": 39,
  "current_supply": "55206722.493 VIZ",
  "total_vesting_fund": "27501277.264 VIZ",
  "total_vesting_shares": "27501269.766153 SHARES",
  "total_reward_fund": "28351.836 VIZ",
  "total_reward_shares": "6610384216022",
  "average_block_size": 120,
  "maximum_block_size": 65536,
  "current_aslot": 10946390,
  "recent_slots_filled": "340282366920938463463374607431768211455",
  "participation_count": 128,
  "last_irreversible_block_num": 10920284,
  "max_virtual_bandwidth": "5986734968066277376",
  "current_reserve_ratio": 20000,
  "vote_regeneration_per_day": 1,
  "bandwidth_reserve_candidates": 1,
  "inflation_calc_block_num": 10315901,
  "inflation_witness_percent": 2000,
  "inflation_ratio": 5000
}
  • get_escrow — возвращает информацию о трехсторонней сделке по логину аккаунта и идентификатору сделки (id);
  • get_expiring_vesting_delegations — возвращает список возвращаемой делегированной доли (по времени истечения возврата);
  • get_hardfork_version — возвращает версию текущего хардфорка;
  • get_master_history — возвращает список истории изменений master привилегий для указанного аккаунта (в виде объекта master_authority_history_api_object);
  • get_next_scheduled_hardfork — возвращает следующий запланированный хардфорк;

Пример:

{"id":1,"method":"call","jsonrpc":"2.0","params":["database_api","get_next_scheduled_hardfork",[]]}

Ответ:

{
  "hf_version": "2.4.0",
  "live_time": "2019-04-30T05:00:00"
}
  • get_potential_signatures — возвращает потенциальные публичные ключи для подписи транзакции;

Пример:

{"id":1,"method":"call","jsonrpc":"2.0","params":["database_api","get_potential_signatures",[{"ref_block_num":41097,"ref_block_prefix":1234018187,"expiration":"2019-10-14T12:21:46","operations":[["award",{"initiator":"social","receiver":"social","energy":15,"custom_sequence":0,"memo":"ubi","beneficiaries":[]}]],"extensions":[]}]]}

Ответ:

[
  "VIZ5iCdUDKypJU3iCp1vbkTk5P7hEW1GgK6E75V1yZZznGU7NuV32"
]
  • get_proposed_transactions — возвращает все предложенные транзакции к аккаунту (атрибуты account, from — начальный номер транзакции, limit — пороговое значение);
  • get_recovery_request — возвращает данные по запросу на восстановление доступа к аккаунту;
  • get_required_signatures — возвращает публичные ключи из предложенных, которые необходимы для подписи транзакции (нужно направить транзакцию без подписи и предоставить открытые ключи);

Пример:

{"id":1,"method":"call","jsonrpc":"2.0","params":["database_api","get_required_signatures",[{"ref_block_num":41097,"ref_block_prefix":1234018187,"expiration":"2019-10-14T12:21:46","operations":[["award",{"initiator":"social","receiver":"social","energy":15,"custom_sequence":0,"memo":"ubi","beneficiaries":[]}]],"extensions":[]},["VIZ7TAJxC1ibwYEgWon3YWXJdjKaTPS3eVy9zSKq2cRFECMuJvHcq","VIZ5iCdUDKypJU3iCp1vbkTk5P7hEW1GgK6E75V1yZZznGU7NuV32"]]]}

Ответ:

[
  "VIZ5iCdUDKypJU3iCp1vbkTk5P7hEW1GgK6E75V1yZZznGU7NuV32"
]
  • get_transaction_hex — возвращает hex значение сырой транзакции;

Пример:

{"id":1,"method":"call","jsonrpc":"2.0","params":["database_api","get_transaction_hex",[{"ref_block_num":41097,"ref_block_prefix":1234018187,"expiration":"2019-10-14T12:21:46","operations":[["award",{"initiator":"social","receiver":"social","energy":15,"custom_sequence":0,"memo":"ubi","beneficiaries":[]}]],"extensions":[],"signatures":["1f64cb23ba686126f9a00d904840e472de44e0e2291eca5c6b380083ee7a0b9f9934bbe9f03404a33a6df0630a020ff4750b886b65f4af8cd9877df8128d45da05"]}]]}

Ответ:

89a08b9f8d495a68a45d012f06736f6369616c06736f6369616c0f000000000000000000037562690000011f64cb23ba686126f9a00d904840e472de44e0e2291eca5c6b380083ee7a0b9f9934bbe9f03404a33a6df0630a020ff4750b886b65f4af8cd9877df8128d45da05
  • get_vesting_delegations — возвращает список делегированной доли аккаунта;
  • get_withdraw_routes — возвращает массив путей понижения доли для аккаунта;
  • lookup_account_names — возвращает массив аккаунтов по запрошенным логинам;

Пример:

{"id":1,"method":"call","jsonrpc":"2.0","params":["database_api","lookup_account_names",[["wildviz","zozo"]]]}

Ответ:

[
  {
    "id": 3276,
    "name": "wildviz",
    "master_authority": {
      "weight_threshold": 1,
      "account_auths": [],
      "key_auths": [
        [
          "VIZ7TAJxC1ibwYEgWon3YWXJdjKaTPS3eVy9zSKq2cRFECMuJvHcq",
          1
        ]
      ]
    },
    "active_authority": {
      "weight_threshold": 1,
      "account_auths": [],
      "key_auths": [
        [
          "VIZ8dJxeKPXe5wf6bnqaMsj3EW8EbMURoKxR4RqPCQH8xA89b6RpC",
          1
        ]
      ]
    },
    "regular_authority": {
      "weight_threshold": 1,
      "account_auths": [],
      "key_auths": [
        [
          "VIZ8iKb38H1JD7PZqEYpoLX3nMt1mgfoh2LwngexvywusE7fgrd5G",
          1
        ]
      ]
    },
    "memo_key": "VIZ8mvAu9beH6gDtBrdy3oh8eTGP4tKpNXHvY7wtGm7EXLuwFaRi7",
    "json_metadata": "",
    "proxy": "",
    "referrer": "",
    "last_master_update": "1970-01-01T00:00:00",
    "last_account_update": "1970-01-01T00:00:00",
    "created": "2019-03-14T08:33:21",
    "recovery_account": "xchng",
    "last_account_recovery": "1970-01-01T00:00:00",
    "subcontent_count": 0,
    "vote_count": 10,
    "content_count": 0,
    "awarded_rshares": 0,
    "custom_sequence": 2,
    "custom_sequence_block_num": 10319967,
    "energy": 9995,
    "last_vote_time": "2019-10-14T08:08:48",
    "balance": "0.000 VIZ",
    "vesting_shares": "19157.679056 SHARES",
    "delegated_vesting_shares": "250.000000 SHARES",
    "received_vesting_shares": "0.000000 SHARES",
    "vesting_withdraw_rate": "0.000000 SHARES",
    "next_vesting_withdrawal": "1969-12-31T23:59:59",
    "withdrawn": 1500000000,
    "to_withdraw": 1500000000,
    "withdraw_routes": 0,
    "curation_rewards": 0,
    "posting_rewards": 0,
    "receiver_awards": 255786,
    "benefactor_awards": 2958047,
    "proxied_vsf_votes": [
      0,
      0,
      0,
      0
    ],
    "witnesses_voted_for": 1,
    "witnesses_vote_weight": "19157679056",
    "last_post": "1970-01-01T00:00:00",
    "last_root_post": "1970-01-01T00:00:00",
    "average_bandwidth": 1089649559,
    "lifetime_bandwidth": "24196000000",
    "last_bandwidth_update": "2019-10-14T08:08:48",
    "witness_votes": [],
    "valid": true,
    "account_seller": "",
    "account_offer_price": "0.000 VIZ",
    "account_on_sale": false,
    "account_on_sale_start_time": "1970-01-01T00:00:00",
    "subaccount_seller": "",
    "subaccount_offer_price": "0.000 VIZ",
    "subaccount_on_sale": false
  },
  null
]
  • lookup_accounts — возвращает массив логинов аккаунтов по нижней границе с указанием количества возвращаемых элементов (не более 1000);

Пример:

{"id":1,"method":"call","jsonrpc":"2.0","params":["database_api","lookup_accounts",["ae","10"]]}

Ответ:

[
  "ae",
  "ae-witness",
  "ae.witness",
  "ae0",
  "ae1",
  "ae10",
  "ae100",
  "ae101",
  "ae102",
  "ae103"
]
  • verify_account_authority — проверяет подпись транзакции отдельным аккаунтом с указанием публичного ключа;
  • verify_authority — принимает подписанную транзакцию и возвращает TRUE, если транзакция подписана всеми необходимыми ключами;

account_by_key

  • get_key_references — возвращает массив логинов аккаунтов, которые содержат указанный публичный ключ.

Пример запроса:

{"id":1,"method":"call","jsonrpc":"2.0","params":["account_by_key","get_key_references",[["VIZ5Z2po7K5CoCXw2xLPPt8JJvJLJ3xVNANLgTy9KDfLeZH2urSSd","VIZ1111111111111111111111111111111114T1Anm"]]]}

Ответ:

[
  [
    "on1x"
  ],
  [
    "viz"
  ]
]

account_history

  • get_account_history — возвращает историю операций (в том числе и виртуальных), связанных с определенным аккаунтом. В конфигурационном файле может быть многократно указан track-account-range в виде json строки: ["from","to"].

Пример запроса:

{"id":1,"method":"call","jsonrpc":"2.0","params":["account_history","get_account_history",["on1x","-1","5"]]}

Ответ:

[
  [
    3057,
    {
      "trx_id": "d5f53163bc237b17c8fd6f3278d3ca1b4ae21691",
      "block": 10913871,
      "trx_in_block": 0,
      "op_in_trx": 0,
      "virtual_op": 0,
      "timestamp": "2019-10-14T07:01:18",
      "op": [
        "transfer",
        {
          "from": "viz-social-bot",
          "to": "on1x",
          "amount": "7.725 VIZ",
          "memo": "withdraw:3353"
        }
      ]
    }
  ],
  [
    3058,
    {
      "trx_id": "ac8c4c225334dc59ec604dfa3d56b55dfc0647d3",
      "block": 10916119,
      "trx_in_block": 0,
      "op_in_trx": 0,
      "virtual_op": 1,
      "timestamp": "2019-10-14T08:53:42",
      "op": [
        "receive_award",
        {
          "initiator": "dignity",
          "receiver": "on1x",
          "custom_sequence": 0,
          "memo": "telegram:151842302",
          "shares": "58.246984 SHARES"
        }
      ]
    }
  ]
]

committee_api

  • get_committee_request — возвращает информацию о заявке (можно указать возвращаемое количество голосов за заявку);

Пример:

{"id":1,"method":"call","jsonrpc":"2.0","params":["committee_api","get_committee_request",["39","1"]]}

Ответ:

{
  "id": 38,
  "request_id": 39,
  "url": "https://control.viz.world/media/@wildviz/committee-1/",
  "creator": "wildviz",
  "worker": "wildviz",
  "required_amount_min": "0.000 VIZ",
  "required_amount_max": "10000.000 VIZ",
  "start_time": "2019-09-23T15:49:39",
  "duration": 432000,
  "end_time": "2019-09-28T15:49:39",
  "status": 5,
  "votes_count": 23,
  "conclusion_time": "2019-09-28T15:49:39",
  "conclusion_payout_amount": "10000.000 VIZ",
  "payout_amount": "10000.000 VIZ",
  "remain_payout_amount": "0.000 VIZ",
  "last_payout_time": "2019-09-28T15:56:42",
  "payout_time": "2019-09-28T15:56:42",
  "votes": [
    {
      "voter": "wildviz",
      "vote_percent": 10000,
      "last_update": "2019-09-23T15:52:09"
    }
  ]
}
  • get_committee_request_votes — возвращает массив всех голосов по заявке;

Пример:

{"id":1,"method":"call","jsonrpc":"2.0","params":["committee_api","get_committee_request_votes",["39"]]}

Ответ:

[
  {
    "voter": "wildviz",
    "vote_percent": 10000,
    "last_update": "2019-09-23T15:52:09"
  },
  {
    "voter": "denis-golub",
    "vote_percent": 10000,
    "last_update": "2019-09-23T15:54:51"
  },
  {
    "voter": "lex",
    "vote_percent": 10000,
    "last_update": "2019-09-23T15:54:51"
  },
  ...
]
  • get_committee_requests_list — возвращает массив идентификаторов заявок в комитет по статусу (0 — ожидает рассмотрения, 1 — отменена создателем, 2 — недостаточно голосов, 3 — недостаточная выплата для заявки, 4 — заявка одобрена и ожидает выплат, 5 — выплаты завершены);

Пример:

{"id":1,"method":"call","jsonrpc":"2.0","params":["committee_api","get_committee_requests_list",["2"]]}

Ответ:

[
  2,
  3,
  13,
  14,
  33
]

invite_api

  • get_invite_by_id — возвращает инвайт по идентификатору;

Пример:

{"id":1,"method":"call","jsonrpc":"2.0","params":["invite_api","get_invite_by_id",["0"]]}

Ответ:

{
  "id": 0,
  "creator": "denis-skripnik",
  "receiver": "liveblogs",
  "invite_key": "VIZ5bqU5UEig5o8gESpJCy662LLXQuZWHX49puiZtGhv3ZxmN6e3t",
  "invite_secret": "5JopL2TpywM2WKx83aTLnwLZjZ58ZEnK7aBJ3L2V2KjaVA1Neko",
  "balance": "0.000 VIZ",
  "claimed_balance": "100.000 VIZ",
  "create_time": "2018-10-05T02:12:57",
  "claim_time": "2018-10-05T11:55:36",
  "status": 2
}
  • get_invite_by_key — возвращает инвайт по публичному ключу;
  • get_invites_list — возвращает массив идентификаторов инвайтов по статусу (0 — не активированные, 1 — погашенные на аккаунт, 2 — использованы для регистрации аккаунта);

Пример:

{"id":1,"method":"call","jsonrpc":"2.0","params":["invite_api","get_invites_list",["0"]]}

Ответ:

[
  6,
  8,
  15,
  20,
  34,
  ...
]

operation_history

  • get_ops_in_block — возвращает массив операций по номеру блока (можно указать необходимость показывать только виртуальные операции);

Пример:

{"id":1,"method":"call","jsonrpc":"2.0","params":["operation_history","get_ops_in_block",["10913871","false"]]}

Ответ:

[
  {
    "trx_id": "d5f53163bc237b17c8fd6f3278d3ca1b4ae21691",
    "block": 10913871,
    "trx_in_block": 0,
    "op_in_trx": 0,
    "virtual_op": 0,
    "timestamp": "2019-10-14T07:01:18",
    "op": [
      "transfer",
      {
        "from": "viz-social-bot",
        "to": "on1x",
        "amount": "7.725 VIZ",
        "memo": "withdraw:3353"
      }
    ]
  },
  {
    "trx_id": "0122149bdfdbae80a6c3f4cabe36c78e0e20c12d",
    "block": 10913871,
    "trx_in_block": 1,
    "op_in_trx": 0,
    "virtual_op": 0,
    "timestamp": "2019-10-14T07:01:18",
    "op": [
      "transfer",
      {
        "from": "viz-social-bot",
        "to": "dance",
        "amount": "6.827 VIZ",
        "memo": "withdraw:3354"
      }
    ]
  },
  {
    "trx_id": "e8d8da85004234afd048979860d88cd78297ac1e",
    "block": 10913871,
    "trx_in_block": 2,
    "op_in_trx": 0,
    "virtual_op": 0,
    "timestamp": "2019-10-14T07:01:18",
    "op": [
      "transfer",
      {
        "from": "viz-social-bot",
        "to": "hypno",
        "amount": "6.976 VIZ",
        "memo": "withdraw:3355"
      }
    ]
  },
  {
    "trx_id": "0000000000000000000000000000000000000000",
    "block": 10913871,
    "trx_in_block": 65535,
    "op_in_trx": 0,
    "virtual_op": 1,
    "timestamp": "2019-10-14T07:01:21",
    "op": [
      "witness_reward",
      {
        "witness": "wildviz",
        "shares": "0.103999 SHARES"
      }
    ]
  }
]
  • get_transaction — возвращает данные транзакции по её хэшу (id);

Пример:

{"id":1,"method":"call","jsonrpc":"2.0","params":["operation_history","get_transaction",["d5f53163bc237b17c8fd6f3278d3ca1b4ae21691"]]}

Ответ:

{
  "ref_block_num": 34889,
  "ref_block_prefix": 453694329,
  "expiration": "2019-10-14T07:11:20",
  "operations": [
    [
      "transfer",
      {
        "from": "viz-social-bot",
        "to": "on1x",
        "amount": "7.725 VIZ",
        "memo": "withdraw:3353"
      }
    ]
  ],
  "extensions": [],
  "signatures": [
    "203e778ae54b5fe51367bef34a7001eca7257732075723e99f07fd8c4cd0cc8040021925c811fef0ff2151a28193403af6af05f05dadc1427bb0daf604df53ee0c"
  ],
  "transaction_id": "d5f53163bc237b17c8fd6f3278d3ca1b4ae21691",
  "block_num": 10913871,
  "transaction_num": 0
}
  • get_paid_subscriptions — возвращает список платных подписок отсортированных по аккаунту инициатору (принимает 2 параметра: from — количество записей для отступа от начала, limit — ограничение на количество результатов, не может превышать 1000); Пример:
    {"id":1,"method":"call","jsonrpc":"2.0","params":["paid_subscription_api","get_paid_subscriptions",[0,1]]}

Ответ:

[
  {
    "update_time" : "2019-06-05T15:07:57",
    "creator" : "tratata",
    "id" : 3,
    "period" : 30,
    "levels" : 10,
    "url" : "",
    "amount" : 500000
  }
]
  • get_active_paid_subscriptions — возвращает массив активных соглашений по платной подписке для аккаунта;

Пример:

{"id":1,"method":"call","jsonrpc":"2.0","params":["paid_subscription_api","get_active_paid_subscriptions",["on1x"]]}

Ответ:

[
  "viz.world"
]
  • get_inactive_paid_subscriptions — возвращает массив отмененных соглашений по платной подписке для аккаунта;
  • get_paid_subscription_options — возвращает данные о соглашении по платной подписке на аккаунт;

Пример:

{"id":1,"method":"call","jsonrpc":"2.0","params":["paid_subscription_api","get_paid_subscription_options",["viz.world"]]}

Ответ:

{
  "creator": "viz.world",
  "url": "https://control.viz.world/media/@viz.world/ru-service/",
  "levels": 2,
  "amount": 5000,
  "period": 30,
  "update_time": "2019-05-21T07:14:45",
  "active_subscribers": [
    "on1x",
    "muz",
    "litrbooh",
    "antonkostroma",
    "xchng",
    "liveblogs",
    "wildviz",
    "id1club"
  ],
  "active_subscribers_count": 8,
  "active_subscribers_summary_amount": 65000,
  "active_subscribers_with_auto_renewal": [
    "on1x",
    "muz",
    "litrbooh",
    "antonkostroma",
    "liveblogs"
  ],
  "active_subscribers_with_auto_renewal_count": 5,
  "active_subscribers_with_auto_renewal_summary_amount": 35000
}
  • get_paid_subscription_status — возвращает данные о соглашении подписчика по платной подписке на определенный аккаунт;

Пример:

{"id":1,"method":"call","jsonrpc":"2.0","params":["paid_subscription_api","get_paid_subscription_status",["on1x","viz.world"]]}

Ответ:

{
  "subscriber": "on1x",
  "creator": "viz.world",
  "level": 2,
  "amount": 5000,
  "period": 30,
  "start_time": "2019-09-18T07:51:39",
  "next_time": "2019-10-18T07:51:39",
  "end_time": "1969-12-31T23:59:59",
  "active": true,
  "auto_renewal": true
}

witness_api

  • get_active_witnesses — возвращает массив делегатов в текущем раунде (из 21 слота, при наличии такого количества делегатов);

Пример:

{"id":1,"method":"call","jsonrpc":"2.0","params":["witness_api","get_active_witnesses",[]]}

Ответ:

[
  "solox",
  "lexai",
  "xchng",
  "creativity",
  "jackvote",
  "pom-vjfru0njnme",
  "retroscope",
  "wildviz",
  "lex",
  "dordoy",
  "denis-skripnik",
  "dmilash",
  "id1club",
  "lb",
  "ae.witness",
  "denis-golub",
  "litrbooh",
  "mad-max",
  "t3",
  "web3",
  "charity"
]
  • get_witness_by_account — возвращает делегата в соответствии с его логином;
  • get_witness_count — возвращает количество аккаунтов, заявивших о желании быть делегатом;

Пример:

{"id":1,"method":"call","jsonrpc":"2.0","params":["witness_api","get_witness_count",[]]}

Ответ:

59
  • get_witness_schedule — возвращает очередь делегатов дополненную расчитанными медианными значениями параметров сети и мажориторной версией хардфорка;

Пример:

{"id":1,"method":"call","jsonrpc":"2.0","params":["witness_api","get_witness_schedule",[]]}

Ответ:

{
  "id": 0,
  "current_virtual_time": "23461032266075892861574835409469",
  "next_shuffle_block_num": 10916661,
  "current_shuffled_witnesses": "736f6c6f780000000000000000000000000000000000000000000000000000006c657861690000000000000000000000000000000000000000000000000000007863686e6700000000000000000000000000000000000000000000000000000063726561746976697479000000000000000000000000000000000000000000006a61636b766f7465000000000000000000000000000000000000000000000000706f6d2d766a667275306e6a6e6d650000000000000000000000000000000000726574726f73636f70650000000000000000000000000000000000000000000077696c6476697a000000000000000000000000000000000000000000000000006c65780000000000000000000000000000000000000000000000000000000000646f72646f79000000000000000000000000000000000000000000000000000064656e69732d736b7269706e696b000000000000000000000000000000000000646d696c61736800000000000000000000000000000000000000000000000000696431636c7562000000000000000000000000000000000000000000000000006c6200000000000000000000000000000000000000000000000000000000000061652e7769746e657373000000000000000000000000000000000000000000006d61642d6d6178000000000000000000000000000000000000000000000000006c697472626f6f6800000000000000000000000000000000000000000000000070686f746f636c75620000000000000000000000000000000000000000000000743300000000000000000000000000000000000000000000000000000000000064656e69732d676f6c75620000000000000000000000000000000000000000006368617269747900000000000000000000000000000000000000000000000000",
  "num_scheduled_witnesses": 21,
  "median_props": {
    "account_creation_fee": "1.000 VIZ",
    "maximum_block_size": 65536,
    "create_account_delegation_ratio": 10,
    "create_account_delegation_time": 2592000,
    "min_delegation": "1.000 VIZ",
    "min_curation_percent": 0,
    "max_curation_percent": 10000,
    "bandwidth_reserve_percent": 0,
    "bandwidth_reserve_below": "0.000000 SHARES",
    "flag_energy_additional_cost": 0,
    "vote_accounting_min_rshares": 50000,
    "committee_request_approve_min_percent": 1000,
    "inflation_witness_percent": 2000,
    "inflation_ratio_committee_vs_reward_fund": 7500,
    "inflation_recalc_period": 806400,
    "data_operations_cost_additional_bandwidth": 0,
    "witness_miss_penalty_percent": 100,
    "witness_miss_penalty_duration": 86400
  },
  "majority_version": "2.4.0"
}
  • get_witnesses — возвращает массив делегатов в соответствии с их id (можно запросить нескольких в массиве);

Пример:

{"id":1,"method":"call","jsonrpc":"2.0","params":["witness_api","get_witnesses",[["0"]]]}

Ответ:

[
  {
    "id": 0,
    "owner": "committee",
    "created": "1970-01-01T00:00:00",
    "url": "",
    "votes": 0,
    "penalty_percent": 0,
    "counted_votes": 0,
    "virtual_last_update": "0",
    "virtual_position": "0",
    "virtual_scheduled_time": "340282366920938463463374607431768211455",
    "total_missed": 15,
    "last_aslot": 144669,
    "last_confirmed_block_num": 132749,
    "signing_key": "VIZ1111111111111111111111111111111114T1Anm",
    "props": {
      "account_creation_fee": "1.000 VIZ",
      "maximum_block_size": 131072,
      "create_account_delegation_ratio": 10,
      "create_account_delegation_time": 2592000,
      "min_delegation": "0.001 VIZ",
      "min_curation_percent": 1600,
      "max_curation_percent": 1600,
      "bandwidth_reserve_percent": 1000,
      "bandwidth_reserve_below": "500.000000 SHARES",
      "flag_energy_additional_cost": 0,
      "vote_accounting_min_rshares": 5000000,
      "committee_request_approve_min_percent": 1000,
      "inflation_witness_percent": 2000,
      "inflation_ratio_committee_vs_reward_fund": 5000,
      "inflation_recalc_period": 806400,
      "data_operations_cost_additional_bandwidth": 0,
      "witness_miss_penalty_percent": 100,
      "witness_miss_penalty_duration": 86400
    },
    "last_work": "0000000000000000000000000000000000000000000000000000000000000000",
    "running_version": "1.0.1",
    "hardfork_version_vote": "0.0.0",
    "hardfork_time_vote": "1970-01-01T00:00:00"
  }
]
  • get_witnesses_by_vote — возвращает массив делегатов, отсортированных по потенциалу полученных голосов (указывается левая граница списка и количество возвращаемых значений в массиве, но не более 100);

Пример:

{"id":1,"method":"call","jsonrpc":"2.0","params":["witness_api","get_witnesses_by_vote",["","10000"]]}

Ответ:

[
 {
   "id": 42,
   "owner": "solox",
   "created": "2018-12-22T19:09:51",
   "url": "http://viz.world/@solox/witness/",
   "votes": "2265575784725",
   "penalty_percent": 0,
   "counted_votes": "2265575784725",
   "virtual_last_update": "23453249838245982754942708691144",
   "virtual_position": "0",
   "virtual_scheduled_time": "23453400035105083671049497423272",
   "total_missed": 273,
   "last_aslot": 10942638,
   "last_confirmed_block_num": 10916550,
   "signing_key": "VIZ8MzGnSUeqbFaFr8g297XNDT7iWQZ8ktBgeBDYj1moWCHQ8a5PA",
   "props": {
     "account_creation_fee": "1.000 VIZ",
     "maximum_block_size": 65536,
     "create_account_delegation_ratio": 10,
     "create_account_delegation_time": 2592000,
     "min_delegation": "1.000 VIZ",
     "min_curation_percent": 0,
     "max_curation_percent": 10000,
     "bandwidth_reserve_percent": 100,
     "bandwidth_reserve_below": "1.000000 SHARES",
     "flag_energy_additional_cost": 0,
     "vote_accounting_min_rshares": 50000,
     "committee_request_approve_min_percent": 1000,
     "inflation_witness_percent": 3000,
     "inflation_ratio_committee_vs_reward_fund": 7500,
     "inflation_recalc_period": 806400,
     "data_operations_cost_additional_bandwidth": 0,
     "witness_miss_penalty_percent": 100,
     "witness_miss_penalty_duration": 86400
   },
   "last_work": "0000000000000000000000000000000000000000000000000000000000000000",
   "running_version": "2.4.0",
   "hardfork_version_vote": "2.4.0",
   "hardfork_time_vote": "2019-04-30T05:00:00"
 }
]
  • get_witnesses_by_counted_vote — возвращает массив делегатов, отсортированных по потенциалу полученных голосов с учетом наложенного штрафа за пропуск блоков (указывается левая граница списка и количество возвращаемых значений в массиве, но не более 100);
  • lookup_witness_accounts — возвращает массив логинов аккаунтов, которые заявляли себя как делегаты (указывается левая граница списка и количество возвращаемых значений в массиве, но не более 1000).

Пример:

 {"id":1,"method":"call","jsonrpc":"2.0","params":["witness_api","lookup_witness_accounts",["","4"]]}

Ответ:

 [
  "t3",
  "lb",
  "ae",
  "in"
]

Библиотеки для работы с VIZ

Для разработки приложений зачастую используют уже готовые библиотеки. Наличие библиотеки для конкретного языка программирования зависит от наличия заготовок для работы с криптографией, большими числами и траспортными протоколами (http/ws). Так как VIZ исторически эволюционировал из Graphene, то большинство библиотек для таких блокчейн систем, как EOS/Steem/Golos — подходят и для VIZ. Отличительной особенностью является формат общения с нодой (структура json-rpc), порядок и наименование параметров при описании операции, формат сложных данных в бинарном виде (например формат ассетов VIZ и SHARES отличается от нового формата SMT в Steem).

Несмотря на разный синтаксис — основа для взаимодействия с VIZ одна: криптография для ключей и подписи сообщений, проверка подписи по данным и публичному ключу, формирование транзакций, взаимодейстие с нодой.

Каждый разработчик может поднять свою ноду для взаимодействия с VIZ, но для начинающих разбираться существуют публичные ноды:

Ниже перечислины основные библиотеки VIZ, которые поддерживают большинство API запросов к ноде и формирование транзакций.


JavaScript

Фаворит для разработки приложений библиотека viz-js-lib. В нем есть поддержка всего что нужно как для серверного (nodejs), так и для пользовательского (js в браузерах) взаимодействия с VIZ:

  • Создание и кодирование ключей;
  • API-запросы;
  • Формирование транзакций;
  • Упрощенный конструктор транзакций для операций;
  • Функции обратного вызова для запросов;

Документация viz-js-lib на английском языке доступна на GitHub. Примеры для часто использумых операций смотрите в разделе Примеры кода.

Python

Библиотека thallid-viz от ksantoprotein поддерживает как API запросы, так и формирование транзакций. В наличии множество примеров с разными операциями.

Библиотека viz-python-lib поддерживает большинство необходимого, но отсутствуют примеры и документация (библиотека пока недоделана).

PHP

После перехода на адаптированные библиотеки BigNumber и Elliptic Curve стало доступным использовать криптографию без сборки secp256k1 для PHP и включения поддержки GMP.

Библиотека viz-php-lib поддерживает JsonRPC, работу с ключами, формирование транзакций, шифрование сообщение через shared key (совместимое с viz-js-lib), присутствуют примеры, поддержка PSR-4 и установка без дополнительных зависимостей (all-in-one).

Библиотека php-graphene-node-client с поддержкой VIZ, установка которого возможна через Composer.

GO

Библиотека viz-go-lib отлично подходит для API запросов и изучения формирования транзакций. К сожалению документации по библиотеке нет, как и примеров с отдельными операциями.

Swift

Библиотека viz-swift-lib — библиотека на Swift, установка которой возможна через Swift Package Manager.

Dart

Криптографическая библиотека viz_dart_ecc — позволяет формировать публичный ключ из приватного, подписывать данные, верифицировать подпись.

Библиотека viz-transaction — позволяет формировать и подписывать транзакции для блокчейна VIZ (библиотека не содержит методов для трансляции транзакции блокчейн-ноде, для этого нужно использовать любые другие библиотеки для http/ws протокола).

Другое

Если вы не нашли требуемый язык программирования, то можно обратить внимание на существующие библиотеки для EOS и Steem. Чтобы модифицировать их и получить поддержку VIZ достаточно проверить формат json-rpc запросов, поменять chain_id (в VIZ он равен 2040effda178d4fffff5eab7a915d4019879f5205cc5392e4bcced2b6edda0cd — это префикс для подписи сырых транзакций) и настроить конструктор операций.

Примеры кода

Начинающим разработчикам всегда рекомендуется прочесть документации по той или иной библиотеке. Это помогает как понять работу библиотеки, так и запомнить возможности, которые можно использовать при разработке приложений. В данном разделе описаны наиболее популярные запросы в виде примеров. Наиболее используемая библиотека для приложений VIZ — viz-js-lib, поэтому примеры будут с её использованием.


viz-js-lib

Подробная документация на английском с указанием всех методов и их аттрибутов доступно по ссылке.

Подключение библиотеки

В зависимости от серверного (nodejs) или браузерного (js) использования библиотеку нужно подключать разными способами.

Для nodejs актуальной инструкцией будет установка библиотеки через npm install viz-js-lib --save и подключением её в js файле через var viz = require('viz-js-lib');.

Для js подключения можно либо самому собрать webpack библиотеки через консоль npm build, либо воспользоваться уже собранной библиотекой от jsDelivr CND или Unpkg CDN. Просто добавьте к html файлу тэг script и укажите url библиотеки: <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/viz-js-lib@latest/dist/viz.min.js"></script>, после чего у вас будет доступ через консоль к глобальной переменной viz.

Использование публичной ноды

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

  • Публичная нода от делегата lex: https://viz.lexa.host/ для JSON-RPC запросов через HTTPS и wss://viz.lexa.host/ws для JSON-RPC запросов через WebSocket over SSL;
  • Публичная нода от делегата solox: https://solox.world/ для JSON-RPC запросов через HTTPS и wss://solox.world/ws для JSON-RPC запросов через WebSocket over SSL.

Пример настройки viz для работы с нодой https://viz.lexa.host/:

var api_gate='https://viz.lexa.host/';
viz.config.set('websocket',api_gate);

API-запросы

В разделе Плагины и их API были перечислены основные плагины и запросы к ним — все они доступны в библиотеке viz-js-lib. Для того, чтобы выполнить тот или иной запрос, достаточно перевести его название в CamelCase.

Например, если вы решили выполнить запрос get_database_info к плагину database_api, то вам необходимо выполнить код:

viz.api.getDatabaseInfo(function(err,response){
    if(!err){
        //получен ответ
        console.log(response);
    }
    else{
        //ошибка
        console.log(err);
    }
});

В случае, если запрос требует входных данных, то вы добавляете их в начало вызова. Например, для запроса get_active_paid_subscriptions к плагину paid_subscription_api, необходимо указать пользователя, для которого будет произведен поиск активных платных подписок:

var subscriber='on1x';
viz.api.getActivePaidSubscriptions(subscriber,function(err,response){
    if(!err){
        //получен ответ
        console.log(response);
    }
    else{
        //ошибка
        console.log(err);
    }
});

Транслирование транзакций (broadcast)

Для каждой операции из протокола VIZ существует отдельный метод в библиотеке viz-js-lib, который принимает приватный ключ (для подписи транзакции) и параметры операции. Название операции, аналогично API методам, должно быть переведено в формат CamelCase. Пример кода для трансляции (broadcast) операции account_metadata (запись в блокчейн мета-данных аккаунта):

var regular_key='5K...';//приватный ключ
var user_login='test';//логин аккаунта
var metadata={'name':'Тестовый аккаунт','photo':'https://cdn.pixabay.com/photo/2015/12/06/14/14/tokyo-1079524_960_720.jpg'};
viz.broadcast.accountMetadata(regular_key,user_login,JSON.stringify(metadata),function(err,result){
    if(!err){
        //транзакция принята публичной нодой
        console.log(result);
    }
    else{
        //нода не приняла транзакцию
        console.log(err);
    }
});

Динамические глобальные свойства сети

Часть новичков хотят периодически опрашивать ноду и получать актуальные данные о DGP (Dynamic Global Properties), чтобы на основе этого показывать новые блоки, исполнять условия по необратимому блоку или подсвечивать в списке делегатов последнего, кто подписал блок. Для этого достаточно запрашивать данные по таймеру каждые 3 секунды (время между блоками):

var dgp={}
function update_dgp(auto=false){
    viz.api.getDynamicGlobalProperties(function(err,response){
        if(!err){
            dgp=response;
        }
    });
    if(auto){
        setTimeout("update_dgp(true)",3000);
    }
}
update_dgp(true);

Получение информации об аккаунте

Пример кода, для получения информации об аккаунте и рассчета актуального значения энергии аккаунта (учитывая скорость её восстановления):

var current_user='on1x';
viz.api.getAccounts([current_user],function(err,response){
    if(!err){
        //получен ответ
        if(typeof response[0] !== 'undefined'){
            //мы запросили массив аккаунтов, смотрим нулевой элемент, соответствующий current_user
            let last_vote_time=Date.parse(response[0].last_vote_time);
            //учитываем временную зону пользователя
            let delta_time=parseInt((new Date().getTime() - last_vote_time + (new Date().getTimezoneOffset()*60000))/1000);
            let energy=response[0].energy;
            //рассчитываем востановленную энергию
            //скорость восстановления энергии от 0% до 100% CHAIN_ENERGY_REGENERATION_SECONDS 5 дней (432000 секунд)
            let new_energy=parseInt(energy+(delta_time*10000/432000));
            //энергии не может быть больше 100%
            if(new_energy>10000){
                new_energy=10000;
            }
            console.log('актуальная энергия аккаунта',new_energy);
        }
        else{
            console.log('аккаунт не найден',current_user);
        }
    }
    else{
        //ошибка
        console.log(err);
    }
});

Работа с ключами

Криптографические ключи представляют собой координаты X и Y которые записаны в общепринятом формате DER на эллептической кривой secp256k1 (в качестве хеш-функции служит SHA-256). В библиотеке viz-js-lib преобразования и работа с ключами относятся к модулю viz.auth.

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

Часть приложений условились использовать эти правила, таким образом упрощая доступ пользователем к разным возможностям аккаунта по общему паролю. Например, пользователь test зарегистрирован используя общий пароль PK3452JENDK332. При авторизации в приложении используя эти логин и пароль, приложение может самостоятельно сформировать ключи нужного типа доступа, просто используя конкатенацию строк. Пользователь хочет перевести токены? Приложение формирует налету приватный активный ключ по строке testPK3452JENDK332active. Пользователь награждает кого-то? Приложение формирует приватный регулярный ключ по строке testPK3452JENDK332regular. Это упрощает доступ для пользователя по общему паролю, но лишает гибкости и подвергает опасности аккаунт. Типы доступа имеют разные полномочия и при компрометации доверенного окружения доверенного окружения пользователя или сайта доступ к аккаунту может быть перехвачен. Поэтому часть приложений отказываются от правил или договоренностей ради безопасности пользователей и не поддерживают общий пароль.

Регистрация аккаунта

Обычно, при регистрации пользователя, приложение генерирует пароль самостоятельно. Но бывает исключения, когда приложение позволяет использовать свой пароль для генерации ключей. Библиотека позволяет самостоятельно указать строки для генерации ключа в методе viz.auth.toWif(account_login,general_pass,auth_type);. В примере ниже представлена функция для генерации случайного пароля заданной длины и генерации ключа по нему без привязки к пользователю и типу доступа:

function pass_gen(length=100,to_wif=true){
    let charset='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-=_:;.,@!^&*$';
    let ret='';
    for (var i=0,n=charset.length;i<length;++i){
        ret+=charset.charAt(Math.floor(Math.random()*n));
    }
    if(!to_wif){
        return ret;
    }
    let wif=viz.auth.toWif('',ret,'');
    return wif;
}

Получить публичный ключ по заданному приватному можно методом viz.auth.wifToPublic(wif). Для тех приложений, которые хотят формировать ключи по кокатенации логина, пароля и типа доступа, существует метод viz.auth.getPrivateKeys(account_login,general_pass,auth_types);. Метод возвращает массив по шаблону result.type для приватных ключей (которые нужно передать пользователю) и result.typePubkey для публичных ключей (которые нужно транслировать в блокчейн для сохранения за аккаунтом пользователя). Код функции для регистрации нового аккаунта в VIZ по главному паролю:

var user_login='test';//аккаунт регистратор
var active_key='5K...';//приватный активный ключ

function create_account_with_general_pass(account_login,token_amount,shares_amount,general_pass){
    let fixed_token_amount=''+parseFloat(token_amount).toFixed(3)+' VIZ';//токены, которые будут переведены в долю новому аккаунту
    let fixed_shares_amount=''+parseFloat(shares_amount).toFixed(6)+' SHARES';//доля, которая будет делегирована новому аккаунту
    if(''==token_amount){
        fixed_token_amount='0.000 VIZ';
    }
    if(''==shares_amount){
        fixed_shares_amount='0.000000 SHARES';
    }
    let auth_types = ['regular','active','master','memo'];//типы доступов
    let keys=viz.auth.getPrivateKeys(account_login,general_pass,auth_types);
    //типы доступов содержат публичные ключи с весом, достаточным для совершения операций
    let master = {
        'weight_threshold': 1,
        'account_auths': [],
        'key_auths': [
            [keys.masterPubkey, 1]
        ]
    };
    let active = {
        'weight_threshold': 1,
        'account_auths': [],
        'key_auths': [
            [keys.activePubkey, 1]
        ]
    };
    let regular = {
        "weight_threshold": 1,
        "account_auths": [],
        "key_auths": [
            [keys.regularPubkey, 1]
        ]
    };
    let memo_key=keys.memoPubkey;
    let json_metadata='';
    let referrer='';
    viz.broadcast.accountCreate(active_key, fixed_token_amount, fixed_shares_amount, user_login, account_login, master, active, regular, memo_key, json_metadata, referrer, [],function(err,result){
        if(!err){
            console.log('VIZ Account: '+account_login+'\r\nGeneral pass (for private keys): '+general_pass+'\r\nPrivate master key: '+keys.master+'\r\nPrivate active key: '+keys.active+'\r\nPrivate regular key: '+keys.regular+'\r\nPrivate memo key: '+keys.memo+'');
        }
        else{
            console.log(err);
        }
    });
}

Создание ваучера (инвайт-кода)

В блокчейне VIZ есть механика ваучеров. Их может создать кто угодно, переведя в него токены VIZ. Ваучеры можно погасить или использовать в качестве инвайт-кода для упрощенной регистрации нового аккаунта. В первом случае токены поступают на счет аккаунта предъявителя, во втором случае токены конвертируются в долю сети для нового аккаунта (с единым ключом доступа для всех типов доступа).

Пример кода для создания ваучера:

var user_login='test';
var active_key='5K...';//приватный активный ключ
var fixed_amount='100.000 VIZ';//количество токенов затраченные на ваучер
var private_key=pass_gen();//генерируем приватный ключ
var public_key=viz.auth.wifToPublic(private_key);//получаем публичный ключ из приватного
viz.broadcast.createInvite(active_key,user_login,fixed_amount,public_key,function(err,result){
    if(!err){
        console.log('Ваучер создан, публичный ключ для проверки: '+public_key+', приватный ключ для использования: '+private_key);
    }
    else{
        console.log(err);
    }
});

Регистрация через инвайт-код

Для анонимного использования инвайт-кода в системе существует аккаунт invite с приватным активным ключом 5KcfoRuDfkhrLCxVcE9x51J6KN9aM9fpb78tLrvvFckxVV6FyFW, пример кода:

var receiver='newtestaccount';//логин нового аккаунта
var secret_key='5K...';//приватный ключ инвайт-кода
var private_key=pass_gen();//генерируем приватный ключ
var public_key=viz.auth.wifToPublic(private_key);//получаем публичный ключ из приватного
viz.broadcast.inviteRegistration('5KcfoRuDfkhrLCxVcE9x51J6KN9aM9fpb78tLrvvFckxVV6FyFW','invite',receiver,secret_key,public_key,function(err,result){
    if(!err){
        console.log('Аккаунт '+receiver+' зарегистрирован, общий приватный ключ для всех типов доступа: '+private_key);
    }
    else{
        console.log(err);
    }
});

Погашение ваучера

Для анонимного использования ваучера в системе существует аккаунт invite с приватным активным ключом 5KcfoRuDfkhrLCxVcE9x51J6KN9aM9fpb78tLrvvFckxVV6FyFW, пример кода:

var receiver='test';//логин аккаунта
var secret_key='5K...';//приватный ключ инвайт-кода
var public_key=viz.auth.wifToPublic(secret_key);//получаем публичный ключ ваучера
viz.broadcast.claimInviteBalance('5KcfoRuDfkhrLCxVcE9x51J6KN9aM9fpb78tLrvvFckxVV6FyFW','invite',receiver,secret_key,function(err,result){
    if(!err){
        console.log('Аккаунт '+receiver+' успешно погасил ваучер с публичным ключом '+public_key);
    }
    else{
        console.log(err);
    }
});

Конвертация токенов VIZ в долю

Для автоматической конвертации всех доступных токенов VIZ в долю сети SHARES необходимо запросить информацию об аккаунте и конвертировать их себе же в долю операцией transfer_to_vesting:

var current_user='test';
var active_key='5K...';//приватный активный ключ
viz.api.getAccounts([current_user],function(err,response){
    if(!err){
        //получен ответ
        if(typeof response[0] !== 'undefined'){
            if('0.000 VIZ'!=response[0].balance){
                viz.broadcast.transferToVesting(active_key,current_user,current_user,response[0].balance,function(err,result){
                    if(!err){
                        console.log('конвертация в долю сети',response[0].balance);
                        console.log(result);
                    }
                    else{
                        console.log(err);
                    }
                });
            }
            else{
                console.log('баланс на нуле');
            }
        }
        else{
            console.log('аккаунт не найден',current_user);
        }
    }
    else{
        //ошибка
        console.log(err);
    }
});

Конвертация доли в токены VIZ

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

var current_user='test';
var active_key='5K...';//приватный активный ключ
viz.api.getAccounts([current_user],function(err,response){
    if(!err){
        //получен ответ
        if(typeof response[0] !== 'undefined'){
            vesting_shares=parseFloat(response[0].vesting_shares);
            delegated_vesting_shares=parseFloat(response[0].delegated_vesting_shares);
            shares=vesting_shares - delegated_vesting_shares;
            let fixed_shares=''+shares.toFixed(6)+' SHARES';
            console.log('доступные SHARES для конвертации',fixed_shares);
            viz.broadcast.withdrawVesting(active_key,current_user,fixed_shares,function(err,result){
                if(!err){
                    console.log('запуск конвертации доли сети в токены VIZ',fixed_shares);
                    console.log(result);
                }
                else{
                    console.log(err);
                }
            });
        }
        else{
            console.log('аккаунт не найден',current_user);
        }
    }
    else{
        //ошибка
        console.log(err);
    }
});

Перевод токенов

Пример перевода 1.000 VIZ из баланса аккаунта в комитет:

var current_user='test';
var active_key='5K...';//приватный активный ключ
var target='committee';
var fixed_amount='1.000 VIZ';
var memo='Заметка';//utf-8 включая emoji
viz.broadcast.transfer(active_key,current_user,target,fixed_amount,memo,function(err,result){
    if(!err){
        //получен ответ
        console.log(result);
    }
    else{
        //ошибка
        console.log(err);
    }
});

Награждение участика сети

Аккаунт может наградить другого участника сети используя операцию award. Можно указать цель награды target, причину (номер custom_sequence или заметку memo), а также бенефициаров (аккаунты, которые разделят награду цели). Пример:

var current_user='test';//аккаунт награждающего
var regular_key='5K...';//приватный обычный ключ награждающего

var target='viz.plus';//цель награды - заказчик статьи
var energy='1000';//10.00% будут потрачены из актуальной энергии аккаунта
var custom_sequence=0;//номер custom операции
var memo='спасибо за viz cookbook';//utf-8 включая emoji
var beneficiaries_list=[{"account":"on1x","weight":2000}];//20% автору статьи
viz.broadcast.award(regular_key,current_user,target,energy,custom_sequence,memo,beneficiaries_list,function(err,result){
    if(!err){
        //получен ответ
        console.log(result);
    }
    else{
        //ошибка
        console.log(err);
    }
});

Смена ключей аккаунта

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

Полномочия для выполнения операций имеют следующую структуру:

  • weight_threshold — необходимый вес для одобрения транзакции с операциями нужного типа;
  • account_auths — массив аккаунтов и их весов. Аккаунты могут добавить вес транзакции при добавлении к ней подписи ключом;
  • key_auths — массив публичных ключей и их весов.

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

Пример полномочий с одним ключом:

{
    "weight_threshold": 1,
    "account_auths": [],
    "key_auths": [
        ["VIZ6LWhhUzKmYM5VcxtC2FpEjzr71imfb7DeGA9yodeqnvtP2SYjA", 1]
    ]
}

Если у аккаунта будет записаны эти полномочия в тип доступа regular, то для совершения операции награждения блокчейн будет требовать подпись транзакции ключом 5KRLZitDd5c9uZzDgTMF4se4eVewENtZ29GbCuKwbT3msRbtLgi (которому соответствует указанный в полномочиях публичный ключ VIZ6LWhhUzKmYM5VcxtC2FpEjzr71imfb7DeGA9yodeqnvtP2SYjA).

При делегировании управления другому аккаунту, например test, необходимо изменить полномочия на:

{
    "weight_threshold": 1,
    "account_auths": [
        ["test", 1]
    ],
    "key_auths": [
        ["VIZ6LWhhUzKmYM5VcxtC2FpEjzr71imfb7DeGA9yodeqnvtP2SYjA", 1]
    ]
}

После этого блокчейн будет требовать подпись либо указанным ключом, либо ключом аналогичного типа доступа аккаунта test.

Мульти-подписное управление предполагает усложнение полномочий, например для управления 2 из 3 можно использовать полномочие:

{
    "weight_threshold": 4,
    "account_auths": [],
    "key_auths": [
        ["VIZ6LWhhUzKmYM5VcxtC2FpEjzr71imfb7DeGA9yodeqnvtP2SYjA", 2],
        ["VIZ5mK1zLnYHy7PbnsxRpS4NbKjEoH2J9eBmgSjVKJ5BKQpLLj9T4", 2],
        ["VIZ4uiqeDPsoteSFVbTWPBUbmfzxYkJyXYmA6B1pAFWZ59n4iBuUK", 2]
    ]
}

Для того чтобы транзакция была принята блокчейном, необходимо добавить подписи как минимум 2 из 3 указанных ключей. В этом примере публичному ключу VIZ4uiqeDPsoteSFVbTWPBUbmfzxYkJyXYmA6B1pAFWZ59n4iBuUK соответствует приватный ключ 5KMBKopgd56MZvV8FYhp5AP7AWFyLKiybqRnZYgjXukw34VRE78.

Рассмотрим пример сброса доступов к аккаунту (смену всех ключей и полномочий):

//функция требует приватный мастер ключ от аккаунта
function reset_account_with_general_pass(account_login,master_key,general_pass){
    if(''==general_pass){
        //если не указан общий пароль, сгенерируем его
        general_pass=pass_gen(50,false);
    }
    let auth_types = ['regular','active','master','memo'];
    let keys=viz.auth.getPrivateKeys(account_login,general_pass,auth_types);
    let master = {
        'weight_threshold': 1,
        'account_auths': [],
        'key_auths': [
            [keys.masterPubkey, 1]
        ]
    };
    let active = {
        'weight_threshold': 1,
        'account_auths': [],
        'key_auths': [
            [keys.activePubkey, 1]
        ]
    };
    let regular = {
        'weight_threshold': 1,
        'account_auths': [],
        'key_auths': [
            [keys.regularPubkey, 1]
        ]
    };
    let memo_key=keys.memoPubkey;
    viz.api.getAccounts([account_login],function(err,response){
        if(0==response.length){
            err=true;
        }
        if(!err){
            let json_metadata=response[0].json_metadata;
            viz.broadcast.accountUpdate(master_key,account_login,master,active,regular,memo_key,json_metadata,function(err,result){
                if(!err){
                    console.log('Reset Account: '+account_login+'\r\nGeneral pass (for private keys): '+general_pass+'\r\nPrivate master key: '+keys.master+'\r\nPrivate active key: '+keys.active+'\r\nPrivate regular key: '+keys.regular+'\r\nPrivate memo key: '+keys.memo+'');
                }
                else{
                    console.log(err);
                }
            });
        }
        else{
            console.log("Пользователь не найден");
        }
    });
}

Объявление аккаунта делегатом

У каждого делегата есть ключ подписи блоков (signing_key), который отвечает за верификацию блокчейном подписи блока. Делегатская нода конфигурируется с приватным ключом подписи (лучше сформировать его заранее), а в блокчейн отправляется публичный ключ, для проверки подписей. В случае если делегат долго отсутствовал блокчейн отметит его как отключенного обнулив ключ подписи. Отключиться можно самостоятельно выставив пустой ключ подписи VIZ1111111111111111111111111111111114T1Anm. Пример кода для объявления аккаунта делегатом:

var account_login='test';
var active_key='5K...';
var url='https://...';//ссылка на заявление о намерении быть делегатом
var private_key=pass_gen();//генерируем приватный ключ
var signing_key=viz.auth.wifToPublic(private_key);//публичный ключ
viz.broadcast.witnessUpdate(active_key,account_login,url,signing_key,function(err,result){
    if(!err){
        console.log('Делегат '+account_login+' заявил о желании быть делегатом, приватный ключ подписи блоков: '+private_key);
    }
    else{
        console.log(err);
    }
});

Голосование за делегата

Для того чтобы делегат начал подписывать блоки, у него должен быть не нулевой вес вес голосов. Доля аккаунта при голосовании за нескольких делегатов делится между ними. Пример кода для голосования за делегата:

var account_login='test';
var active_key='5K...';
var witness_login='witness';
var value=true;//булево значение голоса (true - проголосовать за делегата, false - снять голос)
viz.broadcast.accountWitnessVote(active_key,account_login,witness_login,value,function(err,result){
    if(!err){
        console.log(result);
    }
    else{
        console.log(err);
    }
});

Передача права голосования прокси (proxy)

Если пользователь не принимает участие в выборе делегатов, он может делегировать право распоряжаться его долей другому аккаунту. Для этого существует операция account_witness_proxy, ей может воспользоваться приложение регистратор, чтобы не загружать своего пользователя лишней информацией об устройстве блокчейн-системы и не терять своё влияние (так как для регистрации аккаунта могут затрачиваться токены VIZ, то приложение вкладывает в пользователей потенциал аналогиной доли сети).

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

var account_login='test';
var active_key='5K...';
var proxy_login='proxy';
viz.broadcast.accountWitnessProxy(active_key,account_login,proxy_login,function(err,result){
    if(!err){
        console.log(result);
    }
    else{
        console.log(err);
    }
});

Установка голосуемых параметров сети

Делегаты транслируют свою позицию по голосуемым параметрам сети. Блокчейн система каждый цикл очереди делегатов (21 блок) вычисляет медианные значения голосуемых параметров и фиксирует их на этот цикл. Подробнее про голосуемые параметры сети можно прочесть в разделе Объекты и структуры в VIZ. Пример операции versioned_chain_properties_update для трансляции делегатом голосуемых параметров сети:

var account_login='test';
var active_key='5K...';//приватный активный ключ

var props={};
props.account_creation_fee='1.000 VIZ';
props.create_account_delegation_ratio=10;
props.create_account_delegation_time=2592000 ;
props.bandwidth_reserve_percent=1;
props.bandwidth_reserve_below='1.000000 SHARES';
props.committee_request_approve_min_percent=1000;
props.flag_energy_additional_cost=1000;//устаревший параметр
props.min_curation_percent=0;//устаревший параметр
props.max_curation_percent=10000;//устаревший параметр
props.min_delegation='1.000 VIZ';
props.vote_accounting_min_rshares=5000000 ;
props.maximum_block_size=65536;

props.inflation_witness_percent=2000;
props.inflation_ratio_committee_vs_reward_fund=7500;
props.inflation_recalc_period=806400;

props.data_operations_cost_additional_bandwidth=0;
props.witness_miss_penalty_percent=100;
props.witness_miss_penalty_duration=86400;


viz.broadcast.versionedChainPropertiesUpdate(active_key,account_login,[2,props],function(err,result){
    if(!err){
        console.log(result);
    }
    else{
        console.log(err);
    }
});

Заявка в комитет

var account_login='test';
var regular_key='5K...';//приватный регулярный ключ
var url='https://...';//URL с описанием заявки
var worker='test';//логин аккаунта, который в случае одобрения будет получать средства их фонда комитета
var min_amount='0.000 VIZ';//минимальное количество токенов VIZ для удовлетворения заявки
var max_amount='10000.000 VIZ';//максимальное количество токенов VIZ
var duration=5*24*3600;//длительность заявки в секундах (должно быть между COMMITTEE_MIN_DURATION (5 дней) и COMMITTEE_MAX_DURATION (30 дней))

viz.broadcast.committeeWorkerCreateRequest(regular_key,account_login,url,worker,min_amount,max_amount,duration,function(err,result){
    if(!err){
        console.log(result);
    }
    else{
        console.log(err);
    }
});

Отмена заявки в комитете

Отменить заявку может только её создатель.

var account_login='test';
var regular_key='5K...';//приватный регулярный ключ
var request_id=14;//номер отменяемой заявки комитета

viz.broadcast.committeeWorkerCancelRequest(regular_key,account_login,request_id,function(err,result){
    if(!err){
        console.log(result);
    }
    else{
        console.log(err);
    }
});

Голосование по заявке в комитете

Проголосовать за заявку может любой участник сети. При завершении время действия заявки происходит расчет удовлетворенной суммы выплаты из комитета учитывающий долю голоса от общего веса проголосовавших и выставленный ими процент согласия с условиями заявки (от -100% до +100%). Если сумма доли сети проголосовавших превышает голосуемый параметр сети committee_request_approve_min_percent и расчетная сумма входит в заложенные заявкой рамки, то заявка удовлетворяется комитетом и ставится в очередь на выплаты. Полную механику можно изучить в файле database.cpp метод committee_processing(). Пример кода с голосованием за заявку:

var account_login='test';
var regular_key='5K...';//приватный регулярный ключ
var request_id=15;//номер заявки комитета
var percent=8000;//80% процент от максимальной суммы заявки, на который считает правильным удовлетворить заявку голосующий
viz.broadcast.committeeVoteRequest(regular_key,account_login,request_id,percent,function(err,result){
    if(!err){
        console.log(result);
    }
    else{
        console.log(err);
    }
});

Платные подписки

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

var account_login='test';
var active_key='5K...';//приватный активный ключ
var url='https://...';//URL с описанием платной подписки и соглашения
var levels=3;//количество уровней платной подписки (провайдер сам решает количество уровней и что будет предоставлять за каждый), если указать 0, то новые подписки не смогут быть оформлены
var amount='100.000 VIZ';//количество токенов VIZ за каждый уровень платной подписки
var period=30;//период действия подписки (количество дней)
viz.broadcast.setPaidSubscription(active_key,account_login,url,levels,amount,period,function(err,result){
    if(!err){
        console.log(result);
    }
    else{
        console.log(err);
    }
});

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

var account_login='subscriber';
var active_key='5K...';//приватный активный ключ
var provider_account='test';//логин аккаунта провайдера платной подписки
var level=2;//желаемый уровень платной подписки
var amount='100.000 VIZ';//количество токенов VIZ за каждый уровень по соглашению
var period=30;//период действия подписки по соглашению
var auto_renewal=true;//необходимость автоматического продления
viz.broadcast.paidSubscribe(active_key,account_login,provider_account,level,amount,period,auto_renewal,function(err,result){
    if(!err){
        console.log(result);
    }
    else{
        console.log(err);
    }
});

Получить информацию об условиях соглашения по платной подписке:

var provider_account='test';
viz.api.getPaidSubscriptionOptions(provider_account,function(err,response){
    if(!err){
        console.log(response);
    }
    else{
        console.log(err);
    }
});

Получить список активных или инактивных платных подписок можно API запросом:

var account_login='subscriber';
viz.api.getActivePaidSubscriptions(account_login,function(err,response){
    for(let i in response){
        console.log('Действует соглашение по платной подписке с аккаунтом '+response[i]);
    }
}
viz.api.getInactivePaidSubscriptions(account_login,function(err,response){
    for(let i in response){
        console.log('Действует соглашение по платной подписке с аккаунтом '+response[i]);
    }
}

Проверить действующее соглашение можно через API запрос:

var account_login='subscriber';
var provider_account='test';
viz.api.getPaidSubscriptionStatus(account_login,provider_account,function(err,response){
    if(!err){
        console.log('Соглашение с аккаунтом '+response.creator);
        console.log('Статус соглашения: '+(response.active?'активное':'инактивное'));
        console.log('Автопродление: '+(response.auto_renewal?'включено':'отключено'));
        console.log('Уровень подписки: '+response.level);
        console.log('Стоимость уровеня подписки: '+(response.amount/1000)+' VIZ');
        console.log('Период подписки в днях: '+response.period);
        console.log('Дата начала соглашения: '+response.start_time);
        if(response.active){
            console.log('Дата экспирации соглашения: '+response.next_time);
        }
    }
    else{
        console.log(err);
    }
});

Делегирование доли

В блокчейне VIZ есть возможность делегировать долю (SHARES) другому аккаунту, что позволяет передать управление долей связанное с наградами, голосованием в комитете и получением пропускной способности сети (bandwidth). Делегирование не распространяется на голосование за делегатов, так как там присутствует отдельная операция в виде передачи права голоса всей своей доли операцией account_witness_proxy.

Правила делегирования доли частично прописаны в протоколе сети, частично управляются делегатами:

  • Минимальная сумма делегирования задается делегатами через голосуемый параметр min_delegation;
  • Коэффициент наценки делегирования при создании нового аккаунта задается делегатами через голосуемый параметр create_account_delegation_ratio;
  • Длительность делегирования (невозможность его отмены) при создании аккаунта задается делегатами через голосуемый параметр create_account_delegation_time;
  • Минимальная длительность делегирования заложена в протокол (константа CHAIN_CASHOUT_WINDOW_SECONDS равная одному одному дню);

При делегировании срабатывает защитный механизм от абуза двойной траты энергии. Если делегатор передает 50% от своей доли, то энергия будет уменьшена на 50%. Энергия в таком случае может стать отрицательной (до -100%). Стоит учитывать, что операция delegate_vesting_shares задает фактическое значение делегированной доли. Если вы захотите отменить делегирование, значит надо задать значение делегированной доли 0.000000 SHARES. Если вы хотите увеличить делегирование с 1000.000000 SHARES до 3000.000000 SHARES, то вам нужно просто задать значение делегирования 3000.000000 SHARES.

Пример кода для делегирования доли другому аккаунту:

var account_login='test';
var active_key='5K...';//приватный активный ключ
var delegatee='recipient';//цель делегирования
var shares='100.000000 SHARES';
viz.broadcast.delegateVestingShares(active_key,account_login,delegatee,shares,function(err,result){
    if(!err){
        console.log(result);
    }
    else{
        console.log(err);
    }
});

Получение информации о делегировании:

var account_login='test';
var start_from=0;
var count=1000;
var type=0;//0 - исходящее делегирование, 1 - входящее делегирование
viz.api.getVestingDelegations(account_login,start_from,count,type,function(err,response){
    if(!err){
        if(0==response.length){
            console.log('Нет записей о делегированной доле');
        }
        for(delegation in response){
            console.log('Делегировано аккаунту '+response[delegation].delegatee+', '+response[delegation].vesting_shares+', можно отозвать после '+response[delegation].min_delegation_time);
        }
    }
});

Получение информации о возвращении делегированной доли после отмены делегирования:

var account_login='test';
var start_from=new Date().toISOString().substr(0,19);//поиск возврата делегированной доли после даты оформленной в формате ISO
var count=1000;
viz.api.getExpiringVestingDelegations(account_login,start_from,count,function(err,response){
    if(!err){
        if(0==response.length){
            console.log('Нет записей о возвращаемой делегированной доле');
        }
        for(delegation in response){
            console.log(response[delegation].vesting_shares+' вернется '+response[delegation].expiration);
        }
    }
});

Custom операции

Когда разработчикам нужно ввести свою структуру в блокчейн, сделать децентрализированное приложение (dApp), которое будет мониторить блоки и учитывать операции в сети — они могут использовать custom операции. Custom операция имеет гибкую структуру:

  • required_active_auths — массив аккаунтов, чьи подписи активными ключами должна содержать транзакция;
  • required_regular_auths — массив аккаунтов, чьи подписи регулярными ключами должна содержать транзакция;
  • custom_name — наименование категории custom операции (разработчики сами решают какое наименование использовать для своего приложения);
  • custom_json — любая структура в JSON формате;

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

Пример использования custom операции:

var account_login='test';
var required_active_auths=[];
var required_regular_auths=[account_login];
var private_key='5K...';//приватный ключ нужного типа доступа (в данном случае regular)
var custom_name='file_app';
var custom_json='{"directory":"/photos/2020/viz_conf/","filename":"moscow_camp.jpg","url":"https://..."}';
viz.broadcast.custom(private_key,required_active_auths,required_regular_auths,custom_name,custom_json,function(err,result){
    console.log(err,result);
});

Продажа аккаунта

Владелец аккаунта может выставить его на продажу используя master доступ:

var account_login='test';
var master_key='5K...';
var seller_login='reseller';//логин аккаунта который получит токены при успешной продаже аккаунта
var fixed_price_amount='10000.000 VIZ';//цена аккаунта
var on_sale=true;//выставить аккаунт на продажу (если false, то снять с продажи)
viz.broadcast.setAccountPrice(master_key,account_login,seller_login,fixed_price_amount,on_sale,function(err,result){
    console.log(err,result);
});

Также можно выставить на продажу сабаккаунты (*.login):

var account_login='test';
var master_key='5K...';
var seller_login='test';//логин аккаунта который получит токены при успешной продаже аккаунта
var fixed_price_amount='1000.000 VIZ';//цена сабаккаунта
var on_sale=true;//выставить сабаккаунты на продажу (если false, то снять с продажи)
viz.broadcast.setSubaccountPrice(master_key,account_login,seller_login,fixed_price_amount,on_sale,function(err,result){
    console.log(err,result);
});

Купить аккаунт или сабаккаунт можно операцией buy_account, система проверит возможность покупки и проведет сделку если это возможно:

var account_login='buyer';
var active_key='5K...';
var subaccount_login='enjoy.test';//покупка сабаккаунта у аккаунта test
var account_offer_price='1000.000 VIZ';//согласованная цена предложения (если цена изменится, блокчейн откажет в операции)
var private_key=pass_gen();//генерируем приватный ключ
var public_key=viz.auth.wifToPublic(private_key);//получаем публичный ключ из приватного
var token_to_shares='5.000 VIZ';//дополнительно потратить токены для конвертации в долю нового аккаунта
viz.broadcast.buyAccount(active_key,account_login,subaccount_login,account_offer_price,public_key,token_to_shares,function(err,result){
    if(!err){
        console.log('Покупка аккаунта '+account_login+' прошла успешно, приватный общий ключ '+private_key);
        console.log(result);
    }
    else{
        console.log(err);
    }
});

Трехсторонние Escrow сделки

Трехсторонние сделки работают по принципу проверки выполнения условий гарантом (agent). Получатель и гарант должны подтвердить начало сделки операцией escrow_approve (гарант получает комиссию на этом этапе), иначе при достижении даты дедлайна ратификации (ratification_deadline) происходит возврат всех токенов инициатору сделки (метод expire_escrow_ratification в файле database.cpp).

Если наступает момент спора, то отправитель или получатель могут инициировать разбирательство операцией escrow_dispute, после чего принятие решения по сделке передается гаранту (именно он решает кто и сколько токенов получит). Если сделка повисла и долгое время не разрешается — договор истекает (escrow_expiration) и всеми токенами управляет либо агент (если был открыт диспут), либо любая из сторон сделки.

Создание escrow перевода:

var account_login='test';
var active_key='5K...';
var receiver_login='receiver';
var token_amount='100.000 VIZ';//количество передаваемых токенов
var escrow_id=1;//номер escrow назначается вручную для согласования заявки (uint32)
var agent_login='agent';
var fee='10.000 VIZ';//комиссия гаранта
var json_metadata='{}';//дополнительные мета-данные в формате json
var ratification_deadline=new Date().toISOString().substr(0,19);//дата принудительной окончания сделки и возврата средств если гарант и получатель не подтвердили участие в сделке (дедлайн в формате ISO вида 2019-10-17T13:30:18)
var escrow_expiration=new Date().toISOString().substr(0,19);//дата окончания принятия решения по сделке, после чего невозможно инициировать спор (дедлайн в формате ISO вида 2019-10-17T13:30:18)
viz.broadcast.escrowTransfer(active_key,account_login,receiver_login,token_amount,escrow_id,agent_login,fee,json_metadata,ratification_deadline,escrow_expiration,function(err,result){
    if(!err){
        console.log(result);
    }
    else{
        console.log(err);
    }
});

Подтверждение участие в сделке по предложенным условиям (свое участие должны подтвердить гарант и получатель операцией escrow_approve):

var account_login='test';
var receiver_login='receiver';
var agent_login='agent';
var escrow_id=1;//номер escrow назначается вручную для согласования заявки (uint32)

var who=agent_login;//кто подтверждает свое участие
var active_key='5K...';//ключ подтверждающий стороны (who)

var approve=true;//false в случае отказа от участия в сделке
viz.broadcast.escrowApprove(active_key,account_login,receiver_login,agent_login,who,escrow_id,approve,function(err,result){
    if(!err){
        console.log(result);
    }
    else{
        console.log(err);
    }
});

Требование диспута (открыть спор по сделке может отправитель или получатель операцией escrow_dispute):

var account_login='test';
var receiver_login='receiver';
var agent_login='agent';
var escrow_id=1;//номер escrow назначается вручную для согласования заявки (uint32)


var who=receiver_login;//кто подтверждает свое участие
var active_key='5K...';//ключ подтверждающий стороны (who)

viz.broadcast.escrowDispute(active_key,account_login,receiver_login,agent_login,who,escrow_id,function(err,result){
    if(!err){
        console.log(result);
    }
    else{
        console.log(err);
    }
});

Отпустить средства (операция escrow_release):

var account_login='test';
var receiver_login='receiver';
var agent_login='agent';
var escrow_id=1;//номер escrow назначается вручную для согласования заявки (uint32)
var token_amount='100.000 VIZ';//количество передаваемых токенов

var who=receiver_login;//кто решил отпустить средства (если открыт диспут, то только гарант решает кому и сколько перевести)
var active_key='5K...';//ключ инициатора операции (who)
var receiver=account_login;//получателем средств может быть только другая сторона сделки до истечения срока сделки, иначе — любая из сторон сделки

viz.broadcast.escrowRelease(active_key,account_login,receiver_login,agent_login,who,receiver,escrow_id,token_amount,function(err,result){
    if(!err){
        console.log(result);
    }
    else{
        console.log(err);
    }
});

Чтобы получить информацию об escrow, необходимо вызвать API запрос get_escrow плагина database_api:

var account_login='test';
var escrow_id=1;
viz.api.getEscrow(account_login,escrow_id,function(err,response){
    if(!err){
        //получен ответ
        console.log(response);
    }
    else{
        //ошибка
        console.log(err);
    }
});

Восстановление аккаунта

При создании аккаунта создатель записывается в поле аккаунта recovery как доверенное лицо для восстановления доступа к аккаунта, в случае его взлома и смены ключей доступа. Блокчейн ведет записи по смене мастер полномочий и хранит их 30 дней. Именно в этот период в 30 дней доверенный аккаунт может создать запрос на восстановление доступа операцией request_account_recovery:

var recovery_account='escrow';
var active_key='5K...';

var account_to_recover='test';
var private_key=pass_gen();//генерируем приватный ключ (передаем владельцу аккаунта или согласовываем с ним публичный ключ для мастер полномочий)
var public_key=viz.auth.wifToPublic(private_key);//получаем публичный ключ из приватного

var new_master_authority={
    'weight_threshold': 1,
    'account_auths': [],
    'key_auths': [
        [public_key, 1]
    ]
};//новые мастер полномочия
var extensions=[];//дополнительное поле, не используется
viz.broadcast.requestAccountRecovery(active_key,recovery_account,account_to_recover,new_master_authority,extensions,function(err,result) {
    console.log(err,result);
});

После того как запрос на смену доступов создан его должен подтвердить аккаунт операцией recover_account. Важный момент заключается в том, что транзакцию надо подписать сразу двумя ключами — старым и новым из заявки от доверительного аккаунта, поэтому нужно сформировать транзакцию используя viz.broadcast.send:

var account_login='test';

var new_master_key='5K...';//новый приватный мастер ключ
var new_master_public_key=viz.auth.wifToPublic(new_master_key);//публичный ключ из приватного мастер ключа (согласован с доверенным аккаунтом)
var new_master_authority={
    'weight_threshold': 1,
    'account_auths': [],
    'key_auths': [
        [new_master_public_key, 1]
    ]
};//новые мастер полномочия

var recent_master_key='5K...';//старый приватный мастер ключ для доказательства идентификации
var recent_master_public_key=viz.auth.wifToPublic(recent_master_key);//старый публичный мастер ключ
var recent_master_authority={
    'weight_threshold': 1,
    'account_auths': [],
    'key_auths': [
        [recent_master_public_key, 1]
    ]
};//старые мастер полномочия как доказательство идентификации
var extensions=[];//дополнительное поле, не используется

var operations=['recover_account',['account_to_recover':account_login,'new_master_authority':new_master_authority,'recent_master_authority':recent_master_authority,'extensions':extensions]];

var tx={'extensions':[],operations};

viz.broadcast.send(tx,[recent_master_key,new_master_key],function(err,result) {
    console.log(err,result);
});

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

var account_login='test';
var master_key='5K...';//мастер ключ
var new_recovery_account='escrow';//новый доверенный аккаунт
var extensions=[];//дополнительное поле, не используется
viz.broadcast.changeRecoveryAccount(master_key,account_login,new_recovery_account,extensions,function(err,result) {
    console.log(err,result);
});

Система предложенных операций (proposal)

Для управления системой предложенных операций существует 3 операции: создания предложения, предоставление подписи (обновление), удаление предложения. После создания предложения блокчейн система будет ожидать все необходимые подписи для осуществления операций заложенных внутрь предложения, после чего выполнит их. Если подошел срок истечения, то предложение не будет выполнено. Системой предложенных операций пользуются для управления мультиподписными аккаунтами. Для создания предложения используйте операцию proposal_create:

var account_login='test';
var active_key='5K...';//активный приватный ключ
var title='payments-14';//наименование предложения (играет роль идентификатора, должно быть уникальным)
var memo='Платежи по договору №14';

var expiration_date=new Date();//дата экспирации, когда предложенные операции будут отклонены или выполнены при получении всех необходимых подписей
expiration_date.setDate(expiration_date.getDate() + 10);//плюс десять дней от текущего момента
var expiration_time=expiration_date.toISOString().substr(0,19);//дата истечения предложения в формате ISO
console.log('expiration_time',expiration_time);

var proposed_operations=[];
proposed_operations.push({'op':['transfer',{'from':'escrow','to':'test','amount':'10.000 VIZ','memo':memo}]});
proposed_operations.push({'op':['transfer',{'from':'escrow2','to':'test','amount':'10.000 VIZ','memo':memo}]});

var review_period_date=new Date(expiration_date.getTime() - 10);//дата прекращения приема подписей (минус 10 секунд до даты экспирации)
var review_period_time=review_period_date.toISOString().substr(0,19);//дата истечения предложения в формате ISO
console.log('review_period_time',review_period_time);

var extensions=[];

viz.broadcast.proposalCreate(active_key,account_login,title,memo,expiration_time,proposed_operations,review_period_time,extensions,function(err,result){
    console.log(err,result);
});

Чтобы получить информацию о предложениях сделанных пользователем, нужно выполнить API запрос get_proposed_transactions к плагину database_api (будет возвращен массив предложений):

var looking_account='test';
var from=0;
var limit=100;
viz.api.getProposedTransactions(looking_account,from,limit,function(err,response){
    console.log(err,response);
});

Система предложений поддерживает все существующие операции в блокчейне, но не позволяет смешивать операции требующие разных полномочий (например, операции tranfer, требующие active полномочия, и операции award, требующие regular полномочия). Предоставить подпись нужного типа доступа можно операцией proposal_update указав логин подписавшего транзакцию в массиве на добавление или удаления из списка подтвердивших предложение (для этого есть 4 типа массивов: active, master, regular и key для использования одиночных ключей). Как только предоставлены все необходимые подписи — операции из предложения будут исполнены (при условии, что не указан период review_period_time), в случае ошибки выполнения повторная попытка будет предпринята при экспирации. Пример:

var account_login='escrow';
var active_key='5K...';//активный приватный ключ

var proposal_author='test';//автор предложения
var proposal_title='payments-14';//идентификатор предложения

var active_approvals_to_add=[];//список аккаунтов подписавших данную транзакцию активным типом доступа для подтверждения предложения
var active_approvals_to_remove=[];//список аккаунтов для удаления из списка подтвердивших предложение
var master_approvals_to_add=[];
var master_approvals_to_remove=[];
var regular_approvals_to_add=[];
var regular_approvals_to_remove=[];
var key_approvals_to_add=[];
var key_approvals_to_remove=[];
var extensions=[];

active_approvals_to_add.push(account_login);

viz.broadcast.proposalUpdate(active_key,proposal_author,proposal_title,active_approvals_to_add,active_approvals_to_remove,master_approvals_to_add,master_approvals_to_remove,regular_approvals_to_add,regular_approvals_to_remove,key_approvals_to_add,key_approvals_to_remove,extensions,function(err,result){
    console.log(err,result);
});

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

var account_login='escrow2';//участник предложения
var active_key='5K...';//активный приватный ключ

var proposal_author='test';//автор предложения
var proposal_title='payments-14';//идентификатор предложения

var extensions=[];

viz.broadcast.proposalDelete(active_key,proposal_author,proposal_title,account_login,extensions,function(err,result){
        console.log(err,result);
});

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

var expiration_date=new Date(new Date().getTime() + 20000);//+20 секунд от текущего момента
var expiration_time=expiration_date.toISOString().substr(0,19);//дата истечения предложения в формате ISO
var review_period_date=new Date(expiration_date.getTime() - 10000);//дата прекращения приема подписей (минус 10 секунд от даты экспирации)
var review_period_time=review_period_date.toISOString().substr(0,19);//дата истечения предложения в формате ISO

var login='test';
var active_wif='5K...';
var regular_wif='5J...';

var target='committee';//цель операции награждения
var energy=200;//2%
var memo='отложенного награждения через proposal';

var regular_public_wif = viz.auth.wifToPublic(regular_wif);//для добавление в key_approvals_to_add через операцию proposal_update

const operations = [
["proposal_create",{
    "author": login,
    "title": 'proposal-award',
    "memo": login + '-award',
    "expiration_time": expiration_time,
    "proposed_operations": [
    {"op":['award', {'initiator':login,'receiver':target,'energy':200,'custom_sequence':0,'memo':memo}]}],
    "review_period_time": review_period_time,
    "extensions": []
}],
["proposal_update",{
    "author": login,
    "title": 'proposal-award',
    "active_approvals_to_add": [],
    "active_approvals_to_remove": [],
    "owner_approvals_to_add": [],
    "owner_approvals_to_remove": [],
    "posting_approvals_to_add": [],
    "posting_approvals_to_remove": [],
    "key_approvals_to_add": [regular_public_wif],
    "key_approvals_to_remove": [],
    "extensions": []
}]
];

viz.broadcast.send({extensions:[],operations},[active_wif,regular_wif],function(err,result){
    console.log(err,result)
});

Подпись данных с помощью приватного ключа и проверка подписи с помощью публичного

Чтобы подписать данные приватным ключом (метод viz.auth.signature.sign, для поиска каноничной подписи необходимо добавить nonce, который будет находиться в данных на подпись) и проверить подпись публичным ключом (метод viz.auth.signature.verifyData), можно воспользоваться стандартными методами viz-js-lib:

var private_key='5KRLZitDd5c9uZzDgTMF4se4eVewENtZ29GbCuKwbT3msRbtLgi';
var public_key='VIZ6LWhhUzKmYM5VcxtC2FpEjzr71imfb7DeGA9yodeqnvtP2SYjA';

var invalid_public_key='VIZ65kiW3JsxsF7NCabAuSJUk8Efhx5PW6cbgSS5uuZpbkSTpSjn6';

var data={data:"data signature check!",nonce:0};
var nonce=0;
var data_with_nonce='';
var signature='';

function auth_signature_check(hex){//проверка на каноничность подписи
    if('1f'==hex.substring(0,2)){
        return true;
    }
    return false;
}

while(!auth_signature_check(signature)){
    data.nonce=nonce;
    data_with_nonce=JSON.stringify(data);
    signature=viz.auth.signature.sign(data_with_nonce,private_key).toHex();
    nonce++;
}
console.log('data with nonce',data_with_nonce);
console.log('signature',signature);

console.log('check by public_key',viz.auth.signature.verifyData(data_with_nonce,viz.auth.signature.fromHex(signature),public_key));
console.log('check by invalid_public_key',viz.auth.signature.verifyData(data_with_nonce,viz.auth.signature.fromHex(signature),invalid_public_key));

js запросы к публичной ноде VIZ без библиотеки

Если вашему приложению не требуется криптография и подпись транзакций, то вы можете использовать нативные средства для json-rpc запросов через js.

Пример для WebSocket соединения:

var api_gate='wss://solox.world/ws';
var latency_start=new Date().getTime();
var latency=-1;
var socket = new WebSocket(api_gate);
socket.onmessage=function(event){
    latency=new Date().getTime() - latency_start;
    let json=JSON.parse(event.data);
    if(json.result){
        console.log(json.result);
    }
    else{
        console.log(json.error);
    }
    socket.close();
}
socket.onopen=function(){
    socket.send('{"id":1,"method":"call","jsonrpc":"2.0","params":["database_api","get_dynamic_global_properties",[]]}');
};

Пример для HTTP соединения:

var api_gate='https://viz.lexa.host/';
var latency_start=new Date().getTime();
var latency=-1;
var xhr = new XMLHttpRequest();
xhr.overrideMimeType('text/plain');
xhr.open('POST',api_gate);
xhr.setRequestHeader('accept','application/json, text/plain, */*');
xhr.setRequestHeader('content-type','application/json');
xhr.onreadystatechange=function(){
    if(4==xhr.readyState && 200==xhr.status){
        latency=new Date().getTime() - latency_start;
        console.log(xhr);
        let json=JSON.parse(xhr.response);
        if(json.result){
            console.log(json.result);
        }
        else{
            console.log(json.error);
        }
    }
}
xhr.send('{"id":1,"method":"call","jsonrpc":"2.0","params":["database_api","get_dynamic_global_properties",[]]}');

Формирование транзакций

Есть правила формирования транзакций, заложенные в протокол VIZ (использующий каркас Graphene). Данные, подписываемые криптографическим ключом, должны соответствовать всем структурам и правилам бинарного представления, заложенным в код. В данном разделе вы узнаете как кодируются ключи, как происходит бинарное представление данных или их подготовка.


Структура транзакции для подписи

Структура данных, бинарное представление которой и нужно подписывать используя ключи соответствующих типов доступа используемых в аккаунтах вложенных операций, содержит следующие поля:

  • chain_id — идентификатор цепи, в VIZ это fc::sha256::hash от строки VIZ: 2040effda178d4fffff5eab7a915d4019879f5205cc5392e4bcced2b6edda0cd. Стоит отметить, что fc::sha256::hash преобразует строку в c_str, добавляя в начало ее hex значения 56495A длину строки, в итоге sha256 рассчитывается от hex значения 0356495A;
  • tapos_linkTransactions as Proof of Stake концепция заключается в том, чтобы каждая транзакция ссылалась на конкретный блок, который должен быть в цепи для ее работы. Бинарное представление является отображением параметров транзакции ref_block_num и ref_block_prefix.
  • expiration — unixtime экспирации транзакции (она должна быть включена в цепь до времени экспирации);
  • operations — массив операций находящихся в транзакции, бинарное представление каждой операции состоит из всех аттрибутов операции согласно протоколу;
  • extensions — массив служебных расширений транзакции (не используется, поэтому в бинарном формате представляет собой hex значение массива без элементов: 00);

Зачем необходим chain_id

Открытый и свободный код блокчейн систем основанных на Graphene позволяют запускать новые цепочки, как без изменений, так и полностью переработанные с собственными механиками и экономикой. Более того, множество проектов запускают публичные тестовые цепочки для проверки изменений. Чтобы ноды не путались и транзакции из одной сети нельзя было применять в форке (или цепи с аналогичными аккаунтами и ключами) — существует идентификатор цепи, который присутствует как метка в каждой транзакции и операции подписи.

Формат ключей

Приватные и публичные ключи в VIZ находятся по алгоритму ESDCA (теория), и используют криптографию для проверки подписей набора данных. Многие разработчики не являясь специалистами в криптографии просто используют специализированные библиотеки, не вдаваясь в подробности.

Рассмотрим этапность преобразования приватного ключа (состоящего из 32 байт) в читаемый WIF формат:

  • Ключу добавляем бинарный префикс в hex представлении 80;
  • Вычисляем sha256 хэш от sha256 хэша бинарного представления ключа для контрольной суммы (checksum);
  • Добавляем в конец ключа первые 8 байт от контрольной суммы;
  • Кодируем полученный бинарный результат через base58 алгоритм с использованием алфавита: 123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz;

Этапность преобразование публичного ключа (состоящего из 32 байт) в читаемый формат:

  • Получаем контрольную сумму хэшированием бинарного представления ключа алгоритмом ripemd160;
  • Добавляем в конец ключа первые 8 байт от контрольной суммы;
  • Кодируем полученный бинарный результат через base58 алгоритм с использованием алфавита: 123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz;
  • Добавляем префикс сети (строковое значение VIZ);

В библиотеке viz-js-lib используется модуль auth (ссылка на GitHub), который позволяет предустановленными методами работать с ключами и подписывать данные.

Представление разных типов данных в бинарном виде

  • string — строковое значение в бинарном виде представляет собой байт содержащий длину строки и саму строку (например, строковое значение логина аккаунта escrow будет соответствовать hex значению 06657363726f77 в бинарном представлении);
  • integer — числовое значение переворачивается в бинарном представлении и пустая размерность заполняется нулями (если задан тип значения). Например, энергия аккаунта указываемая в операции award представляет собой uint16_t, для передачи которого достаточно 2 байта. Если необходимо передать значением 10.00%, то в целочисленном значении это будет 1000, в hex представлении 03EB, перевернутое значение которого будет EB03. Поле custom_sequence представляющее собой uint64_t уже состоит из 8 байтов, поэтому для передачи десятичного значения 377, его hex значение 0179 в бинарном значении будет представлять собой hex: 7901000000000000.
  • date — поля дат из JSON представления записаны в ISO формате (например, 2019-02-07T06:19:23 в часовом поясе UTC+0, он же GMT). Для их бинарного значения записывается в десятичном формате unixtime по правилам представления integer. Пример 2019-02-07T06:19:23 в unixtime будет 1549520363 (hex значение 5C5BCDEB), который в бинарном значении будет представлять собой hex: EBCD5B5C.
  • asset — бинарное значение токенов VIZ или SHARES представляет собой последовательность значений: целочисленный integer размерностью 8 байт без точности (0.012 будет представлять собой 12, 1.002 будет представлять собой 1002), 1 байт с точностью (precision) токена (03 для VIZ, 06 для SHARES), строковое значение тикера в 7 байт (56495A00000000 для VIZ, 53484152455300 для SHARES). Например: 1.002 VIZ в JSON значении внутри операции будет иметь бинарное представление в hex: EA030000000000000356495A.
  • public_key — значение публичного ключа в бинарном представлении содержит 33 байта, первый байт — значение для восстанавления публичного ключа (recovery id в ECDSA), 16 байт — координаты точки публичного ключа по оси X, последние 16 байт по оси Y. Например, бинарное значение 026a1dbaacb805f145f9276025627102152840bb1aa09b7fac580f892d93b572b4 соответствует приватному ключу с recovery_id равным 02 и точке с координатами X в hex представлении 6a1dbaacb805f145f927602562710215 и Y в hex представлении 2840bb1aa09b7fac580f892d93b572b4. Что соответствует публичному ключу VIZ5hDwvV1PPUTmehSmZecaxo1ameBpCMNVmYHKK2bL1ppLGRvh85.
  • operation_type — тип операции представляет собой целочисленное значение номера операции по протоколу VIZ записанное в 1 байт (продробнее читайте в разделе Операции и их типы). Например, операция transfer в бинарном виде будет иметь запись в hex 02, а операция create_invite hex значение 2b.

Пример структуры транзакции

Разберем пример транзакции в формате JSON и её представление в бинарном виде:

{"ref_block_num":9023,"ref_block_prefix":1971875185,"expiration":"2019-02-07T06:19:23","operations":[["transfer",{"from":"test1","to":"test2","amount":"1.002 VIZ","memo":"<3"}]],"extensions":[]}
  • ref_block_num — ссылка на номер блока после побитового «и» с hex ffff (например, число 9023 в hex представлении 233F, согласно представлению integer должно быть перевернуто, получаем 3F23);
  • ref_block_prefix — 4 байта (5, 6, 7, 8) от бинарного состояния идентификатора блока в десятичном формате, которое можно получить API запросом get_block_header с номером следующего блока (9024) к плагину database_api. Ответ будет содержать поле previous с идентификатором 0000233F716D887523BB63AD3E6107C96EDCFD8A искомого блока. Берем 716D8875 для бинарного представления, переворачиваем байты — 75886D71 и переводим в десятичный формат для JSON: 1971875185.
  • expiration — unixtime экспирации транзакции. 2019-02-07T06:19:23 в unixtime будет 1549520363 (hex значение 5C5BCDEB), который в бинарном значении будет перевернут и представлять собой hex: EBCD5B5C.
  • operations — массив операций (так как в массиве один элемент, hex: 01);
    • transfer — операция перевода токенов (по нумерации операции в протоколе hex: 02);
      • from — логин аккаунта отправителя (длина строки test1 и hex представление: 057465737431);
      • to — логин аккаунта получателя (длина строки test2 и hex представление: 057465737432);
      • amount — передаваемое количество токенов VIZ (1.002 VIZ в hex: EA030000000000000356495A00000000);
      • memo — заметка для получателя (длина строки <3 и hex представление: 023C33);
  • extensions — массив служебных расширений транзакции (так как не используется и не имеет элементов, то имеет представление 00).

Итоговое бинарное представление данных транзакции в hex: 3F23716D8875EBCD5B5C0102057465737431057465737432EA030000000000000356495A00000000023C3300;

Чтобы отправить транзакцию в блокчейн, необходимо дополнить данное представление chain_id в начале и подписать приватным ключом. Полученную подпись необходимо добавить в JSON поле массив signatures, например:

{"ref_block_num":9023,"ref_block_prefix":1971875185,"expiration":"2019-02-07T06:19:23","operations":[["transfer",{"from":"test1","to":"test2","amount":"1.002 VIZ","memo":"<3"}]],"extensions":[],"signatures":["1f500f2a5d721e45c53e76fca786d690c7c0556f1923aa07c944e26614b50481d353e88f82e731be74c18e3fb8d117dc992a475991974b6e1364a66f5ccb618f83"]}

И передать этот JSON через API запрос broadcast_transaction плагину network_broadcast_api.

Идентификатор транзакции

В исходном коде ноды указан тип подписанной транзакции как transaction_id_type, который в протоколе Graphene записан как fc::ripemd160. Но практика показывает, что идентификатор транзакции не является ripemd160 (размер хэша 20 байт), а является частью sha256 хэша (размер хэша 32 байта). Доподлинно неизвестно, почему так произошло, но можно сделать 2 предположения:

  • Это сделано намеренно, чтобы сократить размерность хэша транзакции (20 байт заместо 32), но увеличивает риск коллизии;
  • Это непреднамеренный баг, который не нашли до запуска Graphene цепочек, а позже решили не исправлять (предположительно, баг происходит в момент преобразования транзакции через digest_type::encoder в файле протокола transaction.cpp);

Стоит отметить, что данную ошибку исправили в протоколе EOS, там тип транзакции fc::sha256, что наводит на мысли, что это все таки баг. Как итог идентификатором транзакции в Graphene проектах до EOS является хэш sha256, но обрезанный — 20 первых байт (вместо полных 32 байт).

Например, транзакция в VIZ из блока 11142739 имеет идентификатор c84f9e8255859b2083be720cf9b64b3542e4360f. Сырая транзакция {"ref_block_num":1612,"ref_block_prefix":2641357798,"expiration":"2019-10-22T05:59:27","operations":[["award",{"initiator":"on1x","receiver":"viz-social-bot","energy":20,"custom_sequence":0,"memo":"telegram:262632819","beneficiaries":[]}]],"extensions":[]} в hex: 4c06e6eb6f9dbf9aae5d012f046f6e31780e76697a2d736f6369616c2d626f74140000000000000000001274656c656772616d3a3236323633323831390000.

Хэш sha256 от сырой транзакции: c84f9e8255859b2083be720cf9b64b3542e4360f0a62e33363bca5d984ee608a, первые 20 байт которого и являются её идентификатором в блокчейне: c84f9e8255859b2083be720cf9b64b3542e4360f.

Получение ref_block_num и ref_block_prefix для формирования транзакции

Нода блокчейна хранит идентификаторы последних 65537 блоков (подробнее читайте про концепцию TaPoS в VIZ). Обычно разработчики ссылаются на один из последних блоков, обычно, выполняя очередь действий:

  • Получают данные о состоянии системы через API запрос get_dynamic_global_properties к плагину database_api;
  • Используя значение поля head_block_number минус 3 блока устанавливают для какого блока будут формировать ref_block_num и запрашивать его идентификатор;
  • Получают идентификатор используемого блока, выполняя API запрос get_block_header к плагину database_api, запрашивая искомый блок плюс один блок (так как заголовок каждого блока содержит ссылку на идентификатор прошлого блока, искомый идентификатор находится в следующем);
  • Из идентификатора формируют ref_block_prefix.

Большинство библиотек которые содержат абстракции для упрощения вызовов и трансляции транзакции в блокчейн делают это самостоятельно.

Пример ручного получения ref_block_num и ref_block_prefix на viz-js-lib можете посмотреть в исходном коде абстракции подготовки транзакции в самой библиотеке.

Пример аналогичного получения ref_block_num и ref_block_prefix на PHP в исходном коде библиотеки php-graphene-node-client.

Порядок сериализации данных в операции

Все операции и их параметры записаны в протоколе VIZ и находятся в файле chain_operations.hpp.

Именно там можно изучить типы параметров и их требуемый порядок в операции. Внимание! Порядок параматров в структуре операции не совпадает с порядком параметров в самой операции. Рассмотрим пример на операции escrow_transfer_operation, структура операции (часто перед операцией присутствует комментарий её описывающий, ):

/**
 *  The purpose of this operation is to enable someone to send money contingently to
 *  another individual. The funds leave the *from* account and go into a temporary balance
 *  where they are held until *from* releases it to *to* or *to* refunds it to *from*.
 *
 *  In the event of a dispute the *agent* can divide the funds between the to/from account.
 *  Disputes can be raised any time before or on the dispute deadline time, after the escrow
 *  has been approved by all parties.
 *
 *  This operation only creates a proposed escrow transfer. Both the *agent* and *to* must
 *  agree to the terms of the arrangement by approving the escrow.
 *
 *  The escrow agent is paid the fee on approval of all parties. It is up to the escrow agent
 *  to determine the fee.
 *
 *  Escrow transactions are uniquely identified by 'from' and 'escrow_id', the 'escrow_id' is defined
 *  by the sender.
 */
struct escrow_transfer_operation : public base_operation {
    account_name_type from;
    account_name_type to;
    account_name_type agent;
    uint32_t escrow_id = 30;

    asset token_amount = asset(0, TOKEN_SYMBOL);
    asset fee;

    time_point_sec ratification_deadline;
    time_point_sec escrow_expiration;

    string json_metadata;

    void validate() const;

    void get_required_active_authorities(flat_set<account_name_type> &a) const {
        a.insert(from);
    }
};

Порядок параметров в операции задается уже в конце файла методом:

FC_REFLECT((graphene::protocol::escrow_transfer_operation), (from)(to)(token_amount)(escrow_id)(agent)(fee)(json_metadata)(ratification_deadline)(escrow_expiration));

Кроме описания структуры операции есть еще обработка параметров в методе validate, найти который можно в файле chain_operations.cpp:

void escrow_transfer_operation::validate() const {
    validate_account_name(from);
    validate_account_name(to);
    validate_account_name(agent);
    FC_ASSERT(fee.amount >= 0, "fee cannot be negative");
    FC_ASSERT(token_amount.amount >=
              0, "tokens amount cannot be negative");
    FC_ASSERT(from != agent &&
              to != agent, "agent must be a third party");
    FC_ASSERT(fee.symbol == TOKEN_SYMBOL, "fee must be TOKEN_SYMBOL");
    FC_ASSERT(token_amount.symbol ==
              TOKEN_SYMBOL, "amount must be TOKEN_SYMBOL");
    FC_ASSERT(ratification_deadline <
              escrow_expiration, "ratification deadline must be before escrow expiration");
    validate_json_metadata(json_metadata);
}

Большинство операций проверяют наличие подписи соответствующих полномочий, например в структуре escrow_transfer_operation присутствует проверка подписи инициатора операции (поле from) активных полномочий в методе get_required_active_authorities.