區塊鏈:我們應該建構什麼 PoC?

你的眼睛害怕,你的手癢!

在之前的文章中,我們討論了建立區塊鏈的技術(我們應該建立什麼區塊鏈?)以及在他們的幫助下可以實施的案例(我們為什麼要建立案例?)。 是時候用你的雙手工作了! 為了實施試點和 PoC(概念驗證),我更喜歡使用雲,因為… 可以從世界任何地方訪問它們,並且通常無需在繁瑣的環境安裝上浪費時間,因為有預設配置。 因此,讓我們做一些簡單的事情,例如,一個用於在參與者之間轉移硬幣的網絡,我們謙虛地稱之為比特幣。 為此,我們將使用 IBM 雲端和通用區塊鏈 Hyperledger Fabric。 首先我們先弄清楚為什麼Hyperledger Fabric被稱為通用區塊鏈?

區塊鏈:我們應該建構什麼 PoC?

Hyperledger Fabric - 通用區塊鏈

一般來說,通用資訊系統是:

  • 一組伺服器和執行業務邏輯的軟體核心;
  • 與系統互動的介面;
  • 用於設備/人員的註冊、身份驗證和授權的工具;
  • 儲存作業和歸檔資料的資料庫:

區塊鏈:我們應該建構什麼 PoC?

Hyperledger Fabric 的官方版本可以閱讀: 在線簡而言之,Hyperledger Fabric 是一個開源平台,可讓您建立私有區塊鏈並執行用 JS 和 Go 程式語言編寫的任意智能合約。 讓我們詳細了解 Hyperledger Fabric 的架構,並確保這是一個通用系統,僅具有儲存和記錄資料的細節。 特殊之處在於,與所有區塊鏈一樣,資料只有在參與者達成共識時才會儲存在放置在區塊鏈上的區塊中,並且記錄後的資料無法悄悄更正或刪除。

超級帳本結構架構

此圖顯示了 Hyperledger Fabric 架構:

區塊鏈:我們應該建構什麼 PoC?

組織 — 組織包含同行,即區塊鏈的存在是由於組織的支持。 不同的組織可以屬於同一通路。

渠道 ——將同儕聯合成組的邏輯結構,即區塊鏈已指定。 Hyperledger Fabric 可以同時處理具有不同業務邏輯的多個區塊鏈。

會員服務提供者 (MSP) 是用於頒發身分和指派角色的CA(憑證授權單位)。 建立節點需要與MSP互動。

對等節點 — 驗證交易、儲存區塊鏈、執行智能合約並與應用程式互動。 Peer有一個身分(數位憑證),由MSP頒發。 與所有節點擁有平等權利的比特幣或以太坊網路不同,Hyperledger Fabric 中的節點扮演著不同的角色:

  • 同行或許 認可同行 (EP)並執行智能合約。
  • 承諾同行 (CP) - 只在區塊鏈中保存資料並更新「世界狀態」。
  • 錨點同行 (AP) - 如果多個組織參與區塊鏈,則錨點用於它們之間的通訊。 每個組織必須有一個或多個錨點。 使用AP,組織中的任何對等點都可以獲得其他組織中所有對等點的資訊。 用於AP之間的資訊同步 八卦協議.
  • 領導同行 - 如果一個組織有多個對等點,那麼只有該對等點的領導者將從排序服務接收區塊並將其提供給其餘的對等點。 領導者可以靜態指定,也可以由組織中的同事動態選擇。 八卦協議也用於同步有關領導者的信息。

資產 — 具有價值並儲存在區塊鏈上的實體。 更具體地說,這是 JSON 格式的鍵值資料。 這些數據記錄在區塊鏈中。 它們有儲存在區塊鏈中的歷史記錄和儲存在「世界狀態」資料庫中的當前狀態。 根據業務任務任意填充資料結構。 沒有必填字段,唯一的建議是資產必須有所有者並且有價值。

萊傑 — 由區塊鏈和Word狀態資料庫組成,儲存資產的當前狀態。 世界狀態使用 LevelDB 或 CouchDB。

