Hyperledger Fabric C#链码开发指南
本教程介绍如何使用.NET Core开发一个简单的Hyperledger Fabric链码,用于进行基本的资产管理和转账操作。如果你是一个熟练的.NET/C#开发人员,由于种种原因需要使用Hyperledger Fabric作为区块链平台,那么除了转身投入Java/Node.js/Go的怀抱之外,这也提供了另外一种选择。
相关教程推荐:
1、Fabric链码.NET Core开发包
首先创建一个.NET Core项目,在命令行执行如下命令添加对Fabric链码.NET Core开发包的依赖:
dotnet add package Thinktecture.HyperledgerFabric.Chaincode \ --version 1.0.0-prerelease.74
Thinktecture.HyperledgerFabric.Chaincode
链码开发包提供了两种不同的方法来开发Hyperledger Fabric链码。
- 使用底层API:通过实现IChaincode接口来开发超级账本Fabric链码是一种比较底层的途径,
好处是对链码实现的掌控程度最大 - 使用上层API:通过继承ContractBase类来开发超级账本Fabric链码会更容易一些,但
代价就是丧失部分灵活性
在这个教程中,我们将通过IChaincode接口这一偏底层的途径来实现Fabric链码。如果你对ContractBase继承实现更有兴趣,请等待我们下一篇教程!
2、Hyperledger Fabric链码开发
创建一个类AssetHolding来实现IChaincode接口:
public class AssetHolding : IChaincode { public async Task<Response> Init(IChaincodeStub stub) { throw new NotImplementedException(); } public Task<Response> Invoke(IChaincodeStub stub) { throw new NotImplementedException(); } }
Ichaincode接口的两个方法Init和Invoke都需要实现。
Init()
方法将在Fabric链码初始化和升级时被调用,可以使用这个方法来初始化资产库,例如,
设置一些默认值。
Invoke()
方法将在Fabric链码的整个生命周期中被Peer节点调用,可以使用这个方法来处理可能影响资产状态的业务交易逻辑。
两个方法都有一个类型为IChaincodeStub的参数,该接口封装了Fabric链码实现和Fabric对等节点之间的通信API。例如,使用这个接口可以对资产库进行CRUD操作。
3、实现Hyperledger Fabric链码的Init()方法
如前所述,我们的Fabric链码需要初始化两个账户名并设置账户初始余额。基本上应用在调用Fabric链码时会传递一个数组参数给链码,例如["accountA", "100", "accountB", "50"]
,为了在Fabric链码初始化时得到这个参数,我们可以使用stub.GetFunctionAndParameters()
,该方法的结果时一个List<String>
类型的参数对象,其中包含了所有参数。
public async Task<Response> Init(IChaincodeStub stub) { var functionAndParameters = stub.GetFunctionAndParameters(); var args = functionAndParameters.Parameters; args.AssertCount(4); }
我们使用Parameters.AssertCount(4)
来快速检查参数的数量是否符合要求,如果数量不是4就会抛出异常,从而终止链码的执行。
下一步是将其中两个参数转换为整型。我们可以自己使用int.TryParse()
来实现这一步,或者使用args.TryGet<int>()
方法。现在我们来完善Init()
的实现:
public async Task<Response> Init(IChaincodeStub stub) { var functionAndParameters = stub.GetFunctionAndParameters(); var args = functionAndParameters.Parameters; args.AssertCount(4); if (!args.TryGet<int>(1, out var aValue) || !args.TryGet<int>(3, out var bValue)) { return Shim.Error("Expecting integer value for asset holding"); } }
在上面的代码中我们尝试将第2个和第4个参数转换为整数,如果某个转换失败,我们就返回Shim.Error()
通知Fabric链码的调用方初始化失败。
如果转换成功,我们就可以使用stub.PutState()
将转换结果存入链码资产库:
public async Task<Response> Init(IChaincodeStub stub) { var functionAndParameters = stub.GetFunctionAndParameters(); var args = functionAndParameters.Parameters; args.AssertCount(4); if (!args.TryGet<int>(1, out var aValue) || !args.TryGet<int>(3, out var bValue)) { return Shim.Error("Expecting integer value for asset holding"); } if (await stub.PutState(args.Get<string>(0), aValue) && await stub.PutState(args.Get<string>(2), bValue)) { return Shim.Success(); } return Shim.Error("Error during Chaincode init!"); }
在上面的代码中,我们使用PutState()
来更新Faric链码资产库中账户的初始值。如果一切顺利,我们就可以使用Shim.Success()
向Fabric链码调用者返回成功响应,否则返回一个错误。
4、实现Hyperledger Fabric链码的Invoke()方法
现在我们进入Invoke方法的实现。Invoke()方法在链码整个声明周期中被Fabric的对等节点调用来处理业务逻辑,该方法的参数和Init()一样,因此我们也需要使用该参数接口的GetFunctionAndParameters()
方法来获取Fabric链码的调用参数。
public Task<Response> Invoke(IChaincodeStub stub) { var functionAndParameters = stub.GetFunctionAndParamaters(); if (functionAndParameters.Function == 'myfn1') { return MyFn1(stub, functionAndParameters.Parameters); } if (functionAndParameters.Function == 'myfn2') { return MyFn2(stub, functionAndParameters.Parameters); } // Rinse and repeat for every function }
依赖于你的具体设计,在Invoke实现中可能需要很多if分支或switch分支,因此Faric链码.NET开发包提供了一个辅助类ChaincodeInvocationMap
来让代码更干净:
private readonly ChaincodeInvocationMap _invocationMap; public AssetHolding() { _invocationMap = new ChaincodeInvocationMap { {"Transfer", InternalTransfer}, {"Query", InternalQuery} }; } public Task<Response> Invoke(IChaincodeStub stub) { return _invocationMap.Invoke(stub); }
需要指出的是,我们还没有实现InternalTransfer()
和InternalQuery()
方法。
下面实现InternalTransfer()
方法:
private async Task<ByteString> InternalTransfer(IChaincodeStub stub, Parameters args) { args.AssertCount(3); var accountA = args.Get<string>(0); var accountB = args.Get<string>(1); if (string.IsNullOrEmpty(accountA) || string.IsNullOrEmpty(accountB)) throw new Exception("Asset holding must not be empty"); var aValue = await stub.TryGetState<int>(accountA); if (!aValue.HasValue) throw new Exception("Failed to get state of asset holder A"); var bValue = await stub.TryGetState<int>(accountB); if (!bValue.HasValue) throw new Exception("Failed to get state of asset holder B"); if (!args.TryGet<int>(2, out var amount)) throw new Exception("Expecting integer value for amount to be transferred"); aValue -= amount; bValue += amount; await stub.PutState(accountA, aValue); await stub.PutState(accountB, bValue); return ByteString.Empty; }
由于我们的Fabric链码需要从一个账户向另一个账户转钱,因此需要三个参数:
- accountA:转出账户
- accountB:转入账户
- amount:转账金额
我们可以使用AssertCount(3)
来确保得到3个参数,就像在Init()
实现中的检查一样。然后,在我们转账之前,需要从Fabric链码资产库中读取当前的账户状态。为此,我们需要使用IChaincodeStub.GetState()
方法或IChaincodeStub.TryGetState()
方法,两者的区别在于第二个方法不会抛出异常,仅仅在失败时返回false。
从Fabric链码资产库中读取了账户状态后,我们可以从accountA中扣除转账金额,并向accountB加上这个金额。在这一步你可以根据自己的需要进行必要的检查,例如转账金额不可以是负数。
在更新了两个账户的状态变量后,我们还需要将新的状态使用stub.PutState()
写入资产库。最后我们返回一个空的ByteString
给Fabric链码调用方表示没有发生错误。
InternalQuery()
方法用来查询指定账户的余额,因此需要传入一个参数表示要查询的账户。
private async Task<ByteString> InternalQuery(IChaincodeStub stub, Parameters args) { args.AssertCount(1); var a = args[0]; var aValueBytes = await stub.GetState(a); if (aValueBytes == null) throw new Exception($"Failed to get state of asset holder {a}"); return aValueBytes; }
好了,现在我们完成了Fabric链码的.NET/C#实现,不过别忘了实现作为.NET应用入口的Main()
方法,我们需要在这里启动链码:
static async Task Main(string[] args) { using (var provider = ChaincodeProviderConfiguration.Configure<AssetHolding>(args)) { var shim = provider.GetRequiredService<Shim>(); await shim.Start(); } }
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
Spring Cloud Alibaba 实战(八) - 审核业务的分布式事务处理实现
Github 本文主要讲解RabbitMQ的介绍和安装,Spring Cloud Stream核心概念,Spring Cloud Alibaba RocketMQ学习,异步消息推送与消费 1 审核业务的实现 com/javaedge/contentcenter/service/content/ShareService.java 假设添加积分操作很耗时,我们的主要操作是审核,而不关心积分,所以可以将其异步化 1.1 Spring实现异步的方法 ◆ AsyncRestTemplate 参考文档Spring 的异步HTTP请求AsyncRestTemplate ◆ @ Async注解 参考文档https://spring.io/guides/gs/async-method/ ◆ WebClient ( Spring 5.0引入 ,为取代AsyncRestTemplate) 参考文档https://docs.spring.io/spring/docs5.1. RELEASE/spring-framework-reference/web-reactive.html#webflux-client ...
- 下一篇
Java script中的函数使用方法
前言 什么是函数,就是把一段相对独立的具有特定功能的代码块封装起来,形成一个独立实体,就是函数,起个名字(函数名),在开发中可以反复调用,函数的作用就是封装一段代码,可以重复使用。 1. 函数的声明及调用 1.1声明 关键字声明 function 函数名(){ // 函数体 } 表达式声明 var fn = function() { // 函数体 } 特点: 函数声明的时候,函数体并不会执行,只要当函数被调用的时候才会执行。 一个函数一般都特定的用来干 一件 事情 1.2 调用 调用函数的语法: 函数名(); 特点: 函数体只有在调用的时候才会执行,调用需要()进行调用。 可以调用多次(重复使用) // 声明函数 function sayHi() { console.log("吃了没?"); } // 调用函数 sayHi(); // 求1-100之间所有数的和 function getSum() { var sum = 0; for (var i = 0; i < 100; i++) { sum += i; } console.log(sum); } // 一段代码可以多次调用...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS关闭SELinux安全模块
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- Mario游戏-低调大师作品
- CentOS6,CentOS7官方镜像安装Oracle11G
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- Windows10,CentOS7,CentOS8安装Nodejs环境
- Docker安装Oracle12C,快速搭建Oracle学习环境
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- CentOS8编译安装MySQL8.0.19
- MySQL8.0.19开启GTID主从同步CentOS8