搜尋
首頁web前端js教程JavaScript實現區塊鏈

JavaScript實現區塊鏈

May 30, 2018 pm 02:26 PM
javascriptjs實現

很多朋友都聽過比特幣和以太幣這樣的加密貨幣,但是只有極少數人懂得隱藏在它們背後的技術,接下來透過本文給大家介紹用JavaScript來創建一個簡單的區塊鏈來示範它們的內部究竟是如何運作的,有興趣的朋友一起看看吧

幾乎每個人都聽說過像比特幣和以太幣這樣的加密貨幣,但是只有極少數人懂得隱藏在它們背後的技術。在這篇文中,我將會用JavaScript來創建一個簡單的區塊鏈來展示它們的內部究竟是如何運作的。我將會稱之為SavjeeCoin!

全文分為三個部分:

  1. part1:實作一個基本的區塊鏈

  2. part2:實作POW

  3. part3:交易與挖礦獎勵

#Part1:實作一個基本的區塊鏈

區塊鏈

區塊鏈是由一個任何人都可以存取的區塊所構成的公共資料庫。這好像沒什麼特別的,不過它們有一個有趣的屬性:它們是不可變的。一旦一個區塊被加入到區塊鏈中,除非讓剩餘的其餘區塊失效,否則它是不會再被改變的。

這就是為什麼加密貨幣是基於區塊鏈的原因。你肯定不希望人們在交易完成後再變更交易!

創造一個區塊

區塊鏈是由許許多多的區塊連結在一起的(這聽起來好像沒毛病..)。鏈上的區塊透過某種方式允許我們檢測到是否有人操縱了之前的任何區塊。

那我們要如何確保資料的完整性呢?每個區塊都包含一個基於其內容計算出來的hash。同時也包含了前一個區塊的hash。

下面是一個區塊類別用JavaScript寫出來大致的樣子:

const SHA256 = require("crypto-js/sha256");
class Block {
 constructor(index, timestamp, data, previousHash = '') {
 this.index = index;
 this.previousHash = previousHash;
 this.timestamp = timestamp;
 this.data = data;
 this.hash = this.calculateHash();
 }
 calculateHash() {
 return SHA256(this.index + this.previousHash + this.timestamp + JSON.stringify(this.data)).toString();
 }
}

因為JavaScript中並不支援sha256所以我引進了crypto-js 函式庫。然後我定義了一個建構函式來初始化我區塊的屬性。每一個區塊上都被賦予了 index 屬性來告知我們這個區塊在整個鏈上的位置。我們同時也產生了一個時間戳,以及一些需要在區塊裡儲存的資料。最後是前一個區塊的hash。

創造一個鏈

現在我們可以在Blockchain類別中將區塊連結起來了!以下是用JavaScript實作的程式碼:

class Blockchain{
 constructor() {
 this.chain = [this.createGenesisBlock()];
 }
 createGenesisBlock() {
 return new Block(0, "01/01/2017", "Genesis block", "0");
 }
 getLatestBlock() {
 return this.chain[this.chain.length - 1];
 }
 addBlock(newBlock) {
 newBlock.previousHash = this.getLatestBlock().hash;
 newBlock.hash = newBlock.calculateHash();
 this.chain.push(newBlock);
 }
 isChainValid() {
 for (let i = 1; i < this.chain.length; i++){
  const currentBlock = this.chain[i];
  const previousBlock = this.chain[i - 1];
  if (currentBlock.hash !== currentBlock.calculateHash()) {
  return false;
  }
  if (currentBlock.previousHash !== previousBlock.hash) {
  return false;
  }
 }
 return true;
 }
}

在建構函式裡,我透過建立一個包含創世區塊的陣列來初始化整個鏈。第一個區塊是特殊的,因為它不能指向前一個區塊。我還添加了下面兩個方法:

  • getLatestBlock() 返回我們區塊鏈上最新的區塊。

  • addBlock() 負責將新的區塊加入我們的鏈。為此,我們將前一個區塊的hash添加到我們新的區塊中。這樣我們就可以保持整個鏈的完整性。因為只要我們變更了最新區塊的內容,我們就需要重新計算它的hash。當計算完成後,我將把這個區塊推進鏈裡(一個陣列)。

