Skip to content

crowdwiz-biz/docs

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 

Repository files navigation

Методические указания по разработке приложений на платформе CWD.GLOBAL

Введение

Аудитория

Данное пособие предназначено в первую очередь для разработчиков имеющих опыт разработки и руководителей проектов с использованием таких языков программирования как C++, Python, Javascript (React), однако приницпы разработки изложенные в данном пособии применимы и для других сред разработки.

Что такое блокчейн

Блокчейн - это такая технология, которая позволяет передавать через интернет не только информацию, но и ценности. Информация в блокчейне открыта для каждого. Также архитектура изначально предполагает многократную проверку достоверности и дублирование информации. При этом то что записано в блокчейн нельзя удалить или изменить. Совокупность этих свойств: децентрализация, передача ценностей, прозрачность и невозможность вносить изменения - делает блокчейн средой, в которой не нужно доверие и отсутствует человеческий фактор. Именно поэтому блокчейн является идеальной основой для создания высокотехнологичных продуктов нацеленных на отказоустойчивость, безопасность и автоматизацию. Для всех блокчейн-проектов, не важно какую технологию конценсуса они используют, характерно отсутвие единой точки отказа и децентрализация.

Платформа CrowdWiz

CrowdWiz это платформа, которую создала группа крипто-энтузиастов - разрабочиков, экономистов, инвесторов и медийных личностей, которые придумали экосистему в которой бы всем было комфортно работать и разиваться, и смогли реализовать её с помощью технологии блокчейн. Основные принципы сообщества следующие:

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

Первый блок был сгенерирован 22 марта 2019 г, и на момент написания данной методички (сентябрь 2020) сообщество превысило 26 000 пользователей, в блокчейне проведено более 5 000 000 транзакций, на бирже размещено более 40 000 ордеров и через децентрализованный обменник проведено более 25 000 сделок.

Основные принципы разработки

Архитектура блокчейн-платформы CrowdWiz

Платформа CrowdWiz (GitHub) это блокчейн-проект с открытым исходным кодом, который основывается на высокоскоростной DPOS технологии Graphene/Bitshare, базовый функционал: криптография, алгоритмы конценсуса и голосования, реализация API и даже базовые операции остались такими же как в оригинальном Bitshares. Это даёт возможность быстро адаптировать приложение написанные для BitShares, использовать документацию и пользоваться помощью большого международного сообщества при разработке собственных сервисов для CrowdWiz.

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

Ссылки на основные источники докуменации Bitshares:

На платформе CrowdWiz можно использовать почти весь функционал Bitshares и кроме него ещё порядка 50 новых операций.

Полностью новый функционал ядра разработанный это:

  • Децентрализованная игровая зона
  • Децентрализованный обменник
  • Децентрализованная система кредитов и залогов
  • Отправка сообщений через блокчейн
  • Система выплаты реферальных вознаграждений

Ядро блокчейна написано на C++ и использованием библиотек Boost. Сервер на котором выполняется ядро блокчейна назвается по-разному, чаще всего Witness, но также нода, узел блокчейна, заверитель, node.

Все узлы образуют полносвязную одноранговую сеть. По своей архитектуре ядро блокчейна это noSQL база данных и интерфейс коммуникации. То есть ядро получает от клиента транзакцию в виде JSON объекта, проверяет её, и если она корректная - отправляет её на те узлы с которыми установлена связь, те узлы которые получили эту транзакцию также проверяют её и отправляют на узлы с которыми они связаны.

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

  • Seed-node - узел, который сообщает другим узлам о других узлах. Все витнесы по умолчанию seed-ноды. Каждый витнес связан с другим витнесом в сети, и они передают друг другу информацию о блоках и транзакциях. Как только новый витнес подключается к одному из действующих узлов, то тот ему сообщаем информацию о других активных узлах в сети, и витнес соединяется и с ними.
  • API-node - витнес может быть сервером API и выдавать информацию клиентам. Он хранит в себе историю об операциях по разным клиентам, и также подготавливает данные для клиентов. Кроме того, через узлы API клиенты передают свои транзакции в сеть crowdwiz. API-сервера довольно гибко настраиваются под разные нужды и могут быть основой для написания различных роботов или сервисов. Они бывают публичные и частные.
  • Block-production node - витнес, который производит блоки.

С заверителями клиенты общаются через web-сокеты, сайт cwd.global это не просто сайт, это web-приложение написанное на React. Когда вы заходите на сайт, оно полностью сохраняется в вашем браузере и после этого подключется к выбраному вами узлу блокчена. Это приложение и есть ваш криптокошелёк (Wallet). То есть у данного сайта нет привычного бэкенда, он взаимодействует напрямую с блокчейном из вашего браузера посредством web-сокетов.

Кроме web-приложения есть также консольный кошелёк для linux/Mac, и Python библиотека которая может работать как кошелёк и позволяет создавать различные автоматизированные сервисы которые могут осуществлять любые операции в блокчейне crowdwiz. Установить её можно простой командой (pip3 install crowdwiz).

API блокчейн-платформы CrowdWiz

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

curl --data '{"jsonrpc": "2.0", "method": "get_full_accounts" "params": [["crowdhack"], false], "id": 1}' https://cwd.global/ws

в ответ вы получите json-объект который содержит в себе всю информацию об объекте аккаунта, а также всю связанную с ним информацию:

