Блокчейн: какво ни струва да построим PoC?

Очите се страхуват, а ръцете се сърбят!

В предишните статии разгледахме технологиите, на които се основават блокчейните (Какво ни струва да построим блокчейн?) и казусите, които могат да бъдат реализирани с тяхна помощ (Какво ни струва да изградим казус?). Време е да поработим с ръце! За реализиране на пилоти и PoC (Proof of Concept) предпочитам да използвам облаците, тъй като те са достъпни от всяка точка на света и често не е нужно да губя време в скучна инсталация на среда, тъй като има предварително инсталирани конфигурации. И така, нека направим нещо просто, например, мрежа за прехвърляне на монети между участниците и нека я наречем скромно Сitcoin. За целта ще използваме облака на IBM и универсалния блокчейн Hyperledger Fabric. Първо, нека разгледаме защо Hyperledger Fabric се нарича универсален блокчейн?

Блокчейн: какво ни струва да построим PoC?

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

Ако говорим общо, универсалната информационна система е:

  • Набор от сървъри и софтуерно ядро, изпълняващо бизнес логиката;
  • Интерфейси за взаимодействие с системата;
  • Средства за регистрация, удостоверяване и авторизация на устройства/хора;
  • База данни, която съхранява оперативни и архивни данни:

Блокчейн: какво ни струва да построим PoC?

Официалната версия за това какво представлява Hyperledger Fabric може да бъде прочетена на сайта, а ако да обобщим, Hyperledger Fabric — това е платформа с отворен код, позволяваща изграждането на затворени блокчейни и извършването на произволни смарт-контракти, написани на програмиращи езици JS и Go. Нека разгледаме подробно архитектурата на Hyperledger Fabric и да се уверим, че това е универсална система, в която само спецификата е свързана със съхранението и записването на данни. Спецификата се състои в това, че данните, както и в повечето блокчейни, се съхраняват в блокове, които се добавят в блокчейна само ако участниците постигнат съгласие, и след записа данните не могат да бъдат незабелязано променени или изтрити.

Архитектура на Hyperledger Fabric

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

Блокчейн: какво ни струва да построим PoC?

Organizations — организациите съдържат peer-ове, т.е. блокчейнът съществува благодарение на подкрепата на организациите. Различни организации могат да входят в един channel.

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

Membership Services Provider (MSP) — това е CA (Certificate Authority) за издаване на идентичности и присвояване на роли. За създаването на нода е необходимо да се взаимодействува с 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“. Данните се попълват произволно в зависимост от бизнес задачите. Няма задължителни полета, единствената препоръка е — активите да имат собственик и да представляват стойност.

Ledger — се състои от блокчейна „Blockchain“ и базата данни „World state“, в която се съхранява текущото състояние на активите. World state използва LevelDB или CouchDB.

Смарт контракт — с помощта на смарт-контрактите се реализира бизнес логиката на системата. В Hyperledger Fabric смарт-контрактите се наричат chaincode. С помощта на chaincode се задават активите и транзакциите над тях. Технически казано, смарт-контрактите са програмни модули, реализирани на програмни езици JS или Go.

Политика за одобрение — за всеки chaincode може да се зададат политики колко и от кого е необходимо да се очакват одобрения за транзакция. Ако политиката не е зададена, по подразбиране се използва: „транзакцията трябва да бъде потвърдена от всеки член (member) на която и да е организация в channel“. Примери за политики:

  • Транзакцията трябва да бъде потвърдена от всеки администратор на организацията;
  • Всеки член (member) или клиент на организацията трябва да потвърди;
  • Всеки peer на организацията трябва да потвърди.

Ordering service — опакова транзакции в блокове и ги изпраща на peer-ите в channel. Гарантира доставката на съобщения до всички peer-ове в мрежата. За индустриални системи се използва меседж брокер Kafka, за разработка и тестване Solo.

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 for Permissioned Blockchains.

Така че, Hyperledger Fabric е наистина универсална система, с помощта на която можете да:

  • Реализирате произволна бизнес логика, използвайки механизма на смарт-контрактите;
  • Записване и получаване на данни от блокчейн база данни в формат JSON;
  • Предоставяне и проверка на достъп до API, използвайки Оставителна Агенция.

Сега, след като малко се запознахме със спецификата на Hyperledger Fabric, нека най-после направим нещо полезно!

Разгръщане на блокчейн

Формулиране на задачата

Задачата е да реализираме мрежа Citcoin с следните функции: създаване на акаунт, получаване на баланс, попълване на сметката, преместване на монети от един акаунт на друг. Ще нарисуваме обектна модел, който след това ще реализираме в смарт-контракт. И така, ще имаме акаунти, които се идентифицират с имената (name) и съдържат баланс (balance), и списък с акаунти. Акаунтите и списъкът с акаунти са, в термини на Hyperledger Fabric, активи. Съответно, те имат история и текущо състояние. Ще опитам да го нарисувам визуално:

Блокчейн: какво ни струва да построим PoC?

Горните фигури представляват текущото състояние, което се съхранява в базата "World state". Под тях фигури, показващи историята, която се съхранява в блокчейна. Текущото състояние на активите се променя чрез транзакции. Активът се променя само изцяло, затова в резултат на изпълнението на транзакцията се създава нов обект, а текущата стойност на актива отива в историята.

