区块链:我们应该构建什么 PoC?

你的眼睛害怕,你的手发痒!

在之前的文章中,我们讨论了构建区块链的技术(我们应该构建什么区块链?)以及在他们的帮助下可以实施的案例(我们为什么要建立案例?)。 是时候用你的双手干活了! 为了实施试点和 PoC(概念验证),我更喜欢使用云,因为…… 可以从世界任何地方访问它们,并且通常无需在繁琐的环境安装上浪费时间,因为有预设配置。 因此,让我们做一些简单的事情,例如,一个用于在参与者之间转移硬币的网络,我们谦虚地称之为比特币。 为此,我们将使用 IBM 云和通用区块链 Hyperledger Fabric。 首先我们先弄清楚为什么Hyperledger Fabric被称为通用区块链?

区块链:我们应该构建什么 PoC?

Hyperledger Fabric - 通用区块链

一般来说,通用信息系统是:

  • 一组服务器和执行业务逻辑的软件核心;
  • 与系统交互的接口;
  • 用于设备/人员的注册、身份验证和授权的工具;
  • 存储操作和归档数据的数据库:

区块链:我们应该构建什么 PoC?

Hyperledger Fabric 的官方版本可以阅读: 在线简而言之,Hyperledger Fabric 是一个开源平台,允许您构建私有区块链并执行用 JS 和 Go 编程语言编写的任意智能合约。 让我们详细了解一下 Hyperledger Fabric 的架构,并确保这是一个通用系统,仅具有存储和记录数据的细节。 特殊之处在于,与所有区块链一样,数据只有在参与者达成共识时才会存储在放置在区块链上的区块中,并且记录后的数据无法悄悄更正或删除。

超级账本结构架构

该图显示了 Hyperledger Fabric 架构:

区块链:我们应该构建什么 PoC?

组织 — 组织包含同行,即区块链的存在是由于组织的支持。 不同的组织可以属于同一渠道。

渠道 ——将同伴联合成组的逻辑结构,即区块链已指定。 Hyperledger Fabric 可以同时处理具有不同业务逻辑的多个区块链。

会员服务提供商 (MSP) 是用于颁发身份和分配角色的CA(证书颁发机构)。 创建节点需要与MSP交互。

对等节点 — 验证交易、存储区块链、执行智能合约并与应用程序交互。 Peer有一个身份(数字证书),由MSP颁发。 与所有节点拥有平等权利的比特币或以太坊网络不同,Hyperledger Fabric 中的节点扮演着不同的角色:

  • 同行也许 认可同行 (EP)并执行智能合约。
  • 承诺同行 (CP) - 只在区块链中保存数据并更新“世界状态”。
  • 锚点同行 (AP) - 如果多个组织参与区块链,则锚点用于它们之间的通信。 每个组织必须有一个或多个锚点。 使用AP,一个组织中的任何对等点都可以获取其他组织中所有对等点的信息。 用于AP之间的信息同步 八卦协议.
  • 领导同行 - 如果一个组织有多个对等点,那么只有该对等点的领导者将从排序服务接收块并将其提供给其余的对等点。 领导者可以静态指定,也可以由组织中的同事动态选择。 八卦协议还用于同步有关领导者的信息。

办公室文员: — 具有价值并存储在区块链上的实体。 更具体地说,这是 JSON 格式的键值数据。 这些数据记录在区块链中。 它们有存储在区块链中的历史记录和存储在“世界状态”数据库中的当前状态。 根据业务任务任意填充数据结构。 没有必填字段,唯一的建议是资产必须有所有者并且有价值。

莱杰 — 由区块链和Word状态数据库组成,存储资产的当前状态。 世界状态使用 LevelDB 或 CouchDB。

聪明的合同 — 使用智能合约,实现系统的业务逻辑。 在 Hyperledger Fabric 中,智能合约称为链码。 使用链码,可以指定资产和交易。 从技术角度来说,智能合约是用 JS 或 Go 编程语言实现的软件模块。

背书政策 - 对于每个链码,您可以设置一项策略,确定一笔交易应该有多少确认以及来自谁。 如果未设置策略,则默认为:“交易必须由通道中任何组织的任何成员确认”。 政策示例:

  • 交易必须得到组织任何管理员的批准;
  • 必须得到该组织的任何成员或客户的确认;
  • 必须得到任何同行组织的确认。

订购服务 — 将交易打包成块并将它们发送到通道中的对等点。 保证将消息传送到网络上的所有对等点。 用于工业系统 卡夫卡消息代理,用于开发和测试 独奏.

呼叫流程

