Блокчейн: що нам варто PoC збудувати?

Очі бояться, а руки сверблять!

У минулих статтях ми розібралися з технологіями, на яких будуються блокчейни.Що нам варто блокчейн збудувати?) та кейсами, які можна з їх допомогою реалізувати (Що нам варто кейс збудувати?). Настав час попрацювати руками! Для реалізації пілотів і PoC (Proof of Concept) я віддаю перевагу використанню хмар, т.к. до них є доступ із будь-якої точки світу і часто не треба витрачати час на нудну установку оточення, т.к. Існують попередньо встановлені зміни. Отже, давайте зробимо щось просте, наприклад, мережу для перекладу монет між учасниками та назвемо її скромно Сitcoin. Для цього будемо використовувати хмару IBM та універсальний блокчейн Hyperledger Fabric. Спочатку розберемося, чому Hyperledger Fabric називають універсальним блокчейном?

Блокчейн: що нам варто PoC збудувати?

Hyperledger Fabric - універсальний блокчейн

Якщо говорити загалом, то універсальна інформаційна система:

  • Набір серверів та програмне ядро, що виконує бізнес логіку;
  • Інтерфейси для взаємодії із системою;
  • Засоби для реєстрації, автентифікації та авторизації пристроїв/людей;
  • База даних, що зберігає оперативні та архівні дані:

Блокчейн: що нам варто PoC збудувати?

Офіційну версію, що таке Hyperledger Fabric можна почитати на сайті, а якщо коротко, то Hyperledger Fabric – це opensource платформа, що дозволяє будувати закриті блокчейни та виконувати довільні смарт-контракти, написані мовами програмування JS та Go. Подивимося детально на архітектуру Hyperledger Fabric і переконаємось, що це універсальна система, в якій тільки є специфіка зберігання та запису даних. Специфіка полягає в тому, що дані, як і у всіх блокчейнах, зберігаються в блоках, які поміщаються в блокчейн тільки якщо учасники прийшли до консенсусу і після запису дані неможливо непомітно виправити або видалити.

Архітектура Hyperledger Fabric

На схемі представлена ​​архітектура Hyperledger Fabric:

Блокчейн: що нам варто PoC збудувати?

Організацій - Організації містять peer-и, т.ч. блокчейн існує з допомогою підтримки організаціями. Різні організації можуть входити до одного channel.

Канал - логічна структура, що поєднує peer-и в групи, т.ч. задається блокчейн. Hyperledger Fabric може одночасно обробляти кілька блокчейнів із різною бізнес-логікою.

Membership Services Provider (MSP) - це CA (Certificate Authority) для видачі identity та призначення ролей. Для створення ноди потрібно взаємодіяти з MSP.

Peer nodes — перевіряють транзакції, зберігають блокчейн, виконують смарт-контракти та взаємодіють із додатками. У peer-ів є identity (цифровий сертифікат), який видає MSP. На відміну від мережі Bitcoin або Etherium, де всі ноди рівноправні, Hyperledger Fabric ноди відіграють різні ролі:

  • Peer може бути endorsing peer (EP) та виконувати смарт-контракти.
  • Committing peer (CP) - тільки зберігають дані в блокчейні та актуалізують "World state".
  • Anchor Peer (AP) - якщо в блокчейні беруть участь кілька організацій, то анкор peer-и використовуються для зв'язку між ними. Кожна організація повинна мати один або кілька анкор peer. За допомогою AP будь-який peer в організації може отримати інформацію про всіх peer-ах в інших організаціях. Для синхронізації інформації між AP використовується gossip протокол.
  • Leader Peer — якщо організація має кілька peer-ів, то тільки лідер peer отримуватиме блоки з Ordering service і віддаватиме їх іншим peer-ам. Лідер може задаватися статично, так і вибиратися динамічно peer-ами в організації. Для синхронізації інформації про лідерів також використовується протокол gossip.

Активи - сутності, що мають цінність, які зберігаються у блокчейні. Більш конкретно це key-value дані у форматі JSON. Саме ці дані записуються в блокчейн «Blockchain». У них є історія, яка зберігається в блокчейні та поточний стан, що зберігається у базі даних «World state». Структури даних наповнюються довільно залежно від бізнес-завдань. Немає жодних обов'язкових полів, єдина рекомендація — asset повинні мати власника і представляти цінність.