IBM Cloud

Създаване на акаунт в IBM Cloud. За да използвате блокчейн платформата, трябва да я обновите до Pay-As-You-Go. Този процес може да не бъде бърз, тъй като IBM изисква допълнителна информация и я проверява ръчно. От позитивна страна мога да кажа, че IBM разполага с добри учебни материали, които позволяват разгръщане на Hyperledger Fabric в тяхната облачна среда. Хареса ми следният цикъл статии и примери:

По-долу са представени скрийншотове от облачната платформа на IBM. Това не е инструкция за създаване на блокчейн, а просто демонстрация на обема на задачата. И така, за нашите цели създаваме една Организация:

Блокчейн: какво ни струва да построим 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 се извиква веднъж при инстанцирането на смарт договора. Всъщност, тя се извиква не веднъж, а всеки път, когато се променя версията на смарт договора. Затова инициализацията на списъка с празен масив е лоша идея, тъй като сега при смяна на версията на смарт договора ще загубим текущия списък. Но нищо, все пак само уча).
  • Акаунтите и списъкът с акаунти (accounts) са JSON структури от данни. За манипулиране на данните се използва JS.
  • Текущото значение на asset-а може да се получи чрез извикване на функцията getState, а обновяването става чрез putState.
  • При създаването на акаунт се извиква функцията AddAccount, в която се извършва сравнение с максималния брой акаунти в блокчейна (maxAccounts = 5). Тук има проблем (забелязахте ли?), който води до безкраен растеж на броя на акаунтите. Такива грешки трябва да се избягват)

След това зареждаме смарт договора в 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 using Vue.js & Express.js. В резултат се получи клиент-сървърно приложение с познат графичен интерфейс в стил Material Design от Google. REST API между клиента и сървъра се състои от няколко извиквания:

  • HyperledgerDemo/v1/init — инициализиране на блокчейна;
  • HyperledgerDemo/v1/accounts/list — получаване на списък с всички акаунти;
  • HyperledgerDemo/v1/account?name=Bob&balance=100 — създаване на акаунт Bob;
  • HyperledgerDemo/v1/info?account=Bob — получаване на информация за акаунта Bob;
  • 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 на борсата все още не се котират) В статията ще поставя екранни снимки на демото, за да е ясна логиката на работа. Демонстрационното приложение може:

  • Да инициализира блокчейна;
  • Да създаде акаунт (но в момента не може да се създаде нов акаунт, тъй като в блокчейна е достигнато максималното число на акаунти, указано в смарт договора);
  • Да получи списък с акаунти;
  • Да прехвърля монети citcoin между Alice, Bob и Alex;
  • Да получава събития (но в момента събитията не могат да се покажат, затова в интерфейса за опростеност е написано, че събитията не се поддържат);
  • Да логва действия.

Първо инициализираме блокчейна:

Блокчейн: какво ни струва да построим PoC?

След това създаваме свой акаунт, без да пестим с баланса:

Блокчейн: какво ни струва да построим PoC?

Получаваме списък на всички налични акаунти:

Блокчейн: какво ни струва да построим PoC?

Избираме подател и получател, получаваме техните баланси. Ако подателят и получателят са един и същ, ще се попълни неговата сметка:

Блокчейн: какво ни струва да построим PoC?

В логовете следим изпълнението на транзакциите:

Блокчейн: какво ни струва да построим PoC?

С демо приложението на това е всичко. След това можем да видим нашата транзакция в блокчейна:

Блокчейн: какво ни струва да построим PoC?

И общ списък с транзакции:

Блокчейн: какво ни струва да построим PoC?

С успешно завършване на PoC по създаването на мрежата Citcoin, какво още трябва да направим, за да стане Citcoin пълноценна мрежа за прехвърляне на монети? Съвсем малко:

  • На етапа на създаване на акаунта да реализираме генерирането на частен / публичен ключ. Частният ключ трябва да се съхранява от потребителя на акаунта, а публичният в блокчейна.
  • Да се направи прехвърляне на монети, при което за идентификация на потребителя се използва не името, а публичният ключ.
  • Да се криптират транзакциите, които отиват от потребителя към сървъра с неговия частен ключ.

Заключение

Ние реализирахме мрежата Citcoin с функции: добавяне на акаунт, получаване на баланс, попълване на сметка, прехвърляне на монети от една сметка на друга. Така че какво ни струва разработването на PoC?

  • Трябва да проучим блокчейна изобщо и Hyperledger Fabric в частност;
  • Да се научим да ползваме облаците на IBM или Amazon;
  • Да овладеем езика за програмиране JS и някакъв уеб фреймуърк;
  • Ако трябва да съхранявате данни извън блокчейна, а в отделна база, научете се да интегрирате, например, с PostgreSQL;
  • И на последно място, но не по важност — без познания по Linux в съвременния свят не можете да мърдате!)

Разбира се, не е нещо сложно, но ще трябва да поработите!

Изходните файлове на GitHub

Изходните файлове са поставени на GitHub. Кратко описание на хранилището:
Каталог «сървер» — Node.js сървър
Каталог «client» — Node.js клиент
Каталог «blockchain» (стойностите на параметрите и ключовете, разбира се, неработещи и посочени само за пример):

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

Вече сме на самото начало!

Източник: habr.com

Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри 🔥 Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри | ProHoster