本教程介紹如何使用.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();
}
}