Блокчэйн: што нам варта 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 арганізацыі.

Ordering service - Пакуе транзакцыі ў блокі і адпраўляе 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: Distributed Operating System for Permissioned Blockchains.

Такім чынам, 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 пабудаваць?

Атрымліваем спіс усіх даступных account-ов:

Блокчэйн: што нам варта PoC пабудаваць?

Выбіраемы адпраўніка і атрымальніка, атрымліваем іх балансы. Калі адпраўнік і атрымальнік адзін і той жа, тое адбудзецца папаўненне яго рахунку:

Блокчэйн: што нам варта PoC пабудаваць?

У логу сочым за выкананнем транзакцый:

Блокчэйн: што нам варта PoC пабудаваць?

Уласна з дэма праграмай на гэтым усё. Далей можна паглядзець нашу транзакцыю ў блокчэйне:

Блокчэйн: што нам варта PoC пабудаваць?

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

Блокчэйн: што нам варта PoC пабудаваць?

На гэтым мы паспяхова завяршылі рэалізацыю PoC па стварэнню сеткі Citcoin. Што яшчэ трэба зрабіць, каб Citcoin стаў паўнавартаснай сеткай для перакладу манет? Зусім няшмат:

  • На этапе стварэння account-а рэалізаваць генерацыю прыватнага / публічнага ключа. Прыватны ключ павінен захоўваецца ў карыстача account-а, публічны ў блокчейне.
  • Зрабіць пераклад манет, у якім для ідэнтыфікацыі карыстальніка выкарыстоўваецца не імя, а публічны ключ.
  • Шыфраваць транзакцыі, якія ідуць ад карыстальніка на сервер яго прыватным ключом.

Заключэнне

Мы рэалізавалі сетку Citcoin з функцыямі: дадаць account, атрымаць баланс, папоўніць свой рахунак, перавесці манеты з аднаго рахунку на іншы. Такім чынам, што нам каштавала PoC пабудаваць?

  • Трэба вывучыць блокчейн наогул і 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

It's only the beginning!

Крыніца: habr.com

Дадаць каментар