区块链:我们应该构建什么 PoC?

  • 应用程序使用 Go、Node.js 或 Java SDK 与 Hyperledger Fabric 进行通信;
  • 客户端创建一个 tx 交易并将其发送给背书节点;
  • Peer验证客户端的签名,完成交易,并将背书签名发送回客户端。 Chaincode仅在背书节点上执行,并将其执行结果发送给所有节点。 这种工作算法称为 PBFT(实用拜占庭容错)共识。 不同于 经典拜占庭容错理论 事实上,消息的发送和确认不是来自所有参与者,而是来自某个特定的参与者;
  • 客户端收到背书策略对应的响应数量后,将交易发送到Ordering服务;
  • 排序服务生成一个块并将其发送给所有提交节点。 排序服务确保了区块的顺序记录,从而消除了所谓的账本分叉(参见“叉子”部分);
  • 节点收到一个块,再次检查背书策略,将该块写入区块链并更改“世界状态”数据库中的状态。

那些。 这导致节点之间的角色划分。 这确保了区块链的可扩展性和安全性:

  • 智能合约(链码)执行对等节点的背书。 这确保了智能合约的机密性,因为它不是由所有参与者存储,而是仅由认可的节点存储。
  • 订购应该很快就能完成。 这是通过以下事实确保的:排序仅形成一个块并将其发送到一组固定的领导节点。
  • 提交节点只存储区块链——它们可以有很多,并且不需要大量的电力和即时操作。

有关 Hyperledger Fabric 架构解决方案的更多详细信息以及为什么它以这种方式工作而不是其他方式可以在此处找到: 建筑起源 或在这里: Hyperledger Fabric:用于许可区块链的分布式操作系统.

因此,Hyperledger Fabric 是一个真正的通用系统,您可以使用它:

  • 利用智能合约机制实现任意业务逻辑;
  • 以JSON格式记录并接收来自区块链数据库的数据;
  • 使用证书颁发机构授予和验证 API 访问权限。

现在我们已经了解了一些 Hyperledger Fabric 的细节,让我们最终做一些有用的事情!

部署区块链

制定问题

任务是实现 Citcoin 网络,具有以下功能:创建帐户、获取余额、为帐户充值、将硬币从一个帐户转移到另一个帐户。 让我们画一个对象模型,我们将在智能合约中进一步实现它。 因此,我们将拥有按名称标识并包含余额和帐户列表的帐户。 账户和账户列表是 Hyperledger Fabric 资产方面的。 因此,它们有历史和现状。 我会试着把它画清楚:

区块链:我们应该构建什么 PoC?

最上面的数字是当前状态,存储在“世界状态”数据库中。 它们下面是显示存储在区块链中的历史的数字。 资产的当前状态因交易而改变。 资产仅作为整体发生变化,因此交易的结果是创建一个新对象,并且资产的当前价值进入历史。

IBM 云

我们创建一个帐户 IBM云。 要使用区块链平台,必须升级为按量付费。 这个过程可能不会很快,因为…… IBM 请求附加信息并手动验证。 从积极的方面来说,我可以说 IBM 拥有很好的培训材料,可让您在其云中部署 Hyperledger Fabric。 我喜欢以下系列文章和示例:

以下是IBM区块链平台的屏幕截图。 这不是关于如何创建区块链的说明,而只是任务范围的演示。 因此,为了我们的目的,我们建立一个组织:

区块链:我们应该构建什么 PoC?

我们在其中创建节点:Orderer CA、Org1 CA、Orderer Peer:

区块链:我们应该构建什么 PoC?

我们创建用户:

区块链:我们应该构建什么 PoC?

创建一个通道并将其命名为 citcoin:

区块链:我们应该构建什么 PoC?

Channel 本质上是一个区块链,所以它从区块零(创世区块)开始:

区块链:我们应该构建什么 PoC?

编写智能合约