Гросбух - складається з блокчейну "Blockchain" і бази даних "Word state", в якій зберігається поточний стан asset-ів. World state використовує LevelDB або CouchDB.

Спритний контракт - За допомогою смарт-контрактів реалізується бізнес-логіка системи. У Hyperledger Fabric смарт-контракти називаються chaincode. За допомогою chaincode задаються asset-и та транзакції над ними. Якщо говорити технічною мовою, то смарт-контракти це програмні модулі, реалізовані мовами програмування JS або Go.

Endorsement policy — для кожного chaincode можна задати політики скільки і від кого потрібно чекати на підтвердження для транзакції. Якщо політика не задана, то за умовчанням використовується: “транзакцію має підтвердити будь-який член (member) будь-якої організації у channel”. Приклади політик:

  • Транзакцію має підтвердити будь-який адміністратор організації;
  • Повинен підтвердити будь-який член (member) чи клієнт організації;
  • Повинен підтвердити будь-який peer організації.

Послуга замовлення - Упаковує транзакції в блоки і відправляє peer-ам в channel. Гарантує доставку повідомлень усім peer-ам у мережі. Для промислових систем використовується брокер повідомлень Kafka, для розробки та тестування Соло.

CallFlow

Блокчейн: що нам варто PoC збудувати?

  • Програма взаємодіє з Hyperledger Fabric, використовуючи Go, Node.js або Java SDK;
  • Клієнт створює транзакцію tx та посилає її на endorsing peer-и;
  • Peer перевіряє підпис клієнта, виконує транзакцію та посилає endorsement signature назад клієнту. Chaincode виконуються тільки на endorsing peer, а результат його виконання розсилається на всі peer-и. Такий алгоритм роботи називається PBFT (Practical Byzantine Fault Tolerant) консенсус. Відрізняється від класичного BFT тим, що повідомлення розсилається та очікується підтвердження не від усіх учасників, а лише від певного набору;
  • Після того, як клієнт отримав кількість відповідей, що відповідає endorsement policy, він посилає транзакцію на Ordering service;
  • Ordering service формує блок і посилає його на всі committing peer-и. Ordering service забезпечує послідовний запис блоків, що виключає так званий ledger fork (див. розділ «Форки»);
  • Peer-и отримують блок, ще раз перевіряють endorsement policy, записують блок у блокчейн і змінюють стан у «World state» DB.

Тобто. виходить поділ ролей між нодами. Це забезпечує масштабування та безпеку блокчейну:

  • Смарт-контракти (chaincode) виконують endorsing peer-и. Це забезпечує конфіденційність смарт-контрактів, т.к. він зберігається не у всіх учасників, а лише на endorsing peer-ах.
  • Ordering має працювати швидко. Це забезпечується тим, що Ordering лише формує блок і відправляє його на фіксований набір leader peer-ів.
  • Committing peers тільки зберігають блокчейн - їх може бути багато і вони не вимагають великої потужності та миттєвої роботи.

Докладніше архітектурні рішення Hyperledger Fabric і чому він працює так, а не інакше можна подивитися тут: Architecture Origins або тут: Hyperledger Fabric: A Distributed Operating System для розподілених блоків.

Отже, Hyperledger Fabric - це справді універсальна система, за допомогою якої можна:

  • реалізовувати довільну бізнес-логіку, використовуючи механізм смарт-контрактів;
  • Записувати та отримувати дані з блокчейн бази даних формату JSON;
  • Надавати та перевіряти доступ до API, використовуючи Certificate Authority.

Тепер, коли ми трохи розібралися зі специфікою Hyperledger Fabric, давайте нарешті зробимо щось корисне!

Розгортаємо блокчейн

Постановка завдання

Завдання - реалізувати мережу Citcoin з наступними функціями: створити account, отримати баланс, поповнити рахунок, перевести монети з одного рахунку на інший. Намалюємо об'єктну модель, яку реалізуємо в смарт-контракті. Отже, у нас будуть account-и, які ідентифікуються іменами (name) та містять баланс (balance), та список account-ів. Account-и та список account-ів - це в термінах Hyperledger Fabric asset-и. Відповідно, у них є історія та поточний стан. Спробую це наочно намалювати:

Блокчейн: що нам варто PoC збудувати?

