前言
本篇文章适合對區塊鍊應用感興趣或是想要通過函數計算服務進一步開發區塊鍊應用的新人。本文将結合阿裡雲區塊鍊服務、阿裡雲函數計算服務、阿裡雲日志服務 以及社群應用 Marbles,手把手教大家如何将阿裡雲區塊鍊服務與阿裡雲函數計算服務相結合,并進一步提供業務上的結合場景,供大家開拓思路。
本文分為以下幾部分:
- 函數計算與區塊鍊
- Marbles 區塊鍊應用介紹
- Marbles 區塊鍊應用結合函數計算進行擴充示例
- 區塊鍊應用與函數計算在業務上結合的場景與價值探讨
函數計算
是事件驅動的全托管計算服務。使用函數計算,無需采購與管理伺服器等基礎設施,隻需編寫并上傳代碼。函數計算為使用者準備好計算資源,彈性地可靠地運作任務,并提供日志查詢、性能監控和報警等功能。借助函數計算,可以快速建構任何類型的應用和服務,并且隻需為任務實際消耗的資源付費。
下圖為函數計算工作流程:
區塊鍊
區塊鍊可以了解為去中心的分布式記賬系統,其是一種 分布式、去中心化的計算與存儲架構 。區塊鍊通過某種方式來記錄資料,使使用者可以信任區塊鍊系統記錄的資料。區塊鍊中的記賬節點會按照一緻性協定記賬。記賬節點願意按照一緻性協定記賬,是因為在一緻性協定的設計中,誠實的記賬節點會得到相應的獎賞,且誠實的記錄比惡意篡改記錄的收益更大。
依托于區塊鍊網絡的可信度,衍生出了智能合約的概念。什麼是智能合約呢?現實生活中,買家與賣家要進行一筆交易,為了保證交易的順利進行,雙方會簽訂一份合約,合約中會聲明雙方各自的身份、權利以及義務。當交易出現糾紛時,買家與賣家根據當時簽訂的合約通過法律的手段解決糾紛。這種方式的不足之處在于解決糾紛的過程需要第三方權威來仲裁以及需要大量時間。那麼,假使我們現在有一位可信公正的交易代理人。賣家将商品交給代理人,買家與代理人雙方之間一手交錢一手交貨。若買家拒絕購買,代理人會将商品歸還給買家。買家也不會付了錢拿不到商品。智能合約就可以充當這樣的代理人,其為區塊鍊上一個包含合約代碼和存儲空間的虛拟賬戶,合約的代碼控制智能合約的行為,合約的賬戶存儲合約的狀态。
由于有了智能合約,DApp (Decentralized Application 即去中心化應用)也應運而生。DApp 是運作在區塊鍊網絡上的應用軟體,其上運作的代碼我們稱之為智能合約。
Marbles 區塊鍊應用是一個 資産轉移 應用示範。在 Marbles 區塊鍊應用中多個使用者可以建立并互相轉移彈珠。 ( 即彈珠就是資産轉移中的資産 )
上圖中:
- Amy、Alice、Ava 所在的小長方形是她們每個人的賬戶
- 小長方形中的圓形彈珠是每個人賬戶中的資産,彈珠的顔色和大小是資産的屬性
- 點選小長方形中的加号是為某個賬戶建立彈珠(資産)
- 将某個小長方形中的彈珠拖拽到右上方的垃圾桶中,是為某個賬戶删除彈珠(資産)
- 将某個彈珠從一個小長方形拖拽到到另一個小長方形,是彈珠(資産)的轉移
- 每一步的操作會在下方的 BLOCKS 建立一個新的小正方形,這個小正方形就代表包含交易内容的區塊
Marbles 區塊鍊應用代碼分成三部分:
- 鍊碼 - 區塊鍊網絡中,對等節點所運作的代碼。鍊碼在此次介紹的 Marbles 應用中的主要作用是處理建立以及轉移彈珠的邏輯。
- 用戶端 - 浏覽器中運作的代碼,負責 Marbles 應用頁面的渲染與互動。
- 服務端 - 伺服器中運作的代碼,充當 Marbles 應用與區塊鍊網絡之間的橋梁,其與用戶端以及區塊鍊網絡中運作着鍊碼的節點進行通信。
在 Marbles 應用中,當用戶端發送消息給服務端,服務端與區塊鍊網絡通信的時序圖大體上如下圖所示:( 讀者對詳細過程感興趣,可以閱讀
Github IBM-Blockchain/marbles)
其中,Peer 節點(對等節點) 存在于區塊鍊網絡中,擁有賬本并且可安裝鍊碼。Orderer 節點負責接收包含簽名的交易,對未打包的交易進行排序生成區塊,廣播給 Peer 節點。
上圖中:Client 以及 Server 是上文中所說的用戶端以及服務端。
- 當使用者建立或轉移彈珠時,Client 用戶端觸發相應事件,并向 Server 服務端發起請求。
- Server 接收到事件資訊後,首先會建構提案(也就是交易),提案是将事件資訊進行封裝,比如:此次交易的兩方以及交易内容是什麼。
- Server 将建構好的提案發送給區塊鍊網絡中的一個 Peer 節點,由 Peer 節點來對提案進行模拟,校驗其合法性。為什麼要這麼做呢?因為鍊碼的作用就是處理交易邏輯,而鍊碼安裝在 Peer 節點上,并沒有安裝在 Server 上。
- 如果 Peer 節點模拟提案成功,認為其合法,則會對提案進行背書,并向 Server 傳回背書後的提案。背書可以了解為,當我們去購買東西并忘記帶現金,付給對方一張 别人 給的支票。對方懷疑支票不可兌現的時候,我們在支票上簽字并表明若這張 他人 給的支票不能兌換,則可以來找我們要現金。這個簽字的動作就是背書,聲明對事物或被認可人的支援。
- Server 将背書後的提案發送給 Orderer 節點。
- Orderer 節點對提案進行排序并打包進區塊,将區塊廣播給區塊鍊網絡中的所有 Peer 節點。
假設說,現在有這麼一個業務場景,需要在每次 Marbles 應用有事件發生時,要對事件進行相應處理,且這部分的處理代碼會 經常疊代 。那麼,在區塊鍊應用更新需要較多時間的情況下,通過函數計算來處理是較為友善的。( 具體緣由在下一節将會繼續探讨 )
接下來,筆者将帶着大家模拟這個業務場景,擴充 Marbles Server 端代碼。在每次事件發生時,将事件資訊打包并通過函數計算的 HTTP 觸發器,由函數計算來對事件資訊做相應處理。
1. 準備工作
開通阿裡雲日志服務、函數計算服務、區塊鍊服務
2. 在阿裡雲區塊鍊服務中建立組織、建立聯盟
3. 在阿裡雲區塊鍊服務中建立通道
- 點選相應組織
- 點選通道
- 點選添加通道,填寫名稱與組織,并建立
- 點選新建立的通道右側的待審批連結,同意審批
4. 部署 Marbles 應用以及鍊碼
參考
阿裡雲區塊鍊服務開發指南注意事項:nodejs 版本為 v8
5. 下載下傳并配置阿裡雲函數計算開發工具 fun
- fun 是一個 Node.js 編寫的指令行工具,通過 npm 進行安裝:
$ npm install @alicloud/fun -g
- 通過在指令行輸入
,根據提示依次配置fun config
、Account ID
Access Key Id
以及Access Key Secret
。可參考: 服務位址 建立 AccessKeyDefault Region Name
- 配置 template.yml
在項目根目錄下建立一個 template.yml 檔案:
ROSTemplateFormatVersion: '2015-09-01'
Transform: 'Aliyun::Serverless-2018-04-03'
Resources:
marblesFC: # 服務名稱
Type: 'Aliyun::Serverless::Service'
Properties:
Description: 'fc test'
LogConfig: # 日志配置
Project: test-log-project # 日志 Project
Logstore: test-log-store # 日志 LogStore
processEvent: # 函數名
Type: 'Aliyun::Serverless::Function'
Properties:
Handler: httpTrigger.handler # 檔案名.方法名
Runtime: nodejs8
CodeUri: './'
Timeout: 60
Events:
http-test: # 觸發器名
Type: HTTP # 觸發器類型
Properties:
AuthType: ANONYMOUS
Methods: ['GET', 'POST', 'PUT']
test-log-project: # LogProject 名稱
Type: 'Aliyun::Serverless::Log'
Properties:
Description: 'just for test'
test-log-store: # LogStore 名稱
Type: 'Aliyun::Serverless::Log::Logstore'
Properties:
TTL: 10
ShardCount: 1
上述檔案做了如下事項:
- 在函數計算中建立了
服務marblesFC
- 為
服務配置了marblesFC
日志 Project 以及test-log-project
日志 LogStoretest-log-store
- 在
服務中建立了marblesFC
函數,并為其設定入口函數為processEvent
檔案中的httpTrigger.js
方法handler
-
函數配置了 HTTP 觸發器,觸發器名為processEvent
http-test
- 建立了
test-log-project
test-log-store
- 在項目根目錄下建立 httpTrigger.js 檔案
var getRawBody = require('raw-body')
module.exports.handler = function (request, response, context) {
// get request info
getRawBody(request, function (err, data) {
var params = {
path: request.path,
queries: request.queries,
headers: request.headers,
method: request.method,
body: data,
url: request.url,
clientIP: request.clientIP,
}
// you can deal with your own logic here
console.log(JSON.stringify(params.queries))
// set response
var respBody = new Buffer.from(JSON.stringify(params));
// var respBody = new Buffer( )
response.setStatusCode(200)
response.setHeader('content-type', 'application/json')
response.send(respBody)
})
};
- 在項目根目錄下執行
進行部署fun deploy
- 在阿裡雲函數計算控制台中,進入到
服務下的marblesFC
函數,在代碼執行頁面記錄下調用 HTTP 觸發器的位址processEvent
6. 修改 Marbles Server 端代碼
- 打開 Marbles 源代碼根目錄檔案夾下的
檔案app.js
- 添加
https子產品var https = require('https');
- 修改
函數setupWebSocket
function setupWebSocket() {
console.log('------------------------------------------ Websocket Up ------------------------------------------');
wss = new ws.Server({ server: server }); // start the websocket now
wss.on('connection', function connection(ws) {
// -- Process all websocket messages -- //
ws.on('message', function incoming(message) {
console.log(' ');
console.log('-------------------------------- Incoming WS Msg --------------------------------');
logger.debug('[ws] received ws msg:', message);
var data = null;
try {
data = JSON.parse(message); // it better be json
} catch (e) {
logger.debug('[ws] message error', message, e.stack);
}
// --- [5] Process the ws message --- //
if (data && data.type == 'setup') { // its a setup request, enter the setup code
logger.debug('[ws] setup message', data);
startup_lib.setup_ws_steps(data); // <-- open startup_lib.js to view the rest of the start up code
} else if (data) { // its a normal marble request, pass it to the lib for processing
https.get("此處填寫觸發 HTTP 觸發器的 URL 位址?type="+data.type, function(res){
console.log('test http trigger');
});
ws_server.process_msg(ws, data); // <-- the interesting "blockchainy" code is this way (websocket_server_side.js)
}
});
// log web socket errors
ws.on('error', function (e) { logger.debug('[ws] error', e); });
// log web socket connection disconnects (typically client closed browser)
ws.on('close', function () { logger.debug('[ws] closed'); });
// whenever someone connects, tell them our app's state
ws.send(JSON.stringify(ws_server.build_state_msg())); // tell client our app state
});
// --- Send a message to all connected clients --- //
wss.broadcast = function broadcast(data) {
var i = 0;
wss.clients.forEach(function each(client) { // iter on each connection
try {
logger.debug('[ws] broadcasting to clients. ', (++i), data.msg);
client.send(JSON.stringify(data)); // BAM, send the data
} catch (e) {
logger.debug('[ws] error broadcast ws', e);
}
});
};
ws_server.setup(wss, null);
}
7. 啟動 Marbles
在控制台中進入 Marbles 項目,通過
gulp marbles_baas
啟動 Marbles 應用
8. 建立彈珠或轉移彈珠
試着在浏覽器中建立彈珠或者用滑鼠拖拽轉移彈珠
9. 檢視日志
對 Marbles 應用做了相應操作後,進入阿裡雲日志服務
test-log-project
Project 下的
test-log-store
LogStore,檢視日志
我們看到當 Marbles Server 接收到事件後,會觸發 HTTP 觸發器,由函數計算 FC 來對事件做相應處理。( 簡單起見,demo 的處理目前僅僅是記錄日志,讀者們可以自行擴充 )
通過制作上面的 demo,相信大家現在對于如何将區塊鍊應用與函數計算相結合有了一定的認識。接下來,就讓我們一起探讨下,區塊鍊應用與函數計算在業務上有哪些結合的場景與價值。
下圖是最原始的 Fabric SDK 時序圖
為了讓讀者友善了解,我們仍舊使用 Marbles 應用的時序圖,讀者可以将 Marbles 應用中的 Server 了解為 Fabric SDK 時序圖中的 Application,
在筆者看來,函數計算可以與區塊鍊應用相結合的場景主要有以下三點:
1. 處理事件
處理事件的場景剛剛在 demo 中各位讀者想必已經體驗過了。那麼,為什麼筆者倡導用函數計算來處理事件呢?
通過函數計算可以将每一次的事件進行相應的處理,處理完成後發送給日志服務。同時,還可以在函數計算中設定定時觸發器,在指定時間内,再次統計事件的資訊,由此對區塊鍊狀态進行一個資料分析。
而關于這種方式的統計邏輯,很有可能是需要經常疊代的。是以,不适合将其邏輯放入區塊鍊應用中,而是更适合放在小巧易疊代的函數計算場景中。
2. 附加業務
考慮一個這樣的場景:現在 X 公司推出了一款新的支付 App,為了鼓勵使用者使用該公司的 App,該公司對外宣傳當使用者用該公司的 App 成功完成交易後,該公司會送大量優惠券以及積分。若該 App 是一款區塊鍊應用,那麼把優惠活動的業務邏輯放在哪裡最合适呢?
筆者目前覺得是放在 Orderer 将交易打包成區塊并廣播的時候最合适,因為優惠活動的業務邏輯是經常會變化的,這類業務邏輯可以統稱為附加業務,将附加業務抽象為一個個函數并放在函數計算中,容易更新疊代。
3. 驗證交易
Peer 節點在模拟提案時,若是有複雜多變的邏輯,可以放入函數計算中,由 Peer 節點來負責調用。
以上三點就是筆者對于如何将區塊鍊服務與函數計算相結合的思考,有不準确的地方,歡迎大家指出。