最後,我創建一個 isChainValid() 來確保沒有人篡改過區塊鏈。它會遍歷所有的區塊來檢查每個區塊的hash是否正確。它會透過比較 previousHash 來檢查每個區塊是否指向正確的上一個區塊。如果一切都沒有問題它會回傳 true 否則會回傳 false 。

使用區塊鏈

我們的區塊鏈類別已經寫完啦,可以真正的開始使用它了!

let savjeeCoin = new Blockchain();
savjeeCoin.addBlock(new Block(1, "20/07/2017", { amount: 4 }));
savjeeCoin.addBlock(new Block(2, "20/07/2017", { amount: 8 }));

在這裡我只是創建了一個區塊鏈的實例,並且將它命名為SavjeeCoin!之後我在鏈上添加了一些區塊。區塊裡可以包含任何你想要放的數據,不過在上面的程式碼裡,我選擇加入了一個有 amount 屬性的物件。

試著操作吧!

在介紹裡我曾說過區塊鏈是不可變的。一旦添加,區塊就不可能再變更了。讓我們試試看!

// 检查是否有效(将会返回true)
console.log(&#39;Blockchain valid? &#39; + savjeeCoin.isChainValid());
// 现在尝试操作变更数据
savjeeCoin.chain[1].data = { amount: 100 };
// 再次检查是否有效 (将会返回false)
console.log("Blockchain valid? " + savjeeCoin.isChainValid());

我會在一開始透過執行 isChainValid() 來驗證整個鏈的完整性。我們操作過任何區塊,所以它會回傳true。

之後我將鏈上的第一個(索引為1)區塊的資料進行了變更。之後我再次檢查整個鏈的完整性,發現它回傳了false。我們的整個鏈不再有效了。

結論

這個小栗子還遠遠沒有達到完成的程度。它還沒有實現POW(工作量證明機制)或P2P網路來與其它礦工進行交流。

但他確實證明了區塊鏈的工作原理。許多人認為原理會非常複雜,但這篇文章證明了區塊鏈的基本概念是非常容易理解和實現的。

Part2:實作POW(proof-of-work:工作量證明)

在part1中我们用JavaScript创建了一个简单的区块链来演示区块链的工作原理。不过这个实现并不完整,很多人发现依旧可以篡改该系统。没错!我们的区块链需要另一种机制来抵御攻击。那么让我们来看看我们该如何做到这一点!

问题

现在我们可以很快的创造区块然后非常迅速的将它们添加进我们的区块链中。不过这导致了三个问题:

  • 第一:人们可以快速创建区块然后在我们的链里塞满垃圾。大量的区块会导致我们区块链过载并让其无法使用。

  • 第二:因为创建一个有效的区块太容易了,人们可以篡改链中的某一个区块,然后重新计算所有区块的hash。即使它们已经篡改了区块,他们仍然可以以有效的区块来作为结束。

  • 第三:你可以通过结合上述两个破绽来有效控制区块链。区块链由p2p网络驱动,其中节点会将区块添加到可用的最长链中。所以你可以篡改区块,然后计算所有其他的区块,最后添加多任意你想要添加的区块。你最后会得到一个最长的链,所有的其它节点都会接受它然后往上添加自己的区块。

显然我们需要一个方案来解决这些问题:POW。

什么是POW

POW是在第一个区块链被创造之前就已经存在的一种机制。这是一项简单的技术,通过一定数量的计算来防止滥用。工作量是防止垃圾填充和篡改的关键。如果它需要大量的算力,那么填充垃圾就不再值得。

比特币通过要求hash以特定0的数目来实现POW。这也被称之为 难度

不过等一下!一个区块的hash怎么可以改变呢?在比特币的场景下,一个区块包含有各种金融交易信息。我们肯定不希望为了获取正确的hash而混淆了那些数据。

为了解决这个问题,区块链添加了一个 nonce 值。Nonce是用来查找一个有效Hash的次数。而且,因为无法预测hash函数的输出,因此在获得满足难度条件的hash之前,只能大量组合尝试。寻找到一个有效的hash(创建一个新的区块)在圈内称之为挖矿。

在比特币的场景下,POW确保每10分钟只能添加一个区块。你可以想象垃圾填充者需要多大的算力来创造一个新区块,他们很难欺骗网络,更不要说篡改整个链。

实现POW

我们该如何实现呢?我们先来修改我们区块类并在其构造函数中添加Nonce变量。我会初始化它并将其值设置为0。

constructor(index, timestamp, data, previousHash = &#39;&#39;) {
 this.index = index;
 this.previousHash = previousHash;
 this.timestamp = timestamp;
 this.data = data;
 this.hash = this.calculateHash();
 this.nonce = 0;
}


我们还需要一个新的方法来增加Nonce,直到我们获得一个有效hash。强调一下,这是由难度决定的。所以我们会收到作为参数的难度。

mineBlock(difficulty) {
 while (this.hash.substring(0, difficulty) !== Array(difficulty + 1).join("0")) {
  this.nonce++;
  this.hash = this.calculateHash();
 }
 console.log("BLOCK MINED: " + this.hash);
}


最后,我们还需要更改一下 calculateHash() 函数。因为目前他还没有使用Nonce来计算hash。

calculateHash() {
 return SHA256(this.index +
 this.previousHash +
 this.timestamp +
 JSON.stringify(this.data) +
 this.nonce
 ).toString();
}

将它们结合在一起,你会得到如下所示的区块类:

class Block {
 constructor(index, timestamp, data, previousHash = &#39;&#39;) {
 this.index = index;
 this.previousHash = previousHash;
 this.timestamp = timestamp;
 this.data = data;
 this.hash = this.calculateHash();
 this.nonce = 0;
 }
 calculateHash() {
 return SHA256(this.index + this.previousHash + this.timestamp + JSON.stringify(this.data) + this.nonce).toString();
 }
 mineBlock(difficulty) {
 while (this.hash.substring(0, difficulty) !== Array(difficulty + 1).join("0")) {
  this.nonce++;
  this.hash = this.calculateHash();
 }
 console.log("BLOCK MINED: " + this.hash);
 }
}

修改区块链

现在,我们的区块已经拥有Nonce并且可以被开采了,我们还需要确保我们的区块链支持这种新的行为。让我们先在区块链中添加一个新的属性来跟踪整条链的难度。我会将它设置为2(这意味着区块的hash必须以2个0开头)。

constructor() {
 this.chain = [this.createGenesisBlock()];
 this.difficulty = 2;
}


现在剩下要做的就是改变 addBlock() 方法,以便在将其添加到链中之前确保实际挖到该区块。下面我们将难度传给区块。

addBlock(newBlock) {
 newBlock.previousHash = this.getLatestBlock().hash;
 newBlock.mineBlock(this.difficulty);
 this.chain.push(newBlock);
}


大功告成!我们的区块链现在拥有了POW来抵御攻击了。

测试

现在让我们来测试一下我们的区块链,看看在POW下添加一个新区块会有什么效果。我将会使用之前的代码。我们将创建一个新的区块链实例然后往里添加2个区块。

let savjeeCoin = new Blockchain();
console.log(&#39;Mining block 1&#39;);
savjeeCoin.addBlock(new Block(1, "20/07/2017", { amount: 4 }));
console.log(&#39;Mining block 2&#39;);
savjeeCoin.addBlock(new Block(2, "20/07/2017", { amount: 8 }));

如果你运行了上面的代码,你会发现添加新区块依旧非常快。这是因为目前的难度只有2(或者你的电脑性能非常好)。

如果你创建了一个难度为5的区块链实例,你会发现你的电脑会花费大概十秒钟来挖矿。随着难度的提升,你的防御攻击的保护程度越高。

免责声明

就像之前说的:这绝不是一个完整的区块链。它仍然缺少很多功能(像P2P网路)。这只是为了说明区块链的工作原理。

并且:由于单线程的原因,用JavaScript来挖矿并不快。

Part3:交易与挖矿奖励

在前面两部分我们创建了一个简单的区块链,并且加入了POW来抵御攻击。然而我们在途中也偷了懒:我们的区块链只能在一个区块中存储一笔交易,而且矿工没有奖励。现在,让我们解决这个问题!

重构区块类

现在一个区块拥有 index , previousHash , timestamp , data , hash 和 nonce 属性。这个 index 属性并不是很有用,事实上我甚至不知道为什么开始我要将它添加进去。所以我把它移除了,同时将 data 改名为 transactions 来更语义化。

class Block{
 constructor(timestamp, transactions, previousHash = &#39;&#39;) {
 this.previousHash = previousHash;
 this.timestamp = timestamp;
 this.transactions = transactions;
 this.hash = this.calculateHash();
 this.nonce = 0;
 }
}


当我们改变区块类时,我们也必须更改 calculateHash() 函数。现在它还在使用老旧的 index 和 data 属性。

calculateHash() {
 return SHA256(this.previousHash + this.timestamp + JSON.stringify(this.transactions) + this.nonce).toString();
}

交易类

在区块内,我们将可以存储多笔交易。因此我们还需要定义一个交易类,一边我们可以锁定交易应当具有的属性:

class Transaction{
 constructor(fromAddress, toAddress, amount){
 this.fromAddress = fromAddress;
 this.toAddress = toAddress;
 this.amount = amount;
 }
}

这个交易例子非常的简单,仅仅包含了发起方( fromAddress )和接受方( toAddress )以及数量。如果有需求,你也可以在里面加入更多字段,不过这个只是为了最小实现。

调整我们的区块链

当前的最大任务:调整我们的区块链来适应这些新变化。我们需要做的第一件事就是存储待处理交易的地方。

正如你所知道的,由于POW,区块链可以稳定的创建区块。在比特币的场景下,难度被设置成大约每10分钟创建一个新区块。但是,是可以在创造两个区块之间提交新的交易。

为了做到这一点,首先需要改变我们区块链的构造函数,以便他可以存储待处理的交易。我们还将创造一个新的属性,用于定义矿工获得多少钱作为奖励:

class Blockchain{
 constructor() {
  this.chain = [this.createGenesisBlock()];
  this.difficulty = 5;
  // 在区块产生之间存储交易的地方
  this.pendingTransactions = [];
  // 挖矿回报
  this.miningReward = 100;
 }
}


下一步,我们将调整我们的 addBlock() 方法。不过我的调整是指删掉并重写它!我们将不再允许人们直接为链上添加区块。相反,他们必须将交易添加至下一个区块中。而且我们将 addBlock() 更名为 createTransaction() ,这看起来更语义化:

createTransaction(transaction) {
 // 这里应该有一些校验!
 // 推入待处理交易数组
 this.pendingTransactions.push(transaction);
}


挖矿

人们现在可以将新的交易添加到待处理交易的列表中。但无论如何,我们需要将他们清理掉并移入实际的区块中。为此,我们来创建一个 minePendingTransactions() 方法。这个方法不仅会挖掘所有待交易的新区块,而且还会向采矿者发送奖励。

minePendingTransactions(miningRewardAddress) {
 // 用所有待交易来创建新的区块并且开挖..
 let block = new Block(Date.now(), this.pendingTransactions);
 block.mineBlock(this.difficulty);
 // 将新挖的看矿加入到链上
 this.chain.push(block);
 // 重置待处理交易列表并且发送奖励
 this.pendingTransactions = [
   new Transaction(null, miningRewardAddress, this.miningReward)
 ];
}


请注意,该方法采用了参数 miningRewardAddress 。如果你开始挖矿,你可以将你的钱包地址传递给此方法。一旦成功挖到矿,系统将创建一个新的交易来给你挖矿奖励(在这个栗子里是100枚币)。

有一点需要注意的是,在这个栗子中,我们将所有待处理交易一并添加到一个区块中。但实际上,由于区块的大小是有限制的,所以这是行不通的。在比特币里,一个区块的大小大概是2Mb。如果有更多的交易能够挤进一个区块,那么矿工可以选择哪些交易达成哪些交易不达成(通常情况下费用更高的交易容易获胜)。

地址的余额

在测试我们的代码钱让我们再做一件事!如果能够检查我们区块链上地址的余额将会更好。

getBalanceOfAddress(address){
 let balance = 0; // you start at zero!
 // 遍历每个区块以及每个区块内的交易
 for(const block of this.chain){
  for(const trans of block.transactions){
   // 如果地址是发起方 -> 减少余额
   if(trans.fromAddress === address){
    balance -= trans.amount;
   }
   // 如果地址是接收方 -> 增加余额
   if(trans.toAddress === address){
    balance += trans.amount;
   }
  }
 }
 return balance;
}

测试

好吧,我们已经完成并可以最终一切是否可以正常工作!为此,我们创建了一些交易:

let savjeeCoin = new Blockchain();
console.log(&#39;Creating some transactions...&#39;);
savjeeCoin.createTransaction(new Transaction(&#39;address1&#39;, &#39;address2&#39;, 100));
savjeeCoin.createTransaction(new Transaction(&#39;address2&#39;, &#39;address1&#39;, 50));

这些交易目前都处于等待状态,为了让他们得到证实,我们必须开始挖矿:

console.log(&#39;Starting the miner...&#39;);
savjeeCoin.minePendingTransactions(&#39;xaviers-address&#39;);

当我们开始挖矿,我们也会传递一个我们想要获得挖矿奖励的地址。在这种情况下,我的地址是 xaviers-address (非常复杂!)。

之后,让我们检查一下 xaviers-address 的账户余额:

console.log(&#39;Balance of Xaviers address is&#39;, savjeeCoin.getBalanceOfAddress(&#39;xaviers-address&#39;));
// 输出: 0


我的账户输出竟然是0?!等等,为什么?难道我不应该得到我的挖矿奖励么?那么,如果你仔细观察代码,你会看到系统会创建一个交易,然后将您的挖矿奖励添加为新的待处理交易。这笔交易将会包含在下一个区块中。所以如果我们再次开始挖矿,我们将收到我们的100枚硬币奖励!

console.log(&#39;Starting the miner again!&#39;);
savjeeCoin.minePendingTransactions("xaviers-address");
console.log(&#39;Balance of Xaviers address is&#39;, savjeeCoin.getBalanceOfAddress(&#39;xaviers-address&#39;));
// 输出: 100

局限性与结论

现在我们的区块链已经可以在一个区块上存储多笔交易,并且可以为矿工带来回报。

不過,還是有些不足:發送貨幣是,我們不檢查發起人是否有足夠的餘額來實際進行交易。然而,這其實是一件容易解決的事情。我們也沒有創建一個新的錢包和簽名交易(傳統上用公鑰/私鑰加密完成)。

上面是我整理給大家的,希望今後對大家有幫助。

相關文章:

vue-cli建立的專案,配置多頁面的實作方法

web3.js增加eth.getRawTransactionByHash(txhash)方法步驟

#nodejs簡單存取及操作mysql資料庫的方法範例

#

以上是JavaScript實現區塊鏈的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
JavaScript的角色:使網絡交互和動態JavaScript的角色:使網絡交互和動態Apr 24, 2025 am 12:12 AM

JavaScript是現代網站的核心,因為它增強了網頁的交互性和動態性。 1)它允許在不刷新頁面的情況下改變內容,2)通過DOMAPI操作網頁,3)支持複雜的交互效果如動畫和拖放,4)優化性能和最佳實踐提高用戶體驗。

C和JavaScript:連接解釋C和JavaScript:連接解釋Apr 23, 2025 am 12:07 AM

C 和JavaScript通過WebAssembly實現互操作性。 1)C 代碼編譯成WebAssembly模塊,引入到JavaScript環境中,增強計算能力。 2)在遊戲開發中,C 處理物理引擎和圖形渲染,JavaScript負責遊戲邏輯和用戶界面。

從網站到應用程序:JavaScript的不同應用從網站到應用程序:JavaScript的不同應用Apr 22, 2025 am 12:02 AM

JavaScript在網站、移動應用、桌面應用和服務器端編程中均有廣泛應用。 1)在網站開發中,JavaScript與HTML、CSS一起操作DOM,實現動態效果,並支持如jQuery、React等框架。 2)通過ReactNative和Ionic,JavaScript用於開發跨平台移動應用。 3)Electron框架使JavaScript能構建桌面應用。 4)Node.js讓JavaScript在服務器端運行,支持高並發請求。

Python vs. JavaScript:比較用例和應用程序Python vs. JavaScript:比較用例和應用程序Apr 21, 2025 am 12:01 AM

Python更適合數據科學和自動化,JavaScript更適合前端和全棧開發。 1.Python在數據科學和機器學習中表現出色,使用NumPy、Pandas等庫進行數據處理和建模。 2.Python在自動化和腳本編寫方面簡潔高效。 3.JavaScript在前端開發中不可或缺,用於構建動態網頁和單頁面應用。 4.JavaScript通過Node.js在後端開發中發揮作用,支持全棧開發。

C/C在JavaScript口譯員和編譯器中的作用C/C在JavaScript口譯員和編譯器中的作用Apr 20, 2025 am 12:01 AM

C和C 在JavaScript引擎中扮演了至关重要的角色,主要用于实现解释器和JIT编译器。1)C 用于解析JavaScript源码并生成抽象语法树。2)C 负责生成和执行字节码。3)C 实现JIT编译器,在运行时优化和编译热点代码,显著提高JavaScript的执行效率。

