PBFT的數學證明
在實際的拜占庭容錯中,如果N = 3F + 1,N個節點的系統可以容忍F個故障節點。
實際拜占庭容錯系統中的每個決策都需要2F + 1批準,其中Fare是故障節點。
我們現在將在數學上證明上述兩個定義,它們是彼此的推論。以下計算是斯坦福大學筆記中數學的簡化。
分布式系統的兩個重要特性是活躍性和安全性。
活躍性
活躍性是系統繼續運行時在分布式系統環境中使用的術語。這意味著即使出現一些錯誤,系統也不會停止運行。在區塊鏈的情況下,活躍意味著系統將繼續向鏈中添加新的區塊,并且在任何時候系統都不會停止工作。
安全性
當系統收斂于單個決策時,安全性是分布式系統環境中使用的術語。在分布式系統中,節點可能會分成兩個決策或進一步拆分,分布式系統的安全性確保了即使存在故障節點,網絡也會在所有可靠節點上以單個決策結束。
證明
非拜占庭式故障
給定網絡中的N個節點,具有f個故障節點,保證活躍性和安全性所需的仲裁大小Q是多少?
仲裁定義為網絡正常運行和做出有效決策所需的最小節點數。仲裁由誠實的節點組成。
活躍度
為了避免網絡停止,必須至少存在一個非故障節點。因此,為了活躍度:
Q 《= N - f
安全度
為了避免網絡分裂成多個決策,大多數決策者應該在線。我們需要一個誠實的多數,仲裁大小應該大于節點總數的一半,以便我們做出有利于我們的決定。
因此,為了安全:
Q 》 N/2
2Q - N 》 0
通過梳理我們得到的兩個條件,
N 《 2Q 《=2(N-f)
f 《 N/2
N 》 2f
因此,對于非拜占庭式故障
拜占庭式故障
假設網絡中有n個節點,其中f個故障節點可能會遇到拜占庭式故障,那么要保證活躍性和安全性,需要多少仲裁大小Q?
遭受拜占庭式失敗的節點可以投票給無效的區塊或決策,導致決策中的分裂,并因此分叉。
活躍度
為了避免網絡停止,必須存在至少一個非故障節點或仲裁。由于拜占庭節點可能無法回復。
因此,為了活躍度:
Q 《= N - f
安全性
為了避免網絡分裂成多個決策,大多數決策者應該在線。然而,與非拜占庭式失敗不同,拜占庭式失敗中的節點也可以投票,因此我們需要在投票過程中考慮故障節點。
Q 》 N/2
2Q - N 》 0
此公式提供了網絡中可能存在的最大失敗節點數。
因此,為了安全起見,也可以將其寫為:
2Q - N 》 f
其中f是可容忍故障節點的最大數量。
把這兩個條件結合起來
N + f 《 2Q 《 2(N - f)
N + f 《 2N - 2f
3f 《 N
N 》 3f
if N = 3f + 1
then 2Q 》 4f + 1
Q 》 2f + 1/2
因此,對于拜占庭式的失敗
Qmin = 3f + 1
在node.js中實現PBFT
在本節中,我們將實現一個以PBFT為共識算法的區塊鏈。值得注意的是,該區塊鏈不會使用加密貨幣,但如果我們使用賬戶模型,則可以使用。此外,由于PBFT易受Sybil攻擊,因此只能在許可的環境下運行,因此需要了解網絡成員。
先決條件
· Node.js
· Postman
· VS code
架構與設計
為了實施PBFT,我們將開發以下組件:
1. 錢包類 - 用于公鑰和簽名數據。
2. 事務類 - 創建事務并驗證它們。
3. 區塊類 - 創建區塊,驗證塊并驗證區塊的提議者。
4. 區塊鏈類 - 創建鏈,添加區塊,計算提議者,驗證區塊,更新區塊。
5. P2p Server類 - 在對等體之間廣播和接收數據。
6. 驗證者 - 生成并驗證驗證者
7. 事務池,區塊池,提交池,準備池和消息池 - 分別存儲事務,區塊,提交,準備和新輪消息。
8. App - 用于與區塊鏈交互的Express API
9. 配置 - 存儲全局變量
10. Chain Utilities - 用于存儲散列和驗證簽名等常用功能。
創建一個根目錄pbft并將其cd入其中。此項目中的所有文件都存在于根目錄中。
mkdir pbft && cd pbft
ChainUtil類
我們將從創建一個chain-util.js文件開始,該文件將在此項目中多次使用。此文件將用于創建用于簽名數據的密鑰對,為事務生成ID,散列數據和驗證簽名。
我們需要三個模塊來執行這些功能。因此我們需要安裝它們。
npm i --save elliptic uuid crypto-js
創建一個ChainUtil類并將其導出。
// EDDSA allows us to create keypairs
// It is collection of cryptographic algorithms that are used to create keypairs
const EDDSA = require(“elliptic”).eddsa;
// ed25519 allows us to create key pair from secret
const eddsa = new EDDSA(“ed25519”);
// uuid/v1 creates timestamp dependent ids
const uuidV1 = require(“uuid/v1”);
// used for hashing data to 256 bits string
const SHA256 = require(“crypto-js/sha256”);
class ChainUtil {
// a static function to return keypair generated using a secret phrase
static genKeyPair(secret) {
return eddsa.keyFromSecret(secret);
}
// returns ids used in transactions
static id() {
return uuidV1();
}
// hashes any data using SHA256
static hash(data) {
return SHA256(JSON.stringify(data)).toString();
}
// verifies the signed hash by decrypting it with public key
// and matching it with the hash
static verifySignature(publicKey, signature, dataHash) {
return eddsa.keyFromPublic(publicKey).verify(dataHash, signature);
}
}
module.exports = ChainUtil;
pbft_chain_util.js
事務類
接下來,我們將創建一個事務類。在項目文件夾中創建文件transaction.js。此項目中的事務將包含以下屬性:
· id - 用于識別
· from - 發件人的地址
· input - 一個進一步包含要存儲的數據和時間戳的對象
· hash - 輸入的SHA256
· signature - 發件人簽名的哈希
在文件中創建一個類Transaction并將其導出。
// Import the ChainUtil class used for hashing and verification
const ChainUtil = require(“。/chain-util”);
class Transaction {
// the wallet instance will be passed as a parameter to the constructor
// along with the data to be stored.
constructor(data, wallet) {
this.id = ChainUtil.id();
this.from = wallet.publicKey;
this.input = { data: data, timestamp: Date.now() };
this.hash = ChainUtil.hash(this.input);
this.signature = wallet.sign(this.hash);
}
// this method verifies wether the transaction is valid
static verifyTransaction(transaction) {
return ChainUtil.verifySignature(
transaction.from,
transaction.signature,
ChainUtil.hash(transaction.input)
);
}
}
module.exports = Transaction;
pbft-transaction.js
錢包類
接下來是錢包。錢包持有公鑰和密鑰對。它還負責簽署數據哈希并創建簽名事務。
在項目目錄中創建文件wallet.js。添加一個類Wallet并將其導出。
// Import the ChainUtil class used for hashing and verification
const ChainUtil = require(“。/chain-util”);
// Import transaction class used for creating transactions
const Transaction = require(“。/transaction”);
class Wallet {
// The secret phase is passed an argument when creating a wallet
// The keypair generated for a secret phrase is always the same
constructor(secret) {
this.keyPair = ChainUtil.genKeyPair(secret);
this.publicKey = this.keyPair.getPublic(“hex”);
}
// Used for prining the wallet details
toString() {
return `Wallet -
publicKey: ${this.publicKey.toString()}`;
}
// Used for signing data hashes
sign(dataHash) {
return this.keyPair.sign(dataHash).toHex();
}
// Creates and returns transactions
createTransaction(data) {
return new Transaction(data, this);
}
// Return public key
getPublicKey() {
return this.publicKey;
}
}
module.exports = Wallet;
pbft-wallet.js
驗證者類
由于PBFT是一種許可的區塊鏈一致性算法,我們需要存儲每個節點系統中所有節點的地址。我們可以通過選擇一個密鑰,創建一個錢包,獲取其公鑰并將該密鑰存儲到一個文件中來手動執行此操作。當我們運行我們的項目時,它會讀取此文件以獲取密鑰。
但是,不是手動執行此操作,我們可以通過創建類并添加可以返回N個節點的公鑰列表的函數來自動執行此操作。
我們將創建一個Validator類,它將生成每個節點都知道的公鑰列表。在這個項目中,我們使用了每個節點的密碼短語
NODE1,NODE2,NODE3 。..。..
這樣,我們就可以更容易地創建公鑰列表,并使用相同的公鑰從命令行創建節點。
// Import the wallet class
const Wallet = require(“。/wallet”);
class Validators {
// constructor will take an argument which is the number of nodes in the network
constructor(numberOfValidators) {
this.list = this.generateAddresses(numberOfValidators);
}
// This function generates wallets and their public key
// The secret key has been known for demonstration purposes
// Secret will be passed from command line to generate the same wallet again
// As a result the same public key will be generatedd
generateAddresses(numberOfValidators) {
let list = [];
for (let i = 0; i 《 numberOfValidators; i++) {
list.push(new Wallet(“NODE” + i).getPublicKey());
}
return list;
}
// This function verfies if the passed public key is a known validator or not
isValidValidator(validator) {
return this.list.includes(validator);
}
}
module.exports = Validators;
注意:密鑰不應該公開。只有在演示中我們才使用這樣的密鑰。在實際項目中,發送注冊請求以使節點成為驗證者。如果整個網絡批準此請求,則該節點將成為驗證者,并將公鑰添加到列表中。
Config.js文件
網絡中的驗證者數量可以通過命令行傳遞,但更容易將其存儲在文件中并在需要的地方導入。
創建一個文件config.js并創建三個變量NUMBER_OF_NODES,MIN_APPROVALS和TRANSACTION_THRESHOLD
// Maximum number of transactions that can be present in a block and transaction pool
const TRANSACTION_THRESHOLD = 5;
// total number of nodes in the network
const NUMBER_OF_NODES = 3;
// Minmu number of positive votes required for the message/block to be valid
const MIN_APPROVALS = 2 * (NUMBER_OF_NODES / 3) + 1;
module.exports = {
TRANSACTION_THRESHOLD,
NUMBER_OF_NODES,
MIN_APPROVALS
};
PBFT-config.js
評論
查看更多