{
   "id":1,
   "jsonrpc":"2.0",
   "result":[
      [
         "crowdhack",
         {
            "account":{
               "id":"1.2.15157",
               "membership_expiration_date":"1970-01-01T00:00:00",
               "registrar":"1.2.28",
               "referrer":"1.2.27",
               "lifetime_referrer":"1.2.0",
               "network_fee_percentage":2000,
               "lifetime_referrer_fee_percentage":3000,
               "referrer_rewards_percentage":0,
               "name":"crowdhack",
               "owner":{
                  "weight_threshold":1,
                  "account_auths":[],
                  "key_auths":[
                     [
                        "CWD7cbxquaRiyuoh8ii3LsoS91PzduGjnQLY9WsxLepYVbUWrZUCB",
                        1
                     ]
                  ],
                  "address_auths":[]
               },
               "active":{
                  "weight_threshold":1,
                  "account_auths":[],
                  "key_auths":[
                     [
                        "CWD7cbxquaRiyuoh8ii3LsoS91PzduGjnQLY9WsxLepYVbUWrZUCB",
                        1
                     ]
                  ],
                  "address_auths":[]
               },
               "options":{
                  "memo_key":"CWD7cbxquaRiyuoh8ii3LsoS91PzduGjnQLY9WsxLepYVbUWrZUCB",
                  "voting_account":"1.2.5",
                  "num_witness":0,
                  "num_committee":0,
                  "votes":[],
                  "extensions":[]
               },
               "statistics":"2.6.15157",
               "whitelisting_accounts":[],
               "blacklisting_accounts":[],
               "whitelisted_accounts":[],
               "blacklisted_accounts":[],
               "owner_special_authority":[
                  0,
                  {}
               ],
               "active_special_authority":[
                  0,
                  {}
               ],
               "top_n_control_flags":0,
               "referral_levels":8,
               "referral_status_type":4,
               "referral_status_paid_fee":500000000,
               "referral_status_expiration_date":"2021-08-13T08:41:30",
               "status_denominator":10000
            },
            "statistics":{
               "id":"2.6.15157",
               "owner":"1.2.15157",
               "name":"crowdhack",
               "most_recent_op":"2.9.9251805",
               "total_ops":4,
               "removed_ops":0,
               "total_core_in_orders":0,
               "core_in_balance":"7100000000",
               "has_cashback_vb":false,
               "is_voting":false,
               "last_vote_time":"1970-01-01T00:00:00",
               "lifetime_fees_paid":500000000,
               "pending_fees":0,
               "pending_vested_fees":0,
               "personal_volume_in_period":0,
               "network_volume_in_period":0,
               "last_pv_update_date":"1970-01-01T00:00:00",
               "last_nv_update_date":"1970-01-01T00:00:00",
               "matrix":"1.18.0",
               "matrix_active_levels":1,
               "matrix_cells_opened":0,
               "matrix_rooms_opened":0,
               "p2p_complete_deals":0,
               "p2p_canceled_deals":0,
               "p2p_arbitrage_loose":0,
               "p2p_deals_volume":0,
               "p2p_banned":"1970-01-01T00:00:00",
               "p2p_gateway_active":true,
               "first_month_income":0,
               "second_month_income":0,
               "third_month_income":0,
               "current_month_income":0,
               "total_credit":0,
               "allowed_to_repay":0,
               "credit_repaid":0,
               "creditor":"1.2.0"
            },
            "registrar_name":"master",
            "referrer_name":"root",
            "lifetime_referrer_name":"committee-account",
            "votes":[],
            "balances":[
               {
                  "id":"2.5.9094",
                  "owner":"1.2.15157",
                  "asset_type":"1.3.0",
                  "balance":"7100000000",
                  "maintenance_flag":false
               }
            ],
            "vesting_balances":[],
            "limit_orders":[],
            "call_orders":[],
            "settle_orders":[],
            "proposals":[],
            "assets":[],
            "withdraws":[]
         }
      ]
   ]
}

Полный список методов API содержится в исходных кодах ядра, в файле database_api.cpp

Отправлять данные в блокчейн вы также можете через API, но все отправляемые операции должны быть криптографически подписаны вашим ключом, а также на балансе должно быть достаточно валюты чтобы заплатить комиссию за выполнение операции. Рекомендуем использовать для этого уже готовые библиотеки для Python, Javascript (Node.js) и C++.

Таким образом разработка может быть двух видов:

  • Разработка off-chain приложений которые используют блокчейн в качестве системы хранения или получения данных, или выполняют в автоматическом режиме те операции которые уже есть в ядре блокчейна.
  • Разработка новых операций (а также объектов и методов API) на базе ядра блокчейна. Такой функционал может стать частью платформы только после того, как большинство узлов заверителей включат данный код в своё ядро и произведут так называемый hardfork.

В рамках данного руководства мы рассмотрим несколько примеров off-chain приложений написанных на языке Python, а затем рассмотрим цикл разработки функционала ядра.

Управление криптоключами в блокчейне CrowdWiz

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

Что такое шифрование с открытым и закрытым ключом

Суть шифрования с открытыми и закрытыми ключами (часто также можно услышать Публичным и Приватным или Public key и Private key) заключается в том, что с помощью специальных математических алгоритмов получают пару ключей, которые представляют собой последовательность цифр, например:

Приватный ключ: 5KM2Rg5Dej9fNbZdkf5KmS1N3guk5R7GEX9JgPjUmymxxMKKyUr
Публичный ключ: CWD7wVJy7g82d7ffqGA2dm52jgCxEjuxiyKDEMsP6kjXDFVpwzVaZ

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

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

Что такое пароль от аккаунта платформы cwd.global

Проще всего зарегистрировать акканут пройдя регистрацию на основном сайте платформы CrowdWiz. Для начала давайте поймём, как тот пароль, который вы вводите при регистрации связан с открытыми и закрытыми ключами, о которых было написано выше. У каждого аккаунта на платформе CrowdWiz в базовом случае есть минимум 3 пары ключей. Вы можете видеть их, если зайдёте на вкладку Права доступа.

В каждом аккаунте присутствуют три пары ключей:

  • Права доступа (Active key)
  • Права владельца (Owner key)
  • Ключ примечания (Memo key)

Так откуда же берутся эти ключи, если при авторизации вы вводите всего лишь один пароль от своего аккаунта?

В базовом случае все эти три пары ключей генерируются в вашем браузере на основании имени вашего аккаунта, введённого пароля и дополнительного признака ключа (Active, Owner, Memo). То есть несмотря на то что вы вводите один пароль - на его основе создаются 3 пары ключей.

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

Именно приватные ключи (в формате WIF), а не пароль от аккаунта нам понадобятся для разработки off-chain приложений

Зачем нужны три пары ключей? (Active, Owner, Memo)

  • Active key (в русской версии Ключ прав доступа) нужен для того, чтобы подписывать все операции, которые не связаны с изменением аккаунта. Например, вы доверяете кому-то совершать транзакции со своего аккаунта, но хотите в любой момент иметь возможность прекратить это, а также быть уверенным, что вы всегда сможете получить доступ к своему аккаунту. В таком случае вы сообщаете тому, кому доверяете, свой приватный Active ключ. Теперь тот, кому вы передали ключ, сможет совершать операции от имени вашего аккаунта. Таких ключей в аккаунте может хранится несколько. Более того существует также система весов. Но всё это нужно для мультиподписей, и это тема отдельной статьи.

  • Owner key (Ключ владельца, самый главный ключ) - нужен, чтобы вносить изменения в аккаунт, как раз для того, чтобы изменять ключи, которые привязаны к аккаунту (Active, Owner, Memo). Зная ключ владельца вы владеете аккаунтом! Также как и в случае с активным ключом ключей владельца может быть несколько.

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