JavaScript在行動中:現實世界中的示例和項目JavaScript在行動中:現實世界中的示例和項目Apr 19, 2025 am 12:13 AM

JavaScript在現實世界中的應用包括前端和後端開發。 1)通過構建TODO列表應用展示前端應用,涉及DOM操作和事件處理。 2)通過Node.js和Express構建RESTfulAPI展示後端應用。

JavaScript和Web:核心功能和用例JavaScript和Web:核心功能和用例Apr 18, 2025 am 12:19 AM

JavaScript在Web開發中的主要用途包括客戶端交互、表單驗證和異步通信。 1)通過DOM操作實現動態內容更新和用戶交互;2)在用戶提交數據前進行客戶端驗證,提高用戶體驗;3)通過AJAX技術實現與服務器的無刷新通信。

了解JavaScript引擎:實施詳細信息了解JavaScript引擎:實施詳細信息Apr 17, 2025 am 12:05 AM

理解JavaScript引擎內部工作原理對開發者重要,因為它能幫助編寫更高效的代碼並理解性能瓶頸和優化策略。 1)引擎的工作流程包括解析、編譯和執行三個階段;2)執行過程中,引擎會進行動態優化,如內聯緩存和隱藏類;3)最佳實踐包括避免全局變量、優化循環、使用const和let,以及避免過度使用閉包。

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

SublimeText3 英文版

SublimeText3 英文版

推薦:為Win版本,支援程式碼提示!

SublimeText3 Linux新版

SublimeText3 Linux新版

SublimeText3 Linux最新版

WebStorm Mac版

WebStorm Mac版

好用的JavaScript開發工具

mPDF

mPDF

mPDF是一個PHP庫,可以從UTF-8編碼的HTML產生PDF檔案。原作者Ian Back編寫mPDF以從他的網站上「即時」輸出PDF文件,並處理不同的語言。與原始腳本如HTML2FPDF相比,它的速度較慢,並且在使用Unicode字體時產生的檔案較大,但支援CSS樣式等,並進行了大量增強。支援幾乎所有語言,包括RTL(阿拉伯語和希伯來語)和CJK(中日韓)。支援嵌套的區塊級元素(如P、DIV),