聰明的合同 — 使用智慧合約,實現系統的業務邏輯。 在 Hyperledger Fabric 中,智慧合約稱為鏈碼。 使用鏈碼,可以指定資產和交易。 從技術角度來說,智能合約是用 JS 或 Go 程式語言實現的軟體模組。

背書政策 - 對於每個鏈碼,您可以設定一項策略,確定一筆交易應該有多少確認以及來自誰。 如果未設定策略,則預設為:「交易必須由通道中任何組織的任何成員確認」。 政策範例:

  • 交易必須得到組織任何管理員的批准;
  • 必須得到該組織的任何成員或客戶的確認;
  • 必須得到任何同行組織的確認。

訂購服務 — 將交易打包成區塊並將它們傳送到頻道中的對等點。 保證將訊息傳送到網路上的所有對等方。 用於工業系統 卡夫卡訊息代理,用於開發和測試 獨奏.

呼叫流程

區塊鏈:我們應該建構什麼 PoC?

  • 應用程式使用 Go、Node.js 或 Java SDK 與 Hyperledger Fabric 進行通訊;
  • 客戶端創建一個 tx 交易並將其發送給背書節點;
  • Peer驗證客戶端的簽名,完成交易,並將背書簽名傳回客戶端。 Chaincode僅在背書節點上執行,並將其執行結果傳送給所有節點。 這種工作演算法稱為 PBFT(實用拜占庭容錯)共識。 不同於 經典拜占庭容錯理論 事實上,訊息的發送和確認不是來自所有參與者,而是來自某個特定的參與者;
  • 客戶端收到背書策略對應的回應數量後,將交易傳送到Ordering服務;
  • 排序服務產生一個區塊並將其發送給所有提交節點。 排序服務確保了區塊的順序記錄,從而消除了所謂的帳本分叉(參見“叉子”部分);
  • 節點收到一個區塊,再次檢查背書策略,將該區塊寫入區塊鏈並更改「世界狀態」資料庫中的狀態。

那些。 這導致節點之間的角色劃分。 這確保了區塊鏈的可擴展性和安全性:

  • 智能合約(鏈碼)執行對等節點的背書。 這確保了智能合約的機密性,因為它不是由所有參與者存儲,而是僅由認可的節點存儲。
  • 訂購應該很快就能完成。 這是透過以下事實確保的:排序僅形成一個區塊並將其發送到一組固定的領導節點。
  • 提交節點只儲存區塊鏈——它們可以有很多,並且不需要大量的電力和即時操作。

有關 Hyperledger Fabric 架構解決方案的更多詳細資訊以及為什麼它以這種方式工作而不是其他方式可以在此處找到: 建築起源 或在這裡: Hyperledger Fabric:用於許可區塊鏈的分散式作業系統.

因此,Hyperledger Fabric 是一個真正的通用系統,您可以使用它:

  • 利用智能合約機制實現任意業務邏輯;
  • 以JSON格式記錄並接收來自區塊鏈資料庫的資料;
  • 使用憑證授權單位授予和驗證 API 存取權限。

現在我們已經了解了一些 Hyperledger Fabric 的細節,讓我們最終做一些有用的事情!

部署區塊鏈

制定問題

任務是實現 Citcoin 網路的以下功能:建立帳戶、取得餘額、為帳戶充值、將硬幣從一個帳戶轉移到另一個帳戶。 讓我們畫一個物件模型,我們將在智能合約中進一步實現它。 因此,我們將擁有按名稱標識並包含餘額和帳戶清單的帳戶。 帳戶和帳戶清單是 Hyperledger Fabric 資產方面的。 因此,它們有歷史和現狀。 我會試著把它畫清楚:

區塊鏈:我們應該建構什麼 PoC?

最上面的數字是當前狀態,儲存在「世界狀態」資料庫中。 它們下面是顯示儲存在區塊鏈中的歷史的數字。 資產的當前狀態因交易而改變。 資產僅作為整體發生變化,因此交易的結果是創建一個新對象,並且資產的當前價值進入歷史。

IBM 雲端