Краткое резюме

При регистрации на платформе на основе вашего имени пользователя и пароля создаются три пары ключей, публичные части которых записываются в ваш аккаунт на блокчейне. Эти ключи генерирует ваш браузер каждый раз при авторизации, и они создаются только в нём. По сути когда вы вводите пароль на платформе, никакой авторизации в системе не происходит, просто ваш браузер генерирует пары ключей и проверяет, что хотя бы один публичный ключ из трёх созданных пар записан в вашем аккаунте в блокчейне. Закрытые ключи никогда и никуда не передаются. Именно поэтому нельзя восстановить пароль, если вы его забыли. Потому что пароли не хранятся нигде в системе, хранятся только публичные ключи.

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

Базовые экономические принципы работы блокчейна Crowdwiz

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

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

Базовая криптовалюта блокчейна CrowdWiz называется CROWD и имеет тикер CWD. Для того чтобы совершать транзакции вам нужно иметь на балансе аккаунта некоторое количество CWD. CWD можно приобрести через децентрализованный обменник CWDEx или обенять через автоматический шлюз BTC.

Приобрести CWD можно сделать после авторизации на платформе на вкладке Финансы -> Пополнить/Снять.

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

Чтение данных из блокчейна конечно бесплатное и доступно каждому.

Обекты и операции блокчейна CrowdWiz

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

Объекты

У каждого объекта в блокчейне есть свой уникальный ID. Который состоит из 3 значаний X.Y.Z

  • X - Признак объекта может быть равен 1 или 2, 1 - это объекты которые являются частью протокола, 2 - это динамические объекты, которые меняются со временем.
  • Y - Типа объекта
  • Z - Порядковый номер

Жирным шрифтосм выделены уникальные объекты CrowdWiz а также те, которые отличаются от базовых, остальные объекты общие для ядра Graphene/Bitshares

ID Типа объекта
1.1.x base object
1.2.x account object
1.3.x asset object
1.4.x force settlement object
1.5.x committee member object
1.6.x witness object
1.7.x limit order object
1.8.x call order object
1.9.x custom object
1.10.x proposal object
1.11.x operation history object
1.12.x withdraw permission object
1.13.x vesting balance object
1.14.x worker object
1.15.x balance object
1.16.x flipcoin_object
1.17.x lottery_goods_object
1.18.x matrix_object
1.19.x matrix_rooms_object
1.20.x p2p_adv_object
1.21.x p2p_order_object
1.22.x credit_offer_object
1.23.x pledge_offer_object
2.0.x global_property_object
2.1.x dynamic_global_property_object
2.3.x asset_dynamic_data
2.4.x asset_bitasset_data
2.5.x account_balance_object
2.6.x account_statistics_object
2.7.x transaction_history_object
2.8.x block_summary_object
2.9.x account_transaction_history_object
2.10.x blinded_balance_object
2.11.x chain_property_object
2.12.x witness_schedule_object
2.13.x budget_record_object
2.14.x special_authority_object
2.15.x buyback_object
2.16.x fba_accumulator_object
2.17.x collateral_bid_object

Все объекты собраны в файле https://github.com/bitshares/bitshares-core/blob/master/libraries/chain/include/graphene/chain/protocol/types.hpp кода ядра.

Операции

В блокчейне CrowdWiz уже разработано 92 операции, в оригинальном Graphee/Bithsares их было 49. Полный список всех операций можно найти в файле: https://github.com/bitshares/bitshares-core/blob/master/libraries/protocol/include/graphene/protocol/operations.hpp кода ядра.

Настройка собственного узла заверителя

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

Системные требования

  • Базовой операционной системой для ядра блокчейна является Ubuntu 18.04 LTS
  • Для сборки необходимо 16 GB оперативной памяти, либо 8 RAM + 8 SWAP (для работы узла количество оперативной памяти зависит от того, как глубоко хранится история операций и от количества подключений по API, на данный момент, сентябрь 2020 узел с полной историей операций и небольшим количеством подключений потребляет примерно 4 GB оперативной памяти)
  • Также на данный момент полная база объектов блокчейна занимает примерно 8 GB, а общее свободное место для сборки узла должно быть не менее 10 GB
  • Для сборки на Ubuntu 18.04 требуются следующие пакеты:
sudo apt-update
sudo apt-upgrade
sudo apt-get install autoconf cmake make automake libtool git libboost-all-dev libssl-dev g++ libcurl4-openssl-dev screen
  • Первая сборка это довольно длительный по времени процесс, и в зависимости от мощности процессора может занимать до двух часов, поэтому сборку лучше осуществлять в screen

Сборка ядра блокчейна

git clone https://github.com/crowdwiz-biz/crowdwiz-core.git
cd crowdwiz-core
git checkout master
cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo .
make

Дальше нужно ждать окончания сборки.

Первый запуск узла блокчейна и синхронизация

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

programs/witness_node/witness_node

После запуска вы увидите сервисную информацию, и начнётся синхронизация. Вы увидите, что сервер получает из блокчейна блоки, пачками по 10 000 штук.

Синхронизация также занимает достаточно длительное время, на данный момент - примерно 50-60 минут в зависимости от скорости интернета и процессора.

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

2115281ms th_a       application.cpp:524           handle_block         ] Got block: #9171006 008bf03e9ad94aa16dccc89347f26f68111bcebb time: 2020-09-16T20:35:15 transaction(s): 2 latency: 281 ms from: infinity-w2  irreversible: 9170987 (-19)
2119615ms th_a       application.cpp:584           handle_transaction   ] Got 1 transactions from network
2120279ms th_a       application.cpp:524           handle_block         ] Got block: #9171007 008bf03fe2d1075af7243db583e8231e18aca5d5 time: 2020-09-16T20:35:20 transaction(s): 1 latency: 279 ms from: adorable-star  irreversible: 9170987 (-20)
2122800ms th_a       application.cpp:584           handle_transaction   ] Got 1 transactions from network
2124833ms th_a       application.cpp:584           handle_transaction   ] Got 1 transactions from network
2125282ms th_a       application.cpp:524           handle_block         ] Got block: #9171008 008bf040b6778282485e05f6c489a9ca9a290e90 time: 2020-09-16T20:35:25 transaction(s): 2 latency: 282 ms from: power8  irreversible: 9170988 (-20)

