У мінулых артыкулах мы разабраліся з тэхналогіямі, на якіх будуюцца блокчейны.Што нам варта блокчэйн пабудаваць?) і кейсамі, якія можна з іх дапамогай рэалізаваць (Што нам варта кейс пабудаваць?). Надышоў час папрацаваць рукамі! Для рэалізацыі пілотаў і PoC (Proof of Concept) я аддаю перавагу выкарыстоўваць аблокі, т.я. да іх ёсць доступ з любой кропкі свету і, часцяком, не трэба марнаваць час на нудную ўсталёўку асяроддзя, т.я. ёсць прадусталяваныя канфігурацыі. Такім чынам, давайце зробім што-небудзь простае, напрыклад, сетку для пераводу манет паміж удзельнікамі і назавем яе сціпла Сitcoin. Для гэтага будзем выкарыстоўваць воблака IBM і ўніверсальны блокчейн Hyperledger Fabric. Для пачатку разбярэмся, чаму Hyperledger Fabric завуць універсальным блокчейном?
Hyperledger Fabric - універсальны блокчейн
Калі казаць увогуле, то ўніверсальная інфармацыйная сістэма гэта:
Набор сервераў і праграмнае ядро, якое выконвае бізнэс логіку;
Інтэрфейсы для ўзаемадзеяння з сістэмай;
Сродкі для рэгістрацыі, аўтэнтыфікацыі і аўтарызацыі прылад /людзей;
База даных, якая захоўвае аператыўныя і архіўныя дадзеныя:
Афіцыйную версію, што такое Hyperledger Fabric можна пачытаць на сайце, а калі сцісла, то Hyperledger Fabric - гэта opensource платформа, якая дазваляе будаваць зачыненыя блокчейны і выконваць адвольныя смарт-кантракты, напісаныя на мовах праграмавання JS і Go. Паглядзім дэталёва на архітэктуру Hyperledger Fabric і пераканаемся, што гэта ўніверсальная сістэма, у якой толькі ёсць спецыфіка па захоўванні і запісы дадзеных. Спецыфіка складаецца ў тым, што дадзеныя, як і ва ўсіх блокчейнах, захоўваюцца ў блоках, якія змяшчаюцца ў блокчейн толькі, калі ўдзельнікі дашлі да кансенсусу і пасля запісу дадзеныя немагчыма неўзаметку выправіць або выдаліць.
Архітэктура Hyperledger Fabric
На схеме прадстаўлена архітэктура Hyperledger Fabric:
Арганізацый - арганізацыі ўтрымліваюць 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
Прыкладанне ўзаемадзейнічае з 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 – гэта сапраўды ўніверсальная сістэма, з дапамогай якой можна:
Рэалізоўваць адвольную бізнес-логіку, выкарыстоўваючы механізм смарт-кантрактаў;
Запісваць і атрымліваць дадзеныя з блокчейн базы дадзеных фармаце JSON;
Прадастаўляць і правяраць доступ да API, выкарыстоўваючы Certificate Authority.
Цяпер, калі мы крыху разабраліся са спецыфікай Hyperledger Fabric, давайце нарэшце зробім што-небудзь карыснае!
Разгортваем блокчэйн
Пастаноўка задачы
Задача - рэалізаваць сетку Citcoin з наступнымі функцыямі: стварыць account, атрымаць баланс, папоўніць рахунак, перавесці манеты з аднаго рахунку на іншы. Намалюем аб'ектную мадэль, якую далей рэалізуем у смарт-кантракце. Такім чынам, у нас будуць account-ы, якія ідэнтыфікуюцца імёнамі (name) і ўтрымоўваюць баланс (balance), і спіс account-ов. Account-ы і спіс account-ов - гэта ў тэрмінах Hyperledger Fabric asset-ы. Адпаведна, у іх ёсць гісторыя і бягучы стан. Паспрабую гэта наглядна намаляваць:
Верхнія фігуры - гэта бягучы стан, якое захоўваецца ў базе "World state". Пад імі фігуры, якія паказваюць гісторыю, якая захоўваецца ў блокчэйне. Бягучы стан asset-ов змяняецца транзакцыямі. Asset змяняецца толькі цалкам, таму ў выніку выкананні транзакцыі ствараецца новы аб'ект, а бягучае значэнне asset-а сыходзіць у гісторыю.
Воблака IBM
Заводзім уліковы запіс у воблаку IBM. Для выкарыстання блокчейн платформы яе трэба апгрэйдзіць да Pay-As-You-Go. Гэты працэс можа быць не хуткім, т.я. IBM запытвае дадатковую інфармацыю і правярае яе ўручную. З дадатнага магу сказаць, што ў IBM нядрэнныя навучальныя матэрыялы, якія дазваляюць разгарнуць Hyperledger Fabric у іх воблаку. Мне спадабаўся наступны цыкл артыкулаў і прыкладаў:
Далей прыведзены скрыншоты Blockchain платформы IBM. Гэта не інструкцыя па стварэнні блокчейна, а проста дэманстрацыя аб'ёму задачы. Такім чынам, для нашых мэт які робіцца адну Organization:
У ёй ствараем ноды: Orderer CA, Org1 CA, Orderer Peer:
Заводзім юзэраў:
Ствараем Channel і завем яго citcoin:
Па сутнасці Channel – гэта блокчейн, таму ён пачынаецца з нулявога блока (Genesis block):
Пішам 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 і інстанцуем яго:
Глядзім транзакцыю на ўстаноўку Smart Contract:
Глядзім падрабязнасці аб нашым Channel:
У выніку атрымліваем наступную схему блокчейн сеткі ў воблаку IBM. Таксама на схеме прысутнічае дэма праграма, запушчаная ў воблаку Amazon на віртуальным серверы (падрабязна пра яе будзе ў наступнай частцы):
Стварэнне 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/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;
Атрымліваць падзеі (але зараз падзеі ніяк не паказаць, таму ў інтэрфейсе для прастаты напісана, што падзеі не падтрымліваюцца);
Лагаваць дзеянні.
Спачатку ініцыялізуем блокчейн:
Далей заводзім свой account, не дробязімся з балансам:
Атрымліваем спіс усіх даступных account-ов:
Выбіраемы адпраўніка і атрымальніка, атрымліваем іх балансы. Калі адпраўнік і атрымальнік адзін і той жа, тое адбудзецца папаўненне яго рахунку:
У логу сочым за выкананнем транзакцый:
Уласна з дэма праграмай на гэтым усё. Далей можна паглядзець нашу транзакцыю ў блокчэйне:
І агульны спіс транзакцый:
На гэтым мы паспяхова завяршылі рэалізацыю 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