Hyperledger Fabric Node.js如何使用基於通道的事件服務

本教程說明了基於通道的事件的使用。這些事件與現有事件相似,可是特定於單個通道。在設置偵聽器時,客戶端處理基於通道的事件有一些新選項。從v1.1開始,基於通道的事件是Hyperledger Fabric Node.js客戶端的新功能。javascript

有關Fabric入門的更多信息,請查看構建你的第一個網絡或者手把手教你走進Hyperledger Fabrichtml

如下假設了解Fabric網絡(orderers和peer)以及Node應用程序開發,包括使用Javascript Promise。java

概述

客戶端應用程序可使用Fabric Node.js客戶端註冊「偵聽器」以在將塊添加到通道分類賬時接收塊。咱們將這些稱爲「基於通道的事件」,它們容許客戶端開始接收來自特定塊編號的塊,從而容許事件處理在可能已丟失的塊上正常運行。Fabric Node.js客戶端還能夠經過處理傳入的塊並查找特定的交易或鏈代碼事件來協助客戶端應用程序。這容許客戶端應用程序被通知交易完成或任意鏈代碼事件,而沒必要在接收時執行多個查詢或搜索塊。node

該服務容許任何用戶接收「過濾的」塊事件(換句話說,不包含敏感信息)。接收「未過濾」的塊事件須要對通道進行讀訪問。默認行爲是鏈接以接收過濾的塊事件。要鏈接以接收未過濾的塊事件,請調用connect(true)(參見下文)。git

請注意,若是你註冊塊事件而後提交交易,則不該對包含交易的塊進行任何假設。特別是,你不該該假設你的交易處於與註冊到對等方基於通道的事件服務以後收到的第一個塊事件關聯的塊中。相反,你能夠只註冊一個交易事件。github

通道上的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參數編程

必須包含此參數。這是當此通道接收新塊時,在偵聽特定交易或鏈代碼事件時要通知的回調函數。promise

errorCallback參數網絡

這是一個可選參數。這是在此通道事件路由關閉時要通知的回調函數。關閉多是由結構網絡錯誤,網絡鏈接問題或經過調用disconnect()方法引發的。

options參數

這是一個可選參數。此參數將包含如下可選屬性:

  • {integer} startBlock(可選):此選項設置爲事件檢查的起始塊號。包含時,將要求對等方基於通道的事件服務開始今後塊編號發送塊。這也是如何恢復監聽或重放添加到分類賬的遺漏塊。默認值是分類賬上最後一個塊的編號。replay事件可能會混淆其餘事件監聽器;所以,當使用startBlockendBlock時,ChannelEventHub上只容許一個偵聽器。當排除此參數時(由於它將正常),將要求事件服務開始從分類賬上的最後一個塊發送塊。
  • {integer} endBlock(可選):此選項設置爲事件檢查的結束塊編號。當包含時,將要求對等方的基於通道的事件服務在交付此塊後中止發送塊。這是如何重播添加到分類賬的遺漏塊。若是未包含startBlock,則endBlock必須等於或大於當前通道塊高度。replay事件可能會混淆其餘事件監聽器;所以,當使用startBlockendBlock時,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區塊鏈開發的最佳選擇。
相關文章
相關標籤/搜索