Запуск в режиме сервера API

После того как синхронизация была выполнена, можно перезапускать сервер в режиме сервера API:

programs/witness_node/witness_node --rpc-endpoint 127.0.0.1:11011

Параметр rpc-endpoint указывать на каком адресе и на каком порту слушать запросы от клиентов. В данном случае запросы будут приниматься только с этого же компьютера 127.0.0.1 на порт 11011

Кроме того чтобы указывать параметры в командной строке, можно указать все необходимые параметры настроки сервера в файле конфигурации - он находится по адресу crowdwiz-core/witness_node_data_dir/config.ini Файл создаётся автоматически при первом запуске, описание всех параметров также содержаться в этом файле.

Далее подразумевается, что сервер запущен в screen или с помощью supervisor.

Проверка работы сервера и подключение через CLI-Wallet

Запуск консольного кошелька выполняется с помощью следующей команды:

programs/cli_wallet/cli_wallet -s ws://127.0.0.1:11011

Затем необходимо задать пароль для доступа к кошельку (это не пароль от аккаунта!) и авторизоваться

set_password ВАШ_ПАРОЛЬ
unlock ВАШ_ПАРОЛЬ

Теперь можно получить данные из блокчейна, например получить информацию об аккаунте:

get_account crowdhack

Также в кошелёк уже встроены базовые операции, вы можете импортировать в него свои приватные ключи с помощью команды

import_key ВАШ_АККАУНТ ВАШ_ПРИВАТНЫЙ_КЛЮЧ

Полный список команд можно получить с помощью команды help детальную информацию о конкретной команде можно получить с помощью команды gethelp

unlocked >>> gethelp transfer

usage: transfer FROM TO AMOUNT SYMBOL "memo" BROADCAST

example: transfer "1.3.11" "1.3.4" 1000.03 CORE "memo" true
example: transfer "usera" "userb" 1000.123 CORE "memo" true

Теперь можно подключаться к собственному серверу через любой клиент, можно указать собственный витнес в настройках web-приложения cwd.global или использовать в скриптах Python.

Разработка off-chain сервисов на платформе CrowdWiz

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

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

Общие действия для разработки off-chain приложений CrowdWiz на Python

Для начала можно создать виртуальное окружение для проекта и установить в него необходимые библиотеки, в своих примерах мы используем web-сервер Tornado и peewee в качестве системы управления базами данных. Базы данных используем локальные SQLite.

mkdir cwd_project
cd cwd_project
python3 -m venv venv
source venv/bin/activate
pip3 install wheel 
pip3 install peewee tornado requests crowdwiz
pip3 install -U requests[socks]

Библиотека Python CrowdWiz создана на базе библиотеки Python Bitshares, однако в неё добавлены функции для использования нового функционала который есть только на платформе CrowdWiz. Как уже было написано выше можно использовать примеры из официальной документации Python Bitshares.

В качестве front-end в наших примерах мы чаще всего используем ботов для Telegram.

Пример №1: Шлюз авторизации пользователей

Полный исходный код примера размещён на github

Почти в каждом сценарии необходимо привязать аккаунт пользователя из блокчейна к аккаунту в вашей программе или сервисе. Это похоже на авторизацию по SMS, только авторизация через блокчейн ещё надёжнее, поскольку все передаваемые данные зашифрованы. Для начала работы мы с вами рассмотрим сервис для авторизации пользователей на базе телеграм-бота.

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

  • Пользователь вводит свой блокчейн-аккаунт
  • Эта информация попадает в шлюз авторизации
  • Шлюз генерирует одноразовый код и высылает его через блокчейн (с помощью операции send_message) на указанный аккаунт. Сообщение зашифровано, и поэтому может быть прочитано только отправителем и получателем.
  • После того, как пользователь посмотрел в блокчейне одноразовый код, он вводит его на игровой платформе, и если он совпадает, с тем что был сгенерирован, то блокчейн аккаунт привязывается к внутреннему аккаунту вашего сервиса.

Далее рассмотрим основные примеры запросов к API и выполнения операций.

Начнём с того что подключим библиотеки для работы с блокчейном CrowdWiz, которые будем использовать в данном примере.

from websocket import create_connection
import datetime, time
import json, requests

from crowdwiz import CrowdWiz
from crowdwiz.account import Account
from crowdwizbase.memo import decode_memo
from crowdwizbase.account import PublicKey, PrivateKey

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

Пусть аккаунт нашего шлюза будет crowdhack

crowdwiz_node='wss://cwd.global/ws' # в случае если вы используете публичный узел или ws://127.0.0.1:11011 если используете собственный локальный узел блокчейна
ws = create_connection(crowdwiz_node) # это экземпляр вебсокета, который подключен к узлу. Мы будем использовать его чтобы получать данные из блокчейна.
cwd = CrowdWiz(node=crowdwiz_node, keys=['Активный ключ аккаунта в формате WIF', 'Ключ примечаний аккаунта в формате WIF']) # это экземпляр объекта блокчейна, подключение осуществляется к узлу crowdwiz_node, через него будут осуществляться операции и запросы к базовым API. Операции будут подписаны активным ключом указанным в массиве keys (Ключи от вашего аккаунта вы можете посмотреть в личном кабинете в разделе права доступа)
bc_acc = Account('crowdhack', blockchain_instance = cwd) # Объект аккаунта, позволяет получить все параметры аккаунта, а такде предоставляет доступ к API истории операций аккунта

Один из самых полезных и частных методов работы API называется get_objects он позволяет получать объекты из блокчейна по их ID

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

ws.send('{"jsonrpc": "2.0", "method": "get_objects" "params": [["%s"]], "id": 1}' % bc_acc['statistics']) # получаем из объекта аккаунта ID связанного с ним объекта статистики, который содержит в себе динамические данные, и с помощью метода get_objects запрашиваем полный объект статистики
result = ws.recv()
mro = json.loads(result).get('result')[0]['most_recent_op'] # Получаем ответ из блокчейна, декодируем его из JSON и получаем most_recent_op - это объект истории транзакций 2.9.Z. 
ws.send('{"jsonrpc": "2.0", "method": "get_objects" "params": [["%s"]], "id": 1}' % mro) # получаем полный объект истории транзакций
result = ws.recv()
most_recent_op = int(json.loads(result).get('result')[0]['operation_id'].split('.')[2])# получаем ID операции 1.11.Z и выделяем её порядковый номер

Теперь совершим операцию отправки сообщения от аккаунта crowdhack на аккаунт crowd-client:

