本教程說明了基於通道的事件的使用。這些事件與現有事件類似,但是特定於單個通道。在設置偵聽器時,客戶端處理基於通道的事件有一些新選項。從v1.1開始,基於通道的事件是Hyperledger Fabric Node.js客戶端的新功能。
有關Fabric入門的更多信息,請查看構建你的第一個網絡或者手把手教你走進Hyperledger Fabric。
以下假設了解Fabric網絡(orderers和peer)以及Node應用程序開發,包括使用Javascript Promise。
概述
客戶端應用程序可以使用Fabric Node.js客戶端註冊“偵聽器”以在將塊添加到通道分類帳時接收塊。我們將這些稱爲“基於通道的事件”,它們允許客戶端開始接收來自特定塊編號的塊,從而允許事件處理在可能已丟失的塊上正常運行。Fabric Node.js客戶端還可以通過處理傳入的塊並查找特定的交易或鏈代碼事件來協助客戶端應用程序。這允許客戶端應用程序被通知交易完成或任意鏈代碼事件,而不必在接收時執行多個查詢或搜索塊。
該服務允許任何用戶接收“過濾的”塊事件(換句話說,不包含敏感信息)。接收“未過濾”的塊事件需要對通道進行讀訪問。默認行爲是連接以接收過濾的塊事件。要連接以接收未過濾的塊事件,請調用connect(true)
(參見下文)。
請注意,如果你註冊塊事件然後提交交易,則不應對包含交易的塊進行任何假設。特別是,你不應該假設你的交易處於與註冊到對等方基於通道的事件服務之後收到的第一個塊事件關聯的塊中。相反,你可以只註冊一個交易事件。
通道上的API
-
newChannelEventHub(peer)
:獲取ChannelEventHub的新實例的Channel實例方法。 -
getChannelEventHubsForOrg
:獲取基於組織的ChannelEventHubs列表。如果省略組織名稱,則使用當前用戶的當前組織。
在v1.1中新增的ChannelEventHub和API:
-
registerBlockEvent(eventCallBack,errorCallBack,options)
:註冊塊事件。 -
unregisterBlockEvent(reg_num)
:刪除塊註冊。 -
registerTxEvent(tx_id,eventCallBack,errorCallBack,options)
:註冊特定的交易事件。 -
unregisterTxEvent(tx_id)
:刪除特定的交易註冊。 -
registerChaincodeEvent(ccid,eventCallBack,errorCallBack,options)
:註冊鏈代碼事件。 -
unregisterChaincodeEvent(cc_handle)
:刪除鏈代碼事件註冊。 -
connect(full_block)
:使客戶端通道事件中心與基於結構通道的事件服務連接。必須在您的ChannelEventHub實例接收事件之前進行此調用。當基於通道的事件中心與服務連接時,它將請求接收塊或過濾的塊。如果省略full_block參數,則默認爲false,並且將請求過濾的塊。調用connect()後,無法更改接收塊或已過濾的塊。 -
disconnect()
:使客戶端通道事件路由關閉與基於結構網絡通道的事件服務的連接,並使用已註冊的errorCallBacks通知所有當前通道事件註冊的關閉。
節點參數
獲取ChannelEventHub的新實例時必須包含此參數。使用連接配置文件時,該值可以是Peer實例或節點的名稱,請參閱如何使用公共網絡配置文件。
eventCallback參數
必須包含此參數。這是當此通道接收新塊時,在偵聽特定交易或鏈代碼事件時要通知的回調函數。
errorCallback參數
這是一個可選參數。這是在此通道事件路由關閉時要通知的回調函數。關閉可能是由結構網絡錯誤,網絡連接問題或通過調用disconnect()
方法引起的。
options參數
這是一個可選參數。此參數將包含以下可選屬性:
-
{integer} startBlock
(可選):此選項設置爲事件檢查的起始塊號。包含時,將要求對等方基於通道的事件服務開始從此塊編號發送塊。這也是如何恢復監聽或重放添加到分類帳的遺漏塊。默認值是分類帳上最後一個塊的編號。replay事件可能會混淆其他事件監聽器;因此,當使用startBlock
和endBlock
時,ChannelEventHub
上只允許一個偵聽器。當排除此參數時(因爲它將正常),將要求事件服務開始從分類帳上的最後一個塊發送塊。 -
{integer} endBlock
(可選):此選項設置爲事件檢查的結束塊編號。當包含時,將要求對等方的基於通道的事件服務在交付此塊後停止發送塊。這是如何重播添加到分類帳的遺漏塊。如果未包含startBlock
,則endBlock
必須等於或大於當前通道塊高度。replay事件可能會混淆其他事件監聽器;因此,當使用startBlock
和endBlock
時,ChannelEventHub
上只允許一個偵聽器。 -
{boolean} unregister
(可選):此選項設置指示在看到事件時應刪除(取消註冊)註冊。當應用程序使用超時僅等待指定的時間來查看交易時,超時處理應包括交易事件偵聽器的手動“取消註冊”,以避免意外調用事件回調。對於不同類型的事件偵聽器,此設置的默認值是不同的。對於塊偵聽器,將end_block設置爲選項時,默認值爲true。對於交易偵聽器,默認值爲true。對於鏈碼偵聽器,默認值爲false,因爲匹配過濾器可能適用於許多交易。 -
{boolean} disconnect
(可選):此選項設置指示ChannelEventHub
實例在看到事件後自動斷開自身與對等方基於通道的事件服務的連接。除非設置了endBlock
,否則默認值爲false,那麼它將爲true。
獲取基於通道的事件路由
Fabric Node.js客戶端Channel
對象中添加了新方法,以簡化ChannelEventHub
對象的設置。使用以下命令獲取將設置爲與對等方基於通道的事件服務一起使用的ChannelEventHub
實例。ChannelEventHub
實例將使用對等實例正在使用的所有相同端點配置設置,例如tls證書以及主機和端口地址。
使用連接配置文件(請參閱參考資料)時,可以使用節點的名稱來獲取新的通道事件路由。
var channel_event_hub = channel.newChannelEventHub('peer0.org1.example.com');
以下是在使用連接配置文件時如何獲取通道事件路由列表的示例。以下內容將根據連接配置文件的當前活動客戶端client
部分中定義的當前組織獲取列表。組織中定義的將eventSource
設置爲true的對象將添加到列表中。
var channel_event_hubs = channel.getChannelEventHubsForOrg();
創建節點實例時,可以使用節點實例獲取ChannelEventHub
實例。
let data = fs.readFileSync(path.join(__dirname, 'somepath/tlscacerts/org1.example.com-cert.pem'));
let peer = client.newPeer(
'grpcs://localhost:7051',
{
pem: Buffer.from(data).toString(),
'ssl-target-name-override': 'peer0.org1.example.com'
}
);
let channel_event_hub = channel.newChannelEventHub(peer);
區塊偵聽器
當需要監視要添加到分類帳的新塊時,請使用塊事件偵聽器。當新塊被提交給節點上的分類帳時,將通知Fabric客戶端Node.js.然後,客戶端Node.js將調用應用程序的已註冊回調。回調將傳遞新添加的塊的JSON表示。請注意,當未使用true值調用connect()
時,回調將接收過濾塊。註冊接收完整塊的用戶的訪問權限將由節點的基於信道的事件服務檢查。當需要查看先前添加的塊時,回調的註冊可以包括起始塊號。回調將開始從此號碼接收塊,並在添加到分類帳時繼續接收新塊。這是應用程序resume和replay在應用程序脫機時可能已丟失的事件的一種方法。應用程序應記住它已處理的最後一個塊,以避免replay整個分類帳。
以下示例將註冊塊偵聽器以開始接收塊。
// keep the block_reg to unregister with later if needed
block_reg = channel_event_hub.registerBlockEvent((block) => {
console.log('Successfully received the block event');
<do something with the block>
}, (error)=> {
console.log('Failed to receive the block event ::'+error);
<do something with the error>
});
以下示例將使用起始塊編號進行註冊,因爲此應用程序需要在特定塊中恢復並replay丟失的塊。應用程序回調將像當前事件一樣處理同一區域中的replay塊。塊偵聽器將繼續接收塊,因爲它們已提交到節點的分類帳。
// keep the block_reg to unregister with later if needed
block_reg = channel_event_hub.registerBlockEvent((block) => {
console.log('Successfully received the block event');
<do something with the block>
}, (error)=> {
console.log('Failed to receive the block event ::'+error);
<do something with the error>
},
{startBlock:23}
);
以下示例將使用起始塊編號和結束塊進行註冊。應用程序需要replay丟失的塊。應用程序回調將處理與當前事件相同的區域中的replay塊。當偵聽器看到結束塊事件時,塊偵聽器將自動取消註冊,並且ChannelEventHub
將關閉。申請將不必處理此句柄。
block_reg = channel_event_hub.registerBlockEvent((block) => {
console.log('Successfully received the block event');
<do something with the block>
}, (error)=> {
console.log('Failed to receive the block event ::'+error);
<do something with the error>
},
// for block listeners, the defaults for unregister and disconnect are true,
// so the they are not required to be set in the following example
{startBlock:23, endBlock:30, unregister: true, disconnect: true}
);
交易監聽器
當需要監視組織對等方的交易完成時,請使用交易偵聽器。當新塊被提交給節點上的分類帳時,將通知客戶端Node.js.然後,客戶端將檢查塊是否已註冊的交易標識符。如果找到交易,則將通過交易ID,交易狀態和塊編號通知回調。過濾的塊包含交易狀態,因此無需連接到對等方的基於通道的事件服務即可接收完整的塊。由於大多數非管理員用戶將無法看到完整的塊,因此當這些用戶只需要監聽其提交的交易時,連接到接收過濾的塊將避免訪問問題。
以下示例將顯示在javascript承諾中註冊交易ID並構建另一個將交易發送到訂購者的承諾。這兩個承諾將一起執行,以便一起收到兩個行動的結果。使用交易偵聽器,取消註冊的默認可選設置爲true。因此,在以下示例中,在偵聽器看到交易之後,將註冊的偵聽器將自動取消註冊。
let tx_object = client.newTransactionID();
// get the transaction ID string for later use
let tx_id = tx_object.getTransactionID();
let request = {
targets : targets,
chaincodeId: 'my_chaincode',
fcn: 'invoke',
args: ['doSomething', 'with this data'],
txId: tx_object
};
return channel.sendTransactionProposal(request);
}).then((results) => {
// a real application would check the proposal results
console.log('Successfully endorsed proposal to invoke chaincode');
// start block may be null if there is no need to resume or replay
let start_block = getBlockFromSomewhere();
let event_monitor = new Promise((resolve, reject) => {
let handle = setTimeout(() => {
// do the housekeeping when there is a problem
channel_event_hub.unregisterTxEvent(tx_id);
console.log('Timeout - Failed to receive the transaction event');
reject(new Error('Timed out waiting for block event'));
}, 20000);
channel_event_hub.registerTxEvent((event_tx_id, status, block_num) => {
clearTimeout(handle);
//channel_event_hub.unregisterTxEvent(event_tx_id); let the default do this
console.log('Successfully received the transaction event');
storeBlockNumForLater(block_num);
resolve(status);
}, (error)=> {
clearTimeout(handle);
console.log('Failed to receive the transaction event ::'+error);
reject(error);
},
// when this `startBlock` is null (the normal case) transaction
// checking will start with the latest block
{startBlock:start_block}
// notice that `unregister` is not specified, so it will default to true
// `disconnect` is also not specified and will default to false
);
});
let send_trans = channel.sendTransaction({proposalResponses: results[0], proposal: results[1]});
return Promise.all([event_monitor, send_trans]);
}).then((results) => {
Chaincode事件監聽器
當需要監控將在您的鏈代碼中發佈的事件時,請使用鏈代碼事件監聽器。當新塊提交到分類帳時,將通知客戶端Node.js.然後,客戶端將在鏈代碼事件的名稱字段中檢查已註冊的鏈代碼模式。監聽器的註冊包括用於檢查鏈代碼事件名稱的正則表達式。如果發現鏈代碼事件名稱與偵聽器的正則表達式匹配,則將通過鏈代碼事件,塊編號,交易ID和交易狀態通知偵聽器的回調。過濾的塊將不具有鏈碼事件有效載荷信息;它只有chaincode事件名稱。如果需要有效載荷信息,則用戶必須能夠訪問完整塊,並且通道事件中心必須連接(true)以從對等方的基於通道的事件服務接收完整塊事件。
以下示例演示如何在javascript承諾中註冊鏈代碼事件偵聽器,並構建另一個將交易發送到訂購者的承諾。這兩個承諾將一起執行,以便一起收到兩個行動的結果。如果長期監視需要chaincode事件偵聽器,請遵循上面的塊偵聽器示例。
let tx_object = client.newTransactionID();
let request = {
targets : targets,
chaincodeId: 'my_chaincode',
fcn: 'invoke',
args: ['doSomething', 'with this data'],
txId: tx_object
};
return channel.sendTransactionProposal(request);
}).then((results) => {
// a real application would check the proposal results
console.log('Successfully endorsed proposal to invoke chaincode');
// Build the promise to register a event listener with the NodeSDK.
// The NodeSDK will then send a request to the peer's channel-based event
// service to start sending blocks. The blocks will be inspected to see if
// there is a match with a chaincode event listener.
let event_monitor = new Promise((resolve, reject) => {
let regid = null;
let handle = setTimeout(() => {
if (regid) {
// might need to do the clean up this listener
channel_event_hub.unregisterChaincodeEvent(regid);
console.log('Timeout - Failed to receive the chaincode event');
}
reject(new Error('Timed out waiting for chaincode event'));
}, 20000);
regid = channel_event_hub.registerChaincodeEvent(chaincode_id.toString(), '^evtsender*',
(event, block_num, txnid, status) => {
// This callback will be called when there is a chaincode event name
// within a block that will match on the second parameter in the registration
// from the chaincode with the ID of the first parameter.
console.log('Successfully got a chaincode event with transid:'+ txnid + ' with status:'+status);
// might be good to store the block number to be able to resume if offline
storeBlockNumForLater(block_num);
// to see the event payload, the channel_event_hub must be connected(true)
let event_payload = event.payload.toString('utf8');
if(event_payload.indexOf('CHAINCODE') > -1) {
clearTimeout(handle);
// Chaincode event listeners are meant to run continuously
// Therefore the default to automatically unregister is false
// So in this case we want to shutdown the event listener once
// we see the event with the correct payload
channel_event_hub.unregisterChaincodeEvent(regid);
console.log('Successfully received the chaincode event on block number '+ block_num);
resolve('RECEIVED');
} else {
console.log('Successfully got chaincode event ... just not the one we are looking for on block number '+ block_num);
}
}, (error)=> {
clearTimeout(handle);
console.log('Failed to receive the chaincode event ::'+error);
reject(error);
}
// no options specified
// startBlock will default to latest
// endBlock will default to MAX
// unregister will default to false
// disconnect will default to false
);
});
// build the promise to send the proposals to the orderer
let send_trans = channel.sendTransaction({proposalResponses: results[0], proposal: results[1]});
// now that we have two promises all set to go... execute them
return Promise.all([event_monitor, send_trans]);
}).then((results) => {
分享一些Fabric等區塊鏈相關的交互式在線編程實戰教程:
- Hyperledger Fabric 區塊鏈開發詳解,本課程面向初學者,內容即包含Hyperledger Fabric的身份證書與MSP服務、權限策略、信道配置與啓動、鏈碼通信接口等核心概念,也包含Fabric網絡設計、nodejs鏈碼與應用開發的操作實踐,是Nodejs工程師學習Fabric區塊鏈開發的最佳選擇。
- Hyperledger Fabric java 區塊鏈開發詳解,課程面向初學者,內容即包含Hyperledger Fabric的身份證書與MSP服務、權限策略、信道配置與啓動、鏈碼通信接口等核心概念,也包含Fabric網絡設計、java鏈碼與應用開發的操作實踐,是java工程師學習Fabric區塊鏈開發的最佳選擇。