我們建立一個帳戶 IBM雲。 要使用區塊鏈平台,必須升級為按量付費。 這個過程可能不會很快,因為... IBM 要求附加資訊並手動驗證。 從積極的方面來說,我可以說 IBM 擁有很好的培訓材料,可讓您在其雲端部署 Hyperledger Fabric。 我喜歡以下系列文章和範例:

以下是IBM區塊鏈平台的螢幕截圖。 這不是關於如何創建區塊鏈的說明,而只是任務範圍的演示。 因此,為了我們的目的,我們建立一個組織:

區塊鏈:我們應該建構什麼 PoC?

我們在其中創建節點:Orderer CA、Org1 CA、Orderer Peer:

區塊鏈:我們應該建構什麼 PoC?

我們創建用戶:

區塊鏈:我們應該建構什麼 PoC?

建立一個頻道並將其命名為 citcoin:

區塊鏈:我們應該建構什麼 PoC?

Channel 本質上是一個區塊鏈,所以它從零號區塊(創世區塊)開始:

區塊鏈:我們應該建構什麼 PoC?

編寫智能合約

/*
 * Citcoin smart-contract v1.5 for Hyperledger Fabric
 * (c) Alexey Sushkov, 2019
 */
 
'use strict';
 
const { Contract } = require('fabric-contract-api');
const maxAccounts = 5;
 
class CitcoinEvents extends Contract {
 
    async instantiate(ctx) {
        console.info('instantiate');
        let emptyList = [];
        await ctx.stub.putState('accounts', Buffer.from(JSON.stringify(emptyList)));
    }
    // Get all accounts
    async GetAccounts(ctx) {
        // Get account list:
        let accounts = '{}'
        let accountsData = await ctx.stub.getState('accounts');
        if (accountsData) {
            accounts = JSON.parse(accountsData.toString());
        } else {
            throw new Error('accounts not found');
        }
        return accountsData.toString()
    }
     // add a account object to the blockchain state identifited by their name
    async AddAccount(ctx, name, balance) {
        // this is account data:
        let account = {
            name: name,
            balance: Number(balance),       
            type: 'account',
        };
        // create account:
        await ctx.stub.putState(name, Buffer.from(JSON.stringify(account)));
 
        // Add account to list:
        let accountsData = await ctx.stub.getState('accounts');
        if (accountsData) {
            let accounts = JSON.parse(accountsData.toString());
            if (accounts.length < maxAccounts)
            {
                accounts.push(name);
                await ctx.stub.putState('accounts', Buffer.from(JSON.stringify(accounts)));
            } else {
                throw new Error('Max accounts number reached');
            }
        } else {
            throw new Error('accounts not found');
        }
        // return  object
        return JSON.stringify(account);
    }
    // Sends money from Account to Account
    async SendFrom(ctx, fromAccount, toAccount, value) {
        // get Account from
        let fromData = await ctx.stub.getState(fromAccount);
        let from;
        if (fromData) {
            from = JSON.parse(fromData.toString());
            if (from.type !== 'account') {
                throw new Error('wrong from type');
            }   
        } else {
            throw new Error('Accout from not found');
        }
        // get Account to
        let toData = await ctx.stub.getState(toAccount);
        let to;
        if (toData) {
            to = JSON.parse(toData.toString());
            if (to.type !== 'account') {
                throw new Error('wrong to type');
            }  
        } else {
            throw new Error('Accout to not found');
        }
 
        // update the balances
        if ((from.balance - Number(value)) >= 0 ) {
            from.balance -= Number(value);
            to.balance += Number(value);
        } else {
            throw new Error('From Account: not enought balance');          
        }
 
        await ctx.stub.putState(from.name, Buffer.from(JSON.stringify(from)));
        await ctx.stub.putState(to.name, Buffer.from(JSON.stringify(to)));
                 
        // define and set Event
        let Event = {
            type: "SendFrom",
            from: from.name,
            to: to.name,
            balanceFrom: from.balance,
            balanceTo: to.balance,
            value: value
        };
        await ctx.stub.setEvent('SendFrom', Buffer.from(JSON.stringify(Event)));
 
        // return to object
        return JSON.stringify(from);
    }
 