cwd.send_message("crowd-client","Код для привязки вашего аккаунта к демо-боту %s" % str(123456),account='crowdhack']) # при отправке сообщения скрипт проверит есть ли в объекте cwd приватные ключи от аккаунта crowdhack, получит открытый ключ клиента и закодирует сообщение. Операция платная! Для её совершения вы должны иметь CWD на счету.

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

Для этого нам нужно проверять историю переводов на аккаунт нашего сервиса, в нашем примере crowdhack.

Напишем функцию handle_deposits которая будет проверять историю операций, передаём в неё объект bot который содержит в себе параметры аккаунта нашего шлюза:

class BOT(peewee.Model):
	# 
	# в этой таблице хранятся данные об аккаунте шлюза авторизации, первоначальное заполнение происходит из конфигурационного файла
	# 
	bc_login = peewee.TextField(default='', null=False) # имя аккаунта в блокчейне
	bc_id = peewee.TextField(default='', null=False) # ID аккаунта в блокчейне
	most_recent_op = peewee.IntegerField(default=0, null=False) # последняя обработанная операция
	statistics_id = peewee.TextField(default='', null=False) # ID объекта статистики аккаунта в блокчейне (для кеширования)
def handle_deposits(bot):
	crowdwiz_node='wss://cwd.global/ws' # в случае если вы используете публичный узел или ws://127.0.0.1:11011 если используете собственный локальный узел блокчейна
	cwd = CrowdWiz(node=crowdwiz_node, keys=['Активный ключ аккаунта в формате WIF', 'Ключ примечаний аккаунта в формате WIF'])
	account=Account(bot.bc_login, blockchain_instance = cwd)
	max_op_id = bot.most_recent_op
	for h in account.history(): #получаем историю операций в историческом порядке от последней к первой
		op_id=h['id']
		int_op_id = int(op_id.split('.')[2])
		if int_op_id > max_op_id: #проверяем что мы ещё не обрабатывали эту операцию
			max_op_id = int_op_id
		if int_op_id<=bot.most_recent_op:
			bot.most_recent_op = max_op_id
			bot.save()
			break

		if h['op'][0] == 0 and h['op'][1]['to'] == bot.bc_id and int_op_id > bot.most_recent_op: #  h['op'][0] == 0 операции в блокчейне хранятся под номерами, это порядковый номер операции в файле https://github.com/bitshares/bitshares-core/blob/master/libraries/protocol/include/graphene/protocol/operations.hpp. 0 - индекс операции перевода (transfer)
			if (h['op'][1]['amount']['asset_id'] == "1.3.0"): # проверяем что валюта перевода CWD, поскольку это базовая валюта блокчейна, у неё ID 1.3.0
				acc = Account(h['op'][1]['from'], blockchain_instance = cwd)
				try:
					# После того как мы проврили все необходимые параметры, можем в этом блоке прописать необходимую нам логику.
				except:
					print("Account not linked")

Полный исходный код примера размещён на github

Разработка нового функционала ядра блокчейна CrowdWiz

Как уже писали ранее, разработка функционала ядра сводится к тому, что нужно добавить новые объекты и операции. Рассмотрим добавление нового функционала на примере игры орёл и решка (flipcoin) которая уже реализована на нашей платформе.

При добавлении новых функций нам необходимо придерживаться той структуры которая была разработана в оригинальном Graphene/Bitshares, поэтому необходимо соблюдать определённое расположение файлов и дополнять объекты необходимыми для работы свойствами.

Логика работы игры Орёл или Решка

Игра должна быть децентрализованная, люди должны играть между собой.

  • Один игрок делает ставку (это первая новая операция)
  • Второй игрок отвечает на эту ставку (это вторая новая операция)
  • Если на ставку нет ответа более 60 минут с момента совершения ставки, она отменяется (за это отвечает специальная функция которая выполняется при сборке блока)
  • После того как на ставку ответили, происходит определение победителя. Для того чтобы на всех узлах победитель определялся одинаково, в качестве генератора случайных чисел должен использоваться какой-то объект блокчейна. Мы будем использовать так называемую технологию блока из будущего. Суть её заключается в том, что после того как на ставку ответили, ставка помечается как ожидающая розыгрыша. И победитель опледеляется при генерации следующего блока на основе хеша этого блока. То есть на момент ответа на ставку этого блока ещё не существует.

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

Создание объектов

Для реализации нашей игры нам понадобится новый тип объекта в блокчейне, назовём его flipcoin.

Сначала создадим файл в котором опишем этот объект:

libraries/chain/include/graphene/chain/gamezone_object.hpp

Теперь опишем в нём наш объект flipcoin. Помимо идентификаторов типа объекта в нём будут следующие свойства:

  • bettor - игрок который делает ставку (создаёт объект) типа account
  • caller - игрок который отвечает на ставку типа optional account, поскольку этот второй игрок появляется не сразу
  • winner - победитель типа optional account
  • bet - это сумма ставки, типа asset
  • nonce - дополнительная случайная переменная
  • expiration - дата истечения ставки
  • status - статус ставки (активная, отвеченая, победитель определён)

Также нам понадобятся индексы чтобы мы могли выбирать эти объекты по Id, по статусу и по времени истечения.

#pragma once
#include <graphene/chain/protocol/asset.hpp>
#include <graphene/chain/protocol/types.hpp>
#include <graphene/chain/protocol/operations.hpp>

#include <graphene/db/object.hpp>
#include <graphene/db/generic_index.hpp>
#include <boost/multi_index/composite_key.hpp>

namespace graphene { namespace chain {
   using namespace graphene::db;

   class flipcoin_object : public abstract_object<flipcoin_object>
   {
      public:
        static const uint8_t space_id = protocol_ids;
        static const uint8_t type_id = flipcoin_object_type;
		account_id_type bettor;
		optional <account_id_type> caller;
		optional <account_id_type> winner;
        asset bet;
        uint8_t nonce = 1;
        time_point_sec expiration;
        uint8_t status = 0; //0 = active, 1 = filled, 2 = flipped
   };

    struct by_id;
    struct by_status;
    struct by_expiration;