Верхні фігури - це поточний стан, що зберігається в базі "World state". Під ними фігури, що показують історію, що зберігається у блокчейні. Поточний стан asset-ів змінюється транзакціями. Asset змінюється лише цілком, у результаті виконання транзакції створюється новий об'єкт, а поточне значення asset-а йде у історію.

Хмара IBM

Заводимо обліковий запис хмарі IBM. Для використання блокчейн платформи її потрібно апгрейдити до Pay-As-You-Go. Цей процес то, можливо не швидким, т.к. IBM запитує додаткову інформацію та перевіряє її вручну. З позитивного можу сказати, що IBM непогані навчальні матеріали, що дозволяють розгорнути Hyperledger Fabric в їх хмарі. Мені сподобався наступний цикл статей та прикладів:

Далі наведені скріншоти Blockchain платформи IBM. Це не інструкція щодо створення блокчейну, а просто демонстрація обсягу завдання. Отже, для наших цілей робимо одну Organization:

Блокчейн: що нам варто PoC збудувати?

У ній створюємо ноди: Orderer CA, Org1 CA, Orderer Peer:

Блокчейн: що нам варто PoC збудувати?

Заводимо користувачів:

Блокчейн: що нам варто PoC збудувати?

Створюємо Channel і називаємо його citcoin:

Блокчейн: що нам варто PoC збудувати?

По суті Channel це блокчейн, тому він починається з нульового блоку (Genesis block):

Блокчейн: що нам варто PoC збудувати?

Пишемо Smart Contract

/*
 * Citcoin smart-contract v1.5 for Hyperledger Fabric
 * (c) Alexey Sushkov, 2019
 */
 
'use strict';
 
const { Contract } = require('fabric-contract-api');
const maxAccounts = 5;
 
class CitcoinEvents extends Contract {
 
    async instantiate(ctx) {
        console.info('instantiate');
        let emptyList = [];
        await ctx.stub.putState('accounts', Buffer.from(JSON.stringify(emptyList)));
    }
    // Get all accounts
    async GetAccounts(ctx) {
        // Get account list:
        let accounts = '{}'
        let accountsData = await ctx.stub.getState('accounts');
        if (accountsData) {
            accounts = JSON.parse(accountsData.toString());
        } else {
            throw new Error('accounts not found');
        }
        return accountsData.toString()
    }
     // add a account object to the blockchain state identifited by their name
    async AddAccount(ctx, name, balance) {
        // this is account data:
        let account = {
            name: name,
            balance: Number(balance),       
            type: 'account',
        };
        // create account:
        await ctx.stub.putState(name, Buffer.from(JSON.stringify(account)));
 
        // Add account to list:
        let accountsData = await ctx.stub.getState('accounts');
        if (accountsData) {
            let accounts = JSON.parse(accountsData.toString());
            if (accounts.length < maxAccounts)
            {
                accounts.push(name);
                await ctx.stub.putState('accounts', Buffer.from(JSON.stringify(accounts)));
            } else {
                throw new Error('Max accounts number reached');
            }
        } else {
            throw new Error('accounts not found');
        }
        // return  object
        return JSON.stringify(account);
    }
    // Sends money from Account to Account
    async SendFrom(ctx, fromAccount, toAccount, value) {
        // get Account from
        let fromData = await ctx.stub.getState(fromAccount);
        let from;
        if (fromData) {
            from = JSON.parse(fromData.toString());
            if (from.type !== 'account') {
                throw new Error('wrong from type');
            }   
        } else {
            throw new Error('Accout from not found');
        }
        // get Account to
        let toData = await ctx.stub.getState(toAccount);
        let to;
        if (toData) {
            to = JSON.parse(toData.toString());
            if (to.type !== 'account') {
                throw new Error('wrong to type');
            }  
        } else {
            throw new Error('Accout to not found');
        }
 
        // update the balances
        if ((from.balance - Number(value)) >= 0 ) {
            from.balance -= Number(value);
            to.balance += Number(value);
        } else {
            throw new Error('From Account: not enought balance');          
        }
 
        await ctx.stub.putState(from.name, Buffer.from(JSON.stringify(from)));
        await ctx.stub.putState(to.name, Buffer.from(JSON.stringify(to)));
                 
        // define and set Event
        let Event = {
            type: "SendFrom",
            from: from.name,
            to: to.name,
            balanceFrom: from.balance,
            balanceTo: to.balance,
            value: value
        };
        await ctx.stub.setEvent('SendFrom', Buffer.from(JSON.stringify(Event)));
 
        // return to object
        return JSON.stringify(from);
    }
 