    // get the state from key
    async GetState(ctx, key) {
        let data = await ctx.stub.getState(key);
        let jsonData = JSON.parse(data.toString());
        return JSON.stringify(jsonData);
    }
    // GetBalance   
    async GetBalance(ctx, accountName) {
        let data = await ctx.stub.getState(accountName);
        let jsonData = JSON.parse(data.toString());
        return JSON.stringify(jsonData);
    }
     
    // Refill own balance
    async RefillBalance(ctx, toAccount, value) {
        // get Account to
        let toData = await ctx.stub.getState(toAccount);
        let to;
        if (toData) {
            to = JSON.parse(toData.toString());
            if (to.type !== 'account') {
                throw new Error('wrong to type');
            }  
        } else {
            throw new Error('Accout to not found');
        }
 
        // update the balance
        to.balance += Number(value);
        await ctx.stub.putState(to.name, Buffer.from(JSON.stringify(to)));
                 
        // define and set Event
        let Event = {
            type: "RefillBalance",
            to: to.name,
            balanceTo: to.balance,
            value: value
        };
        await ctx.stub.setEvent('RefillBalance', Buffer.from(JSON.stringify(Event)));
 
        // return to object
        return JSON.stringify(from);
    }
}
module.exports = CitcoinEvents;

直觀上,這裡一切都應該很清楚:

  • 演示程式將使用 Hyperledger Fabric API 呼叫多個函數(AddAccount、GetAccounts、SendFrom、GetBalance、RefillBalance)。
  • SendFrom 和 RefillBalance 函數產生示範程式將接收的事件。
  • 實例化函數在智慧合約實例化時被呼叫一次。 事實上,它不僅被調用一次,而是每次智能合約版本發生變化時被調用。 因此,用空數組初始化列表是一個壞主意,因為現在,當我們更改智能合約的版本時,我們將丟失當前列表。 不過沒關係,我只是在學習)。
  • 帳戶和帳戶清單是 JSON 資料結構。 JS 用於資料操作。
  • 您可以使用 getState 函數呼叫取得資產的目前值,並使用 putState 更新它。
  • 建立帳戶時,會呼叫AddAccount函數,比較區塊鏈中的最大帳戶數(maxAccounts = 5)。 這裡有一個門框(你注意到了嗎?),這導致帳戶數量無休止地增加。 應避免此類錯誤)

接下來,我們將智能合約載入到 Channel 中並實例化它:

區塊鏈:我們應該建構什麼 PoC?

我們來看看安裝智能合約的交易:

區塊鏈:我們應該建構什麼 PoC?

讓我們看看有關我們頻道的詳細資訊:

區塊鏈:我們應該建構什麼 PoC?

結果,我們得到了 IBM 雲端中的區塊鏈網路的下圖。 該圖還顯示了在亞馬遜雲端虛擬伺服器上運行的演示程式(下一節將詳細介紹):

區塊鏈:我們應該建構什麼 PoC?

為 Hyperledger Fabric API 呼叫建立 GUI

Hyperledger Fabric 有一個 API,可用來:

  • 創建頻道;
  • 對等通道的連接;
  • 通道中智能合約的安裝和實例化;
  • 調用交易;
  • 請求區塊鏈上的信息。

應用開發

在我們的演示程式中,我們將僅使用 API 來呼叫交易和請求訊息,因為我們已經使用 IBM 區塊鏈平台完成了剩餘步驟。 我們使用標準技術堆疊來編寫 GUI:Express.js + Vue.js + Node.js。 您可以撰寫一篇單獨的文章來介紹如何開始建立現代 Web 應用程式。 在這裡我留下我最喜歡的系列講座的連結: 使用 Vue.js 和 Express.js 的全端 Web 應用程式。 結果是一個客戶端-伺服器應用程序,具有熟悉的 Google 材料設計風格的圖形介面。 客戶端和伺服器之間的 REST API 由多個呼叫組成:

  • HyperledgerDemo/v1/init - 初始化區塊鏈;
  • HyperledgerDemo/v1/accounts/list — 取得所有帳戶的清單;
  • HyperledgerDemo/v1/account?name=Bob&balance=100 — 建立 Bob 帳戶;
  • HyperledgerDemo/v1/info?account=Bob — 取得有關 Bob 帳戶的資訊;
  • HyperledgerDemo/v1/transaction?from=Bob&to=Alice&volume=2 — 將兩個幣從 Bob 轉移給 Alice;
  • HyperledgerDemo/v1/disconnect - 關閉與區塊鏈的連線。