    using flipcoin_multi_index_type = multi_index_container<
      flipcoin_object,
      indexed_by<
         ordered_unique< tag<by_id>,
            member<object, object_id_type, &object::id>
         >,
         ordered_non_unique< tag<by_status>,
            composite_key<
               flipcoin_object,
               member<flipcoin_object, uint8_t, &flipcoin_object::status>,
               member< object, object_id_type, &object::id>
            >
         >,
         ordered_unique< tag<by_expiration>,
            composite_key< flipcoin_object,
               member< flipcoin_object, time_point_sec, &flipcoin_object::expiration>,
               member< object, object_id_type, &object::id>
            >
         >
      >
   >;
   using flipcoin_index = generic_index<flipcoin_object, flipcoin_multi_index_type>;
} } // graphene::chain

FC_REFLECT_DERIVED( graphene::chain::flipcoin_object, (graphene::db::object),
	(bettor)
	(caller)
	(winner)
	(bet)
	(nonce)
	(expiration)
	(status)
) 

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

libraries/chain/include/graphene/chain/protocol/types.hpp //определяет типы
namespace graphene { namespace chain {
...
   enum object_type
   {
...
      flipcoin_object_type, 
...
   };
...
   class flipcoin_object;
...
   typedef object_id< protocol_ids, flipcoin_object_type,flipcoin_object> flipcoin_id_type;
...
} }  

FC_REFLECT_ENUM( graphene::chain::object_type,
...
                 (flipcoin_object_type)
...
               )
...
FC_REFLECT_TYPENAME( graphene::chain::flipcoin_id_type )
...
libraries/chain/db_init.cpp //файл инициализации базы данных
#include <graphene/chain/gamezone_object.hpp> 
...
const uint8_t flipcoin_object::space_id; //Идентификаторы типа объекта
const uint8_t flipcoin_object::type_id; //Идентификаторы типа объекта
...
add_index< primary_index<flipcoin_index> >(); //Поисковой индекс нового объекта
...
libraries/chain/db_notify.cpp //устанавливает взаимосвязь между объектами
#include <graphene/chain/gamezone_object.hpp>
...
void get_relevant_accounts( const object* obj, flat_set<account_id_type>& accounts )
{
...
        case flipcoin_object_type:{
           const auto& aobj = dynamic_cast<const flipcoin_object*>(obj);
           FC_ASSERT( aobj != nullptr );
           accounts.insert( aobj->bettor ); //Обект связан с аккаунтом который его создал
           break;
        }
...
} 

Создание операций

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

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

В каждой операции есть struct fee_parameters_type { uint64_t fee = XXXX; }; это комиссия за операцию по умолчанию. Также в каждой операции есть fee_payer() это тот аккаунт который оплачивает комиссию за операцию, а значит является её инициатором.

libraries/chain/include/graphene/chain/protocol/gamezone.hpp //содержит в себе описание структуры операции
#pragma once
#include <graphene/chain/protocol/base.hpp>

namespace graphene { namespace chain { 

   struct flipcoin_bet_operation : public base_operation //сделать ставку
   {
      struct fee_parameters_type { uint64_t fee = 0; };

      asset             fee;
      account_id_type   bettor;
      asset             bet;
      uint8_t           nonce;

      account_id_type 	fee_payer()const { return bettor; }
      void            	validate()const;
   };

   struct flipcoin_call_operation : public base_operation //ответить на ставку
   {
      struct fee_parameters_type { share_type fee = 0; };

      asset             fee;
      flipcoin_id_type   flipcoin;
      account_id_type   caller;
      asset             bet;

      account_id_type 	fee_payer()const { return caller; }
      void            	validate()const;
   };

   struct flipcoin_win_operation : public base_operation //виртуальная операция выигрыша
   {
      struct fee_parameters_type { share_type fee = 0; };

      asset             fee;
      flipcoin_id_type   flipcoin;
      account_id_type   winner;
      asset             payout;
      asset             referral_payout;

      account_id_type 	fee_payer()const { return winner; }
      void            	validate()const;
   };

   struct flipcoin_loose_operation : public base_operation //виртуальная операция проигрыша
   {
      struct fee_parameters_type { share_type fee = 0; };

      asset             fee;
      flipcoin_id_type   flipcoin;
      account_id_type   looser;
      asset             bet;

      account_id_type 	fee_payer()const { return looser; }
      void            	validate()const;
   };

   struct flipcoin_cancel_operation : public base_operation//виртуальная операция отмены ставки
   {
      struct fee_parameters_type { share_type fee = 0; };

      asset             fee;
      flipcoin_id_type   flipcoin;
      account_id_type   bettor;
      asset             bet;

      account_id_type 	fee_payer()const { return bettor; }
      void            	validate()const;
   };
} } // graphene::chain
FC_REFLECT( graphene::chain::flipcoin_bet_operation::fee_parameters_type, (fee) )
FC_REFLECT( graphene::chain::flipcoin_bet_operation, (fee)(bettor)(bet)(nonce))

FC_REFLECT( graphene::chain::flipcoin_call_operation::fee_parameters_type, (fee) )
FC_REFLECT( graphene::chain::flipcoin_call_operation, (fee)(flipcoin)(caller)(bet))

FC_REFLECT( graphene::chain::flipcoin_win_operation::fee_parameters_type, (fee) )
FC_REFLECT( graphene::chain::flipcoin_win_operation, (fee)(flipcoin)(winner)(payout)(referral_payout))

FC_REFLECT( graphene::chain::flipcoin_loose_operation::fee_parameters_type, (fee) )
FC_REFLECT( graphene::chain::flipcoin_loose_operation, (fee)(flipcoin)(looser)(bet))

FC_REFLECT( graphene::chain::flipcoin_cancel_operation::fee_parameters_type, (fee) )
FC_REFLECT( graphene::chain::flipcoin_cancel_operation, (fee)(flipcoin)(bettor)(bet) )
libraries/chain/protocol/gamezone.cpp //содержит базовые проверки
#include <graphene/chain/protocol/gamezone.hpp>

namespace graphene { namespace chain {

    share_type cut_fee_gamezone(share_type a, uint16_t p)
    {
    if( a == 0 || p == 0 )
        return 0;
    if( p == GRAPHENE_100_PERCENT )
        return a;

    fc::uint128 r(a.value);
    r *= p;
    r /= GRAPHENE_100_PERCENT;
    return r.to_uint64();
    }

    void flipcoin_bet_operation::validate()const
    {
    FC_ASSERT( fee.amount >= 0 );
    FC_ASSERT( bet.amount > 0 );
    }

