Blockchain: PoC apakah yang perlu kita bina?

Глаза боятся, а руки чешутся!

В прошлых статьях мы разобрались с технологиями, на которых строятся блокчейны (Apakah yang perlu kita bina blockchain?) и кейсами, которые можно с их помощью реализовать (Что нам стоит кейс построить?). Настало время поработать руками! Для реализации пилотов и PoC (Proof of Concept) я предпочитаю использовать облака, т.к. к ним есть доступ из любой точки мира и, зачастую, не надо тратить время на нудную установку окружения, т.к. есть предустановленные конфигурации. Итак, давайте сделаем что-нибудь простое, например, сеть для перевода монет между участниками и назовем ее скромно Сitcoin. Для этого будем использовать облако IBM и универсальный блокчейн Hyperledger Fabric. Для начала разберемся, почему Hyperledger Fabric называют универсальным блокчейном?

Blockchain: PoC apakah yang perlu kita bina?

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

Если говорить в общем, то универсальная информационная система это:

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

Blockchain: PoC apakah yang perlu kita bina?

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

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

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

Blockchain: PoC apakah yang perlu kita bina?

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

Saluran — логическая структура, объединяющая 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 протокол.

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

lejar — состоит из блокчейна «Blockchain» и базы данных «Word state», в которой хранится текущее состояние asset-ов. World state использует LevelDB или CouchDB.

Kontrak pintar — с помощью смарт-контрактов реализуется бизнес логика системы. В Hyperledger Fabric смарт-контракты называются chaincode. С помощью chaincode задаются asset-ы и транзакции над ними. Если говорить техническим языком, то смарт-контракты — это программные модули, реализованные на языках программирования JS или Go.

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

  • Транзакцию должен подтвердить любой администратор организации;
  • Должен подтвердить любой член (member) или клиент организации;
  • Должен подтвердить любой peer организации.

Ordering service — упаковывает транзакции в блоки и отправляет peer-ам в channel. Гарантирует доставку сообщений всем peer-ам в сети. Для промышленных систем используется брокер сообщений Kafka, для разработки и тестирования Pelancong.

CallFlow

Blockchain: PoC apakah yang perlu kita bina?

  • Приложение взаимодействует с 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, используя Certificate Authority.

Теперь, когда мы немного разобрались со спецификой Hyperledger Fabric, давайте наконец сделаем что-нибудь полезное!

Разворачиваем блокчейн

Pernyataan masalah

Задача — реализовать сеть Citcoin со следующими функциями: создать account, получить баланс, пополнить счет, перевести монеты с одного счета на другой. Нарисуем объектную модель, которую далее реализуем в смарт-контракте. Итак, у нас будут account-ы, которые идентифицируются именами (name) и содержат баланс (balance), и список account-ов. Account-ы и список account-ов — это в терминах Hyperledger Fabric asset-ы. Соответственно, у них есть история и текущее состояние. Попробую это наглядно нарисовать:

Blockchain: PoC apakah yang perlu kita bina?

Верхние фигуры — это текущее состояние, которое хранится в базе «World state». Под ними фигуры, показывающие историю, которая хранится в блокчейне. Текущее состояние asset-ов изменяется транзакциями. Asset изменяется только целиком, поэтому в результате выполнения транзакции создается новый объект, а текущее значение asset-а уходит в историю.

Облако IBM

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

Далее приведены скриншоты Blockchain платформы IBM. Это не инструкция по созданию блокчейна, а просто демонстрация объема задачи. Итак, для наших целей делаем одну Organization:

Blockchain: PoC apakah yang perlu kita bina?

В ней создаем ноды: Orderer CA, Org1 CA, Orderer Peer:

Blockchain: PoC apakah yang perlu kita bina?

Заводим юзеров:

Blockchain: PoC apakah yang perlu kita bina?

Создаем Channel и называем его citcoin:

Blockchain: PoC apakah yang perlu kita bina?

По сути Channel — это блокчейн, поэтому он начинается с нулевого блока (Genesis block):

Blockchain: PoC apakah yang perlu kita bina?

Пишем 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 и инстанциируем его:

Blockchain: PoC apakah yang perlu kita bina?

Смотрим транзакцию на установку Smart Contract:

Blockchain: PoC apakah yang perlu kita bina?

Смотрим подробности о нашем Channel:

Blockchain: PoC apakah yang perlu kita bina?

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

Blockchain: PoC apakah yang perlu kita bina?

Создание GUI для вызовов Hyperledger Fabric API

У Hyperledger Fabric есть API, которое может использоваться для:

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

Pembangunan aplikasi

В нашей демо программе будем использовать 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 — получить список всех 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 c примерами положил на сайт «Postman» — широко известной программы для тестирования HTTP API.

Демо приложение в облаке Amazon

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

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

Сначала инициализируем блокчейн:

Blockchain: PoC apakah yang perlu kita bina?

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

Blockchain: PoC apakah yang perlu kita bina?

Получаем список всех доступных account-ов:

Blockchain: PoC apakah yang perlu kita bina?

Выбираем отправителя и получателя, получаем их балансы. Если отправитель и получатель один и тот же, то произойдет пополнение его счета:

Blockchain: PoC apakah yang perlu kita bina?

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

Blockchain: PoC apakah yang perlu kita bina?

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

Blockchain: PoC apakah yang perlu kita bina?

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

Blockchain: PoC apakah yang perlu kita bina?

На этом мы успешно завершили реализацию PoC по созданию сети Citcoin. Что нужно еще сделать, чтобы Citcoin стал полноценной сетью для перевода монет? Совсем немного:

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

Kesimpulan

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

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

Конечно, не rocket science, но попотеть придется!

Sumber di GitHub

Исходники положил на GitHub. Краткое описание репозитория:
Katalog «server» — Node.js сервер
Katalog «pelanggan» — Node.js клиент
Katalog «blockchain» (значения параметров и ключи, разумеется, нерабочие и приведены только для примера):

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

Ia hanya permulaan!

Sumber: www.habr.com

Tambah komen