/*
 * 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;

直观上,这里一切都应该很清楚:

  • 演示程序将使用 Hyperledger Fabric API 调用多个函数(AddAccount、GetAccounts、SendFrom、GetBalance、RefillBalance)。
  • SendFrom 和 RefillBalance 函数生成演示程序将接收的事件。
  • 实例化函数在智能合约实例化时被调用一次。 事实上,它不仅被调用一次,而是每次智能合约版本发生变化时被调用。 因此,用空数组初始化列表是一个坏主意,因为现在,当我们更改智能合约的版本时,我们将丢失当前列表。 不过没关系,我只是在学习)。
  • 帐户和帐户列表是 JSON 数据结构。 JS 用于数据操作。
  • 您可以使用 getState 函数调用获取资产的当前值,并使用 putState 更新它。
  • 创建账户时,会调用AddAccount函数,比较区块链中的最大账户数(maxAccounts = 5)。 这里有一个门框(你注意到了吗?),这导致帐户数量无休止地增加。 应避免此类错误)

接下来,我们将智能合约加载到 Channel 中并实例化它:

区块链:我们应该构建什么 PoC?

我们来看一下安装智能合约的交易:

区块链:我们应该构建什么 PoC?

让我们看看有关我们频道的详细信息:

区块链:我们应该构建什么 PoC?

结果,我们得到了 IBM 云中的区块链网络的下图。 该图还显示了在亚马逊云虚拟服务器上运行的演示程序(下一节将详细介绍):

区块链:我们应该构建什么 PoC?

为 Hyperledger Fabric API 调用创建 GUI

Hyperledger Fabric 有一个 API,可用于:

  • 创建频道;
  • 对等通道的连接;
  • 通道中智能合约的安装和实例化;
  • 调用交易;
  • 请求区块链上的信息。

应用开发

在我们的演示程序中,我们将仅使用 API 来调用交易和请求信息,因为我们已经使用 IBM 区块链平台完成了剩余步骤。 我们使用标准技术堆栈编写 GUI:Express.js + Vue.js + Node.js。 您可以撰写一篇单独的文章来介绍如何开始创建现代 Web 应用程序。 在这里我留下我最喜欢的系列讲座的链接: 使用 Vue.js 和 Express.js 的全栈 Web 应用程序。 结果是一个客户端-服务器应用程序,具有熟悉的 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 的描述以及示例包含在 邮递员网站 - 一个著名的测试 HTTP API 的程序。

亚马逊云中的演示应用程序

我将应用程序上传到亚马逊是因为...... IBM 仍然无法升级我的帐户并允许我创建虚拟服务器。 如何将樱桃添加到域: www.citcoin.info。 我会让服务器打开一段时间,然后将其关闭,因为...... 租金正在滴水,而 citcoin 硬币尚未在证券交易所上市)我在文章中包含了演示的屏幕截图,以便工作的逻辑清晰。 该演示应用程序可以:

  • 初始化区块链;
  • 创建账户(但现在无法创建新账户,因为区块链中已达到智能合约中指定的最大账户数);
  • 接收账户列表;
  • 在 Alice、Bob 和 Alex 之间转移 citcoin 币;
  • 接收事件(但是现在没有办法展示事件,所以为了简单起见,接口上说不支持事件);
  • 记录操作。

首先我们初始化区块链:

区块链:我们应该构建什么 PoC?

接下来,我们创建我们的帐户,不要浪费时间处理余额:

区块链:我们应该构建什么 PoC?

我们得到所有可用帐户的列表:

区块链:我们应该构建什么 PoC?

我们选择发件人和收件人,并获取他们的余额。 如果发件人和收件人相同,则他的帐户将被充值:

区块链:我们应该构建什么 PoC?

在日志中我们监控事务的执行情况:

区块链:我们应该构建什么 PoC?

实际上,演示程序就是这样。 下面你可以看到我们在区块链中的交易:

区块链:我们应该构建什么 PoC?

以及交易的一般列表:

区块链:我们应该构建什么 PoC?

至此,我们成功完成了创建 Citcoin 网络的 PoC 实施。 要使 Citcoin 成为一个成熟的代币转移网络,还需要做什么? 很少:

  • 在账户创建阶段,实现私钥/公钥的生成。 私钥必须与账户用户一起存储,公钥必须存储在区块链中。
  • 进行硬币转账,其中使用公钥而不是姓名来识别用户。
  • 使用用户的私钥加密从用户到服务器的交易。

结论

我们已经实现了 Citcoin 网络,具有以下功能:添加帐户、获取余额、为帐户充值、将硬币从一个帐户转移到另一个帐户。 那么,我们构建 PoC 的成本是多少?

  • 您需要总体学习区块链,特别是 Hyperledger Fabric;
  • 学习使用IBM或亚马逊云;
  • 学习JS编程语言和一些Web框架;
  • 如果某些数据不需要存储在区块链中,而是存储在单独的数据库中,那么学习集成,例如与 PostgreSQL 集成;
  • 最后但并非最不重要的一点是——如果不了解 Linux,你就无法生活在现代世界!)

当然,这不是火箭科学,但你必须努力工作!

GitHub 上的资源

来源已放出 GitHub上。 存储库的简要说明:
目录 ”服务器» — Node.js 服务器
目录 ”客户» — Node.js 客户端
目录 ”blockchain“(当然,参数值和键是不起作用的,仅作为示例给出):

  • Contract——智能合约源代码
  • 钱包 — 使用 Hyperledger Fabric API 的用户密钥。
  • *.cds - 智能合约的编译版本
  • *.json 文件 - 使用 Hyperledger Fabric API 的配置文件示例

这只是开始!

来源: habr.com

添加评论