    // get the state from key
    async GetState(ctx, key) {
        let data = await ctx.stub.getState(key);
        let jsonData = JSON.parse(data.toString());
        return JSON.stringify(jsonData);
    }
    // GetBalance   
    async GetBalance(ctx, accountName) {
        let data = await ctx.stub.getState(accountName);
        let jsonData = JSON.parse(data.toString());
        return JSON.stringify(jsonData);
    }
     
    // Refill own balance
    async RefillBalance(ctx, toAccount, value) {
        // get Account to
        let toData = await ctx.stub.getState(toAccount);
        let to;
        if (toData) {
            to = JSON.parse(toData.toString());
            if (to.type !== 'account') {
                throw new Error('wrong to type');
            }  
        } else {
            throw new Error('Accout to not found');
        }
 
        // update the balance
        to.balance += Number(value);
        await ctx.stub.putState(to.name, Buffer.from(JSON.stringify(to)));
                 
        // define and set Event
        let Event = {
            type: "RefillBalance",
            to: to.name,
            balanceTo: to.balance,
            value: value
        };
        await ctx.stub.setEvent('RefillBalance', Buffer.from(JSON.stringify(Event)));
 
        // return to object
        return JSON.stringify(from);
    }
}
module.exports = CitcoinEvents;

Інтуїтивно тут має бути все зрозуміло:

  • Є кілька функцій (AddAccount, GetAccounts, SendFrom, GetBalance, RefillBalance), які викликатиме демо програма за допомогою Hyperledger Fabric API.
  • Функції SendFrom та RefillBalance генерують події (Event), які отримуватиме демо програма.
  • Функція instantiate – викликається один раз під час інстанціювання смарт-контракту. Насправді вона викликається не один раз, а щоразу при зміні версії смарт-контракту. Тому ініціалізація списку порожнім масивом це погана ідея, т.к. тепер при зміні версії смарт-контракту ми втрачатимемо поточний список. Але нічого, я ж тільки навчаюсь).
  • Account-и та список account-ів (accounts) - це JSON структури даних. Для маніпуляцій із даними використовується JS.
  • Отримати поточне значення asset-а можна з допомогою виклику функції getState, а оновити з допомогою putState.
  • Під час створення Account викликається функція AddAccount, у якій виробляється порівняння на максимальне число account-в блокчейне (maxAccounts = 5). І тут є косяк (помітили?), що призводить до нескінченного зростання числа account-ів. Таких помилок треба уникати)

Далі завантажуємо смарт-контракт у Channel та інстанціюємо його:

Блокчейн: що нам варто PoC збудувати?

Дивимося транзакцію на встановлення Smart Contract:

Блокчейн: що нам варто PoC збудувати?

Дивимося подробиці про наш Channel:

Блокчейн: що нам варто PoC збудувати?

В результаті отримуємо наступну схему блокчейн мережі у хмарі IBM. Також на схемі є демо програма, запущена в хмарі Amazon на віртуальному сервері (докладно про неї буде в наступному розділі):

Блокчейн: що нам варто PoC збудувати?

Створення GUI для викликів Hyperledger Fabric API

Hyperledger Fabric має API, яке може використовуватися для:

  • Створення channel;
  • Підключення peer до channel;
  • Встановлення та інстанціювання смарт-конкрактів у channel;
  • Виклик транзакцій;
  • Запит інформації у блокчейні.

Розробка програми

У нашій демо програмі будемо використовувати API лише для виклику транзакцій та запиту інформації, т.к. решту кроків ми вже зробили, використовуючи блокчейн платформу IBM. Пишемо GUI, використовуючи стандартний стек технологій: Express.js + Vue.js + Node.js. Про те, як почати створювати сучасні веб-додатки, можна написати окрему статтю. Тут залишу посилання на серію лекцій, яка мені найбільше сподобалася: Full Stack Web App за допомогою Vue.js & Express.js. В результаті вийшов клієнт-серверний додаток зі знайомим графічним інтерфейсом у стилі Material Design від Google. REST API між клієнтом та сервером складається з кількох викликів:

  • HyperledgerDemo/v1/init - ініціалізувати блокчейн;
  • HyperledgerDemo/v1/accounts/list - отримати список всіх account-ів;
  • HyperledgerDemo/v1/account?name=Bob&balance=100 — створити Bob account;
  • HyperledgerDemo/v1/info?account=Bob — отримати інформацію про Bob account;
  • HyperledgerDemo/v1/transaction?from=Bob&to=Alice&volume=2 — перекласти дві монети від Bob до Alice;
  • HyperledgerDemo/v1/disconnect – закрити з'єднання з блокчейном.

