在上一篇文章中, 通過執行node query.js, 查詢Fabric 網路, 並返回了10輛車的資訊:
#node query.js
命令列的輸出如下:
這是10輛車。 由Adriana擁有的黑色特斯拉模型S,由Brad擁有的紅色福特野馬,由名叫Pari的人擁有的紫羅蘭菲亞特Punto等等。 分類帳是基於鍵/值的,在這個實現中,關索引值是從CAR0到CAR9。 這一點將變得特別重要。
查詢賬本的實現
現在讓我們來看看底層的實現。 使用編輯器(例如atom或visual studio)並開啟query.js程式。
應用程式的初始部分定義了某些變數,如鏈碼,通道名稱和網路端點:
下面的代碼用來構造對於汽車的查詢, 注意前面的注釋, 這個注釋中指明, 如果調用queryCar, 則參數args就要傳入汽車的key, 也就是CAR0等;在這個例子中指明是用queryAllCars, 則返回所有的car對象
在上面的request對象中, 通過chaincodeId來指定需要調用的chaincode,如上面所示, chaincode是fabcar.
那麼這個名為fabcar的chaincode, 具體是什麼樣子的呢。來看一下代碼。首先這個chaincode是放在下面的這個目錄下的:
開啟這個chaincode 源檔案,是用Go 語言編寫的, 最上面定義了SmartContract 以及Car 兩個類, 其中Car 是主要的Model 對象, 有Make,Model等幾個屬性。
下面的兩個方法則是和Fabric 網路有關, 主要是通過shim對象進行互動。
第一個方法是Init,從注釋中看, init 方法會在chaincode 被執行個體化時被執行。注釋還提及一個最佳實務, 就是建議不要存放任何商務邏輯, 而是把初始化資料等商務邏輯放在一個單獨的方法中進行調用,這裡是initLedger方法。
第二個方法是Invoke, 也就是應用程式對於區塊鏈網路的訪問, 這裡使用Invoke進行訪問。在方法內部, 首先通過GetFunctionAndParameters方法來擷取要調用的函數方法和參數。這裡可以看出, Go 語言和Javascript 語言一樣, 變數可以不定義直接使用, Go 語言編譯器會根據上下文推導變數的類型;而且還可以返回多個變數。
這裡面需要注意的第一點就是, 對於不瞭解Go語言的同學來說,函數名Init 前面的部分 (s *SmartContract) 是難以理解的, 實際上可以把這個看做Go語言的類方法,記得最前面的SmartContract嗎。 前面的SmartContract 代碼塊中, SmartContract是空的:
// Define the Smart Contract structure
type SmartContract struct {
}
這個實際上就是Go語言的特色之一, 類方法的動態添加;在這裡給SmartContract添加了Init和Invoke 兩個方法, 當Invoke 方法被調用時, 則根據傳入的參數, 執行queryCar還有queryAllCars等方法。
下面幾個業務方法有 initLedger,queryCar,queryAllCars,createCar和changeCarOwner。 我們來仔細看一下queryAllCars函數,看看它如何與Fabric進行互動。
該函數使用Shim介面的函數GetStateByRange在startKey和endKey的args之間返回賬本資料。 這些鍵分別定義為CAR0和CAR999。 因此,我們理論上可以建立1,000輛汽車(假設鑰匙被正確標記),並且一個queryAllCars會顯示每一個。這個方法的下半部分是轉換成json格式返回。
下面是應用程式調用不同chaincode方法和區塊鏈網路進行交易的示意圖:
之前我們看到了queryAllCars方法的調用樣本, 下面來嘗試一下其他的方法。
查詢特定車輛
返回到query.js程式並編輯建構函式請求來查詢特定的車。 我們將通過將函數從queryAllCars更改為queryCar並將特定的“Key”更改為args參數。 我們在這裡使用CAR4。 所以我們編輯的query.js程式現在應該包含以下內容:
然後來執行一下:
#node query.js
結果如下:
可以看到最下面的輸出:
所以我們從查詢所有車到只查詢一個,Adriana的黑色特斯拉模型S.使用queryCar函數,我們可以查詢任何關鍵字(例如CAR0),並獲得與該車相對應的任何代碼,模型,顏色和所有者。
現在,您應該對鏈碼中的基本查詢功能以及查詢程式中的少數參數感到舒適。 更新分類帳的時間到了。。。
更新賬本
現在我們已經完成了幾個賬本查詢並添加了一些代碼,我們已經準備好更新分類帳。 讓我們為新手開一輛新車。
賬本更新的工作,從應用產生一個交易提案(transaction proposal)開始。就像查詢一樣, 需要構造一個請求對象(request)來確定channel ID, 方法和特定的只能合約, 來指定一個事務(transaction)。 這個應用下一步會調用
channel.SendTransactionProposal
這個API 來發送交易提案到對等節點來執行背書(endorsement)。
區塊鏈網路(如背書節點, endorsing peer) 返回一個提案響應(proposal response), 應用會利用這個對象來構建和簽名交易請求(transaction request), 這個交易請求會通過調用channel.sendTransction API, 發送到ordering service 節點。 Ordering Service 會把這個事務打包到一個區塊中, 並把這個區塊發送到channel 中的所有節點來執行驗證(在這個例子中只有一個背書節點)。
最後, 應用會調用eh.setPeerAddr API來串連peer 節點的事件監聽連接埠, 然後通過調用eh.registerTxEvent 來註冊和特定事務ID 相關聯的事件。 這個API 允許應用來獲知一個特定事務的最終狀態(例如成功提交或者失敗)。把這個過程想象成一個通知機制會更有利於理解。
這裡並不打算更多的解釋事務的生命週期, 後續會有更多介紹。
在這次的最初的invoke 調用中, 我們僅僅是建立一個新的asset:汽車。 在fabcar 目錄中,有一個單獨的Javascript 檔案 ——invoke.js 我們會使用這個檔案來執行事務。
開啟文字編輯器, 查看如下代碼:
你會看到我們可以調用兩個函數之一 - createCar或changeCarOwner。 讓我們創造一個紅色的雪佛蘭伏特,並把它交給一個名叫尼克的業主。 我們在分類帳上使用CAR9,所以我們將使用CAR10作為識別鍵。 更新的代碼塊應如下所示:
儲存並執行代碼:
#node invoke.js
結果會有很多的輸出:
然後,我們把注意力在這一行:
Peer 節點發出事件通知, 我們的應用由於通過eh.registerTxEvent API , 會接收到通知。
現在, 我們返回query.js, 把代碼修改為查詢key為'CAR11'的汽車資訊:
然後執行
#node query.js
查看結果, 發現我們的平治C200已經顯示出來了:
車輛過戶
還有一個changeCarOwner的智能合約沒有用到, 現在, 你可以自己實現這個功能了嗎。