Hyperledger Fabric Node.js如何使用基於通道的事件服務
本教程說明了基於通道的事件的使用。這些事件與現有事件類似,但是特定於單個通道。在設定偵聽器時,客戶端處理基於通道的事件有一些新選項。從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
在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) => {
======================================================================
分享一些比特幣、以太坊、EOS、Fabric等區塊鏈相關的互動式線上程式設計實戰教程:
- java比特幣開發教程,本課程面向初學者,內容即涵蓋比特幣的核心概念,例如區塊鏈儲存、去中心化共識機制、金鑰與指令碼、交易與UTXO等,同時也詳細講解如何在Java程式碼中整合比特幣支援功能,例如建立地址、管理錢包、構造裸交易等,是Java工程師不可多得的比特幣開發學習課程。
- php比特幣開發教程,本課程面向初學者,內容即涵蓋比特幣的核心概念,例如區塊鏈儲存、去中心化共識機制、金鑰與指令碼、交易與UTXO等,同時也詳細講解如何在Php程式碼中整合比特幣支援功能,例如建立地址、管理錢包、構造裸交易等,是Php工程師不可多得的比特幣開發學習課程。
- c#比特幣開發教程,本課程面向初學者,內容即涵蓋比特幣的核心概念,例如區塊鏈儲存、去中心化共識機制、金鑰與指令碼、交易與UTXO等,同時也詳細講解如何在C#程式碼中整合比特幣支援功能,例如建立地址、管理錢包、構造裸交易等,是C#工程師不可多得的比特幣開發學習課程。
- java以太坊開發教程,主要是針對java和android程式設計師進行區塊鏈以太坊開發的web3j詳解。
- python以太坊,主要是針對python工程師使用web3.py進行區塊鏈以太坊開發的詳解。
- php以太坊,主要是介紹使用php進行智慧合約開發互動,進行賬號建立、交易、轉賬、代幣開發以及過濾器和交易等內容。
- 以太坊入門教程,主要介紹智慧合約與dapp應用開發,適合入門。
- 以太坊開發進階教程,主要是介紹使用node.js、mongodb、區塊鏈、ipfs實現去中心化電商DApp實戰,適合進階。
- ERC721以太坊通證實戰,課程以一個數字藝術品創作與分享DApp的實戰開發為主線,深入講解以太坊非同質化通證的概念、標準與開發方案。內容包含ERC-721標準的自主實現,講解OpenZeppelin合約程式碼庫二次開發,實戰專案採用Truffle,IPFS,實現了通證以及去中心化的通證交易所。
- C#以太坊,主要講解如何使用C#開發基於.Net的以太坊應用,包括賬戶管理、狀態與交易、智慧合約開發與互動、過濾器和交易等。
- EOS入門教程,本課程幫助你快速入門EOS區塊鏈去中心化應用的開發,內容涵蓋EOS工具鏈、賬戶與錢包、發行代幣、智慧合約開發與部署、使用程式碼與智慧合約互動等核心知識點,最後綜合運用各知識點完成一個便籤DApp的開發。
- 深入淺出玩轉EOS錢包開發,本課程以手機EOS錢包的完整開發過程為主線,深入學習EOS區塊鏈應用開發,課程內容即涵蓋賬戶、計算資源、智慧合約、動作與交易等EOS區塊鏈的核心概念,同時也講解如何使用eosjs和eosjs-ecc開發包訪問EOS區塊鏈,以及如何在React前端應用中整合對EOS區塊鏈的支援。課程內容深入淺出,非常適合前端工程師深入學習EOS區塊鏈應用開發。
- Hyperledger Fabric 區塊鏈開發詳解,本課程面向初學者,內容即包含Hyperledger Fabric的身份證書與MSP服務、許可權策略、通道配置與啟動、鏈碼通訊介面等核心概念,也包含Fabric網路設計、nodejs鏈碼與應用開發的操作實踐,是Nodejs工程師學習Fabric區塊鏈開發的最佳選擇。
- Hyperledger Fabric java 區塊鏈開發詳解,課程面向初學者,內容即包含Hyperledger Fabric的身份證書與MSP服務、許可權策略、通道配置與啟動、鏈碼通訊介面等核心概念,也包含Fabric網路設計、java鏈碼與應用開發的操作實踐,是java工程師學習Fabric區塊鏈開發的最佳選擇。
- tendermint區塊鏈開發詳解,本課程適合希望使用tendermint進行區塊鏈開發的工程師,課程內容即包括tendermint應用開發模型中的核心概念,例如ABCI介面、默克爾樹、多版本狀態庫等,也包括代幣發行等豐富的實操程式碼,是go語言工程師快速入門區塊鏈開發的最佳選擇。
匯智網原創翻譯,轉載請標明出處。這裡是Hyperledger Fabric node.js如何使用基於通