    void flipcoin_call_operation::validate()const
    {
    FC_ASSERT( fee.amount >= 0 );
    FC_ASSERT( bet.amount >= 0 );
    }
    void flipcoin_win_operation::validate()const
    {
    FC_ASSERT( fee.amount >= 0 );
    FC_ASSERT( payout.amount >= 0 );
    FC_ASSERT( referral_payout.amount >= 0 );
    }
    void flipcoin_loose_operation::validate()const
    {
    FC_ASSERT( fee.amount >= 0 );
    FC_ASSERT( bet.amount >= 0 );
    }
    void flipcoin_cancel_operation::validate()const
    {
    FC_ASSERT( fee.amount >= 0 );
    FC_ASSERT( bet.amount >= 0 );
    }
} } 

Теперь нужно добавить наши операции в список операций блокчейна:

libraries/chain/include/graphene/chain/protocol/operations.hpp
#pragma once
...
#include <graphene/chain/protocol/gamezone.hpp>
...
namespace graphene { namespace chain {
   typedef fc::static_variant<
...
            flipcoin_bet_operation,  //GAMEZONE
            flipcoin_call_operation,  //GAMEZONE
            flipcoin_win_operation,  //VOP
            flipcoin_cancel_operation,  //VOP
            flipcoin_loose_operation,  //VOP
...
         > operation;
...
} } // graphene::chain
...

Также нужно указать в истории операций какого аккаунта будет отображаться та или иная операция, это необходимо прописать в файле

libraries/chain/db_notify.cpp
#include <graphene/chain/gamezone_object.hpp>
...
using namespace fc;
using namespace graphene::chain;
struct get_impacted_account_visitor
{
...
   void operator()( const flipcoin_bet_operation& op )
   {
      _impacted.insert( op.fee_payer() ); 
   }
   void operator()( const flipcoin_call_operation& op )
   {
      _impacted.insert( op.fee_payer() ); 
   }
   void operator()( const flipcoin_win_operation& op )
   {
      _impacted.insert( op.winner); 
   }
   void operator()( const flipcoin_loose_operation& op )
   {
      _impacted.insert( op.looser); 
   }
   void operator()( const flipcoin_cancel_operation& op )
   {
      _impacted.insert( op.bettor); 
   }
...
};

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

libraries/chain/include/graphene/chain/gamezone_evaluator.hpp

do_evaluate функция которая проверяет, что операция может быть выполнена конкретным аккаунтом

do_apply вносит изменения в объекты блокчейна

#pragma once
#include <graphene/chain/evaluator.hpp>
#include <graphene/chain/gamezone_object.hpp>

namespace graphene { namespace chain {

   class flipcoin_bet_evaluator : public evaluator<flipcoin_bet_evaluator>
   {
      public:
         typedef flipcoin_bet_operation operation_type;

         void_result do_evaluate( const flipcoin_bet_operation& o );
         object_id_type do_apply( const flipcoin_bet_operation& o );
   };

   class flipcoin_call_evaluator : public evaluator<flipcoin_call_evaluator>
   {
      public:
         typedef flipcoin_call_operation operation_type;

         void_result do_evaluate( const flipcoin_call_operation& o );
         void_result do_apply( const flipcoin_call_operation& o );
        const flipcoin_object* flipcoin;
   };

} } // graphene::chain 
libraries/chain/gamezone_evaluator.cpp
#include <graphene/chain/gamezone_evaluator.hpp>
#include <graphene/chain/gamezone_object.hpp>
#include <graphene/chain/account_object.hpp>
#include <graphene/chain/database.hpp>
#include <graphene/chain/hardfork.hpp>

namespace graphene
{
namespace chain
{

share_type cut_fee_game(share_type a, uint16_t p)
{
   if (a == 0 || p == 0)
      return 0;
   if (p == GRAPHENE_100_PERCENT)
      return a;

   fc::uint128 r(a.value);
   r *= p;
   r /= GRAPHENE_100_PERCENT;
   return r.to_uint64();
}

void_result flipcoin_bet_evaluator::do_evaluate(const flipcoin_bet_operation &op)
{
    try
    {
      database& d = db();
		const account_object& bettor    	= op.bettor(d);
		const asset_object&   asset_type    = op.bet.asset_id(d);

      FC_ASSERT( op.bet.asset_id == asset_id_type(), "Price must be in core asset");
      FC_ASSERT( op.bet.amount >= 1000,  "Bet amount must be more or equal than 0.01 CWD");

     	bool insufficient_balance = d.get_balance( bettor, asset_type ).amount >= op.bet.amount;
     	FC_ASSERT( insufficient_balance,
                 "Insufficient Balance: ${balance}, unable to bet '${total_bet}' from account '${a}'", 
                 ("a",bettor.name)("total_bet",d.to_pretty_string(op.bet))("balance",d.to_pretty_string(d.get_balance(bettor, asset_type))) );
        return void_result();
    }
    FC_CAPTURE_AND_RETHROW((op))
}

object_id_type flipcoin_bet_evaluator::do_apply(const flipcoin_bet_operation &op)
{
    try
    {
        database& d = db();
		db().adjust_balance( op.bettor, -op.bet );

        const auto& new_flipcoin_object = db().create<flipcoin_object>([&](flipcoin_object &obj) {
            obj.bettor = op.bettor;
            obj.bet = op.bet;
            obj.status = 0;
			obj.nonce = op.nonce;
            obj.expiration = d.head_block_time() + fc::hours(1);
        });
        return new_flipcoin_object.id;
    }
    FC_CAPTURE_AND_RETHROW((op))
}

void_result flipcoin_call_evaluator::do_evaluate(const flipcoin_call_operation &op)
{
    try
    {
      database& d = db();
		flipcoin = &op.flipcoin(d);

		const account_object& caller    	= op.caller(d);
		const asset_object&   asset_type    = op.bet.asset_id(d);

     	bool insufficient_balance = d.get_balance( caller, asset_type ).amount >= op.bet.amount;
     	FC_ASSERT( insufficient_balance,
                 "Insufficient Balance: ${balance}, unable to bet '${total_bet}' from account '${a}'", 
                 ("a",caller.name)("total_bet",d.to_pretty_string(op.bet))("balance",d.to_pretty_string(d.get_balance(caller, asset_type))) );
		FC_ASSERT(flipcoin->status == 0, "flipcoin already called");
		FC_ASSERT(flipcoin->expiration >= d.head_block_time(), "flipcoin already called");
		FC_ASSERT(flipcoin->bet.amount == op.bet.amount, "flipcoin bet amount must match");

        return void_result();
    }
    FC_CAPTURE_AND_RETHROW((op))
}

void_result flipcoin_call_evaluator::do_apply(const flipcoin_call_operation &op)
{
    try
    {
        database& d = db();
		db().adjust_balance( op.caller, -op.bet );
		d.modify(
			d.get(op.flipcoin),
			[&]( flipcoin_object& f )
			{
				f.status = 1;
				f.caller = op.caller;
                f.expiration = d.head_block_time() + fc::seconds(10);
			}
		);
        return void_result();
    }
    FC_CAPTURE_AND_RETHROW((op))
}

}} // namespace chain

