用.NET/C#开发Hyperledger Fabric链码

本教程介绍如何使用.NET Core开发一个简单的Hyperledger Fabric 链码,用于进行基本的资产管理和转账操作。如果你是一个熟练的.NET/C#开发人员, 由于种种原因需要使用Hyperledger Fabric作为区块链平台,那么除了 转身投入Java/Node.js/Go的怀抱之外,这也提供了另外一种选择。

相关教程推荐:

1、Fabric链码.NET Core开发包

首先创建一个.NET Core项目,在命令行执行如下命令添加 对Fabric链码.NET Core开发包的依赖:

1
2
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接口:

1
2
3
4
5
6
7
8
9
10
11
12
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>类型的参数对象,其中包含了所有参数。

1
2
3
4
5
6
7
8
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() 的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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()将转换结果存入链码资产库:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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链码的调用参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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来让代码更干净:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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()方法用来查询指定账户的余额,因此需要传入一个参数表示 要查询的账户。

1
2
3
4
5
6
7
8
9
10
11
12
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()方法,我们需要在这里启动链码:

1
2
3
4
5
6
7
8
static async Task Main(string[] args)
{
using (var provider = ChaincodeProviderConfiguration.Configure<AssetHolding>(args))
{
var shim = provider.GetRequiredService<Shim>();
await shim.Start();
}
}

原文链接:Developing A Simple Chaincode With .NET Core

汇智网翻译整理,转载请标明出处