API 的描述以及範例包含在 郵差網站 - 一個著名的測試 HTTP API 的程式。

亞馬遜雲端中的演示應用程式

我將應用程式上傳到亞馬遜是因為... IBM 仍然無法升級我的帳戶並允許我建立虛擬伺服器。 如何將櫻桃添加到網域: www.citcoin.info。 我會讓伺服器打開一段時間,然後將其關閉,因為... 租金正在滴水,而且 citcoin 硬幣尚未在證券交易所上市)我在文章中包含了演示的屏幕截圖,以便工作的邏輯清晰。 該演示應用程式可以:

  • 初始化區塊鏈;
  • 建立帳戶(但現在無法建立新帳戶,因為區塊鏈中已達到智慧合約中指定的最大帳戶數);
  • 接收帳戶清單;
  • 在 Alice、Bob 和 Alex 之間轉移 citcoin 幣;
  • 接收事件(但是現在沒有辦法展示事件,所以為了簡單起見,介面上說不支援事件);
  • 記錄操作。

首先我們初始化區塊鏈:

區塊鏈:我們應該建構什麼 PoC?

接下來,我們建立我們的帳戶,不要浪費時間處理餘額:

區塊鏈:我們應該建構什麼 PoC?

我們得到所有可用帳戶的清單:

區塊鏈:我們應該建構什麼 PoC?

我們選擇寄件者和收件人,並取得他們的餘額。 如果寄件者和收件者相同,則他的帳戶將被充值:

區塊鏈:我們應該建構什麼 PoC?

在日誌中我們監控交易的執行:

區塊鏈:我們應該建構什麼 PoC?

實際上,演示程序就是這樣。 下面你可以看到我們在區塊鏈中的交易:

區塊鏈:我們應該建構什麼 PoC?

以及交易的一般清單:

區塊鏈:我們應該建構什麼 PoC?

至此,我們成功完成了創建 Citcoin 網路的 PoC 實作。 要讓 Citcoin 成為一個成熟的代幣轉移網絡,還需要做些什麼? 很少:

  • 在帳戶建立階段,實現私鑰/公鑰的產生。 私鑰必須與帳戶用戶一起存儲,公鑰必須儲存在區塊鏈中。
  • 進行硬幣轉賬,其中使用公鑰而不是姓名來識別用戶。
  • 使用用戶的私鑰加密從用戶到伺服器的交易。

結論

我們已經實現了 Citcoin 網絡,具有以下功能:新增帳戶、取得餘額、為帳戶充值、將硬幣從一個帳戶轉移到另一個帳戶。 那麼,我們建造 PoC 的成本是多少?

  • 您需要總體學習區塊鏈,特別是 Hyperledger Fabric;
  • 學習使用IBM或亞馬遜雲端;
  • 學習JS程式語言和一些Web框架;
  • 如果某些資料不需要儲存在區塊鏈中,而是儲存在單獨的資料庫中,那麼學習集成,例如與 PostgreSQL 集成;
  • 最後但並非最不重要的一點是——如果不了解 Linux,你就無法生活在現代世界!)

當然,這不是火箭科學,但你必須努力!

GitHub 上的資源

來源已放出 GitHub上。 儲存庫的簡要說明:
目錄 ”服務器» — Node.js 伺服器
目錄 ”客戶» — Node.js 用戶端
目錄 ”blockchain「(當然,參數值和鍵是不起作用的,僅作為示例給出):

  • Contract-智能合約原始碼
  • 錢包 — 使用 Hyperledger Fabric API 的使用者金鑰。
  • *.cds - 智能合約的編譯版本
  • *.json 檔案 - 使用 Hyperledger Fabric API 的設定檔範例

這只是開始!

來源: www.habr.com

添加評論