Теперь нужно зарегистрировать наши новые эвалюаторы

libraries/chain/db_init.cpp
void database::initialize_evaluators()
{
...
   register_evaluator<flipcoin_bet_evaluator>(); 
   register_evaluator<flipcoin_call_evaluator>(); 
...
} 

Создание функций вызывающихся при сборке блоков

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

libraries/chain/include/graphene/chain/database.hpp

Опишем нашу новую функцию

...
//////////////////// db_update.cpp ////////////////////
...
void proceed_bets();
...

Теперь пропишем её функционал в файле

libraries/chain/db_update.cpp
void database::proceed_bets()
{ try {
   auto head_time = head_block_time();

   auto& flipcoin_idx = get_index_type<flipcoin_index>().indices().get<by_expiration>();
   while( !flipcoin_idx.empty() && flipcoin_idx.begin()->expiration <= head_time && flipcoin_idx.begin()->status < 2 )
   {
      auto block_id = head_block_id();
      const flipcoin_object& flipcoin = *flipcoin_idx.begin();
      uint8_t check_nonce = 8+flipcoin.nonce;
      if (check_nonce>39) {
         check_nonce = 39;
      }
      std::string id_substr = std::string(block_id).substr(check_nonce,1);
      if(flipcoin.status == 0) {
         flipcoin_log( "proceed_bets: CANCEL FLIPCOIN: ${b}", ("b", flipcoin.id) );
         adjust_balance( flipcoin.bettor, flipcoin.bet );
         flipcoin_cancel_operation vop_cancel;
         vop_cancel.flipcoin = flipcoin.id;
         vop_cancel.bettor = flipcoin.bettor;
         vop_cancel.bet = flipcoin.bet;
         push_applied_operation( vop_cancel );

         remove(flipcoin);
      }
      if (flipcoin.status == 1) {
         flipcoin_log( "proceed_bets: FLIP FLIPCOIN: ${b}", ("b", flipcoin.id) );
         flipcoin_log( "proceed_bets: block ID: ${b}", ("b", block_id) );
         flipcoin_log( "proceed_bets: block NUM: ${b}", ("b", head_block_num()) );
         flipcoin_log( "proceed_bets: block TIME: ${b}", ("b", head_time) );
         flipcoin_log( "proceed_bets: ID SUBSTRING: ${b}", ("b", id_substr) );
         bool heads;
         switch(id_substr[0])
               {
                     case '0': heads = true; break;
                     case '1': heads = false; break;
                     case '2': heads = true; break;
                     case '3': heads = false; break;
                     case '4': heads = true; break;
                     case '5': heads = false; break;
                     case '6': heads = true; break;
                     case '7': heads = false; break;
                     case '8': heads = true; break;
                     case '9': heads = false; break;
                     case 'a': heads = true; break;
                     case 'b': heads = false; break;
                     case 'c': heads = true; break;
                     case 'd': heads = false; break;
                     case 'e': heads = true; break;
                     case 'f': heads = false; break;
               }
         flipcoin_log( "proceed_bets: HEADS: ${b}", ("b", heads) );
         flipcoin_log( "proceed_bets: flipcoin: ${b}", ("b", flipcoin ) );
         asset prize;
         prize.asset_id = flipcoin.bet.asset_id;
         prize.amount = count_prize(flipcoin.bet.amount);
         asset referral_prize;
         referral_prize.asset_id = flipcoin.bet.asset_id;
         referral_prize.amount = count_referral_prize(flipcoin.bet.amount);

         flipcoin_win_operation vop_win;
         flipcoin_loose_operation vop_loose;

         vop_win.flipcoin = flipcoin.id;
         vop_win.payout = prize;
         vop_win.referral_payout = referral_prize;

         vop_loose.flipcoin = flipcoin.id;
         vop_loose.bet = flipcoin.bet;
         
         const account_object& caller = get(*flipcoin.caller);

         if(heads == true) {
            flipcoin_log( "proceed_bets: Winner: ${b}", ("b", flipcoin.bettor ) );
            vop_win.winner = flipcoin.bettor;     
            vop_loose.looser =  caller.id;
            adjust_balance( flipcoin.bettor, prize );
         }
         else {
            flipcoin_log( "proceed_bets: Winner: ${b}", ("b", flipcoin.caller ) );
            vop_win.winner = caller.id;
            vop_loose.looser = flipcoin.bettor;
            adjust_balance( caller.id, prize );
         }
         push_applied_operation( vop_win );
         push_applied_operation( vop_loose );

         const account_object& winner_account = get(vop_win.winner);
         const account_object& looser_account = get(vop_loose.looser);

         winner_account.statistics(*this).update_nv(referral_prize.amount, uint8_t(1) , uint8_t(0) , winner_account, *this);
         const account_statistics_object& customer_statistics = winner_account.statistics(*this);

         if ( head_block_time() >= HARDFORK_CWD2_TIME ) {
            winner_account.statistics(*this).update_pv(flipcoin.bet.amount, winner_account, *this);
            looser_account.statistics(*this).update_pv(flipcoin.bet.amount, looser_account, *this);
         }
         else {
            winner_account.statistics(*this).update_pv(referral_prize.amount, winner_account, *this);
         }

         modify(customer_statistics, [&](account_statistics_object& s)
         {
            s.pay_fee( referral_prize.amount, false );
         });
         //--------
         // if (head_time < HARDFORK_CWD6_TIME) {
            modify(winner_account, [&](account_object& a) {
               a.statistics(*this).process_fees(a, *this);
            });
         // }
         // -----------------------

         remove(flipcoin);
      }
   }
} FC_CAPTURE_AND_RETHROW() }

И будем вызывать её каждый блок:

libraries/chain/db_block.cpp
void database::_apply_block( const signed_block& next_block )
{ try {
   uint32_t next_block_num = next_block.block_num();
   uint32_t skip = get_node_properties().skip_flags;
   _applied_ops.clear();
...
   proceed_bets();
...
} FC_CAPTURE_AND_RETHROW( (next_block.block_num()) )  }

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published