Опис API з прикладами поклав на сайт «Postman» — широко відома програма для тестування HTTP API.

Демо додаток у хмарі Amazon

Програма залив на Amazon, т.к. IBM досі не зміг апгрейдити мою облікову і дозволити створювати віртуальні сервери. Як вишеньку приробив домен: www.citcoin.info. Підтримаю трохи сервер увімкненим, потім вимкну, т.к. центи за оренду капають, а монети citcoin на біржі ще не котируються) У статтю поміщаю скріншоти демо, щоб була зрозуміла логіка роботи. Демо додаток може:

  • Ініціалізувати блокчейн;
  • Створювати Account (але зараз новий Account не створити, тому що в блокчейні досягнуто максимальної кількості account-ів, прописаної в смарт-контракті);
  • Отримувати список Account-ів;
  • Перекладати монети citcoin між Alice, Bob та Alex;
  • Отримувати події (але зараз події не показати, тому в інтерфейсі для простоти написано, що події не підтримуються);
  • Логувати дії.

Спочатку ініціалізуємо блокчейн:

Блокчейн: що нам варто PoC збудувати?

Далі заводимо свій account, не дрібнимемося з балансом:

Блокчейн: що нам варто PoC збудувати?

Отримуємо список всіх доступних акаунтів:

Блокчейн: що нам варто PoC збудувати?

Вибираємо відправника та одержувача, отримуємо їх баланси. Якщо відправник і одержувач той самий, то відбудеться поповнення його рахунку:

Блокчейн: що нам варто PoC збудувати?

У лозі слідкуємо за виконанням транзакцій:

Блокчейн: що нам варто PoC збудувати?

Власне з демо програмою на цьому все. Далі можна переглянути нашу транзакцію в блокчейні:

Блокчейн: що нам варто PoC збудувати?

І загальний список транзакцій:

Блокчейн: що нам варто PoC збудувати?

На цьому ми успішно завершили реалізацію PoC створення мережі Citcoin. Що ще потрібно зробити, щоб Citcoin став повноцінною мережею для перекладу монет? Зовсім небагато:

  • На етапі створення Account-а реалізувати генерацію приватного / публічного ключа. Приватний ключ повинен зберігатись у користувача account-а, публічний у блокчейні.
  • Зробити переклад монет, у якому ідентифікації користувача використовується не ім'я, а публічний ключ.
  • Шифрувати транзакції, що від користувача на сервер його приватним ключем.

Висновок

Ми реалізували мережу Citcoin з функціями: додати рахунок, отримати баланс, поповнити свій рахунок, перевести монети з одного рахунку на інший. Отже, що нам варто було побудувати?

  • Потрібно вивчити блокчейн взагалі і Hyperledger Fabric зокрема;
  • Навчитися користуватися хмарами IBM чи Amazon;
  • Вивчити мову програмування JS та який-небудь web framework;
  • Якщо якісь дані потрібно зберігати над блокчейні, а окремій базі, то навчитися інтегруватися, наприклад, з PostgreSQL;
  • І останнє за списком, але не за важливістю без знання Linux в сучасному світі нікуди!)

Звичайно, не rocket science, але попітніти доведеться!

Вихідники на GitHub

Вихідники поклав на GitHub. Короткий опис репозиторію:
Каталог «сервер» - Node.js сервер
Каталог «клієнт» - Node.js клієнт
Каталог «blockchain» (значення параметрів та ключі, зрозуміло, неробочі та наведені тільки для прикладу):

  • contract - вихідник смарт-контракту
  • wallet - ключі користувача для використання Hyperledger Fabric API.
  • *.cds - скомпіловані версії смартконтрактів
  • *.json файли — приклади конфігураційних файлів для використання Hyperledger Fabric API

Це лише початок!

Джерело: habr.com

Додати коментар або відгук