天天看點

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();
    }
}           

原文連結:

用.NET/C#開發Hyperledger Fabric鍊碼