本文為純粹區塊鏈技術分享,沒有任何投資建議。希望大家喜歡~
一、故事導讀開始分享之前,引用自網上一個段子來引導大家。
《小明的故事》
小明是誰?小明是一名前端工程師,也是一個足球迷。
他有一項神奇的技能:他對足球有很深的理解,能夠在每屆世界盃開賽之前準確預測出最終奪冠的球隊

比如,在 2010 年的那屆世界盃,小明就預測出了正確的結果。大賽閉幕,小明難掩興奮之情,想在女朋友面前顯擺一下。

女朋友很自然地提出質疑,而小明並沒有證據證明自己,只能啞口無言。
小明痛定思痛,決定寫一個網站來提前記錄自己的預言。
最終網站看起來是這個樣子的:

接下來,小明靜靜等待下一屆世界盃的到來。
時間過得很快,轉眼到了 2014 年。這一次,小明再次正確預測出了冠軍得主。
有網站記錄預言,小明心想,這次女朋友應該會相信自己了吧!
然而……

女朋友也是懂技術的,她這次仍然提出了一個合理的質疑。小明再次無言以對。
那麼問題來了,該怎麼辦能夠讓女朋友相信自己呢?
如果現在還有沒結論,可以繼續向下看。
二、基礎概念區塊鏈技術中有很多新的概念,對於一些並不深入這個領域的同學來說,相對不是很友好。本文先對一些技術的概念進行講解。作為前置的知識。
區塊鏈的概念特殊的分布式數據庫。

一種鍊表結構,鍊表中元素作為一個區塊。而每個鍊表的結構包括:
nonce: 與區塊頭的hash值共同證明計算量(工作量)hash: 本區塊鏈的hash,由上述幾個屬性進行哈希計算而得暫時無法在飛書文檔外展示此內容
一些特點分布式數據庫很早之前就已經出現,但與之不同的是區塊鏈是一個沒有管理者的、無中心化的分布式數據庫。其起初的設計目標就是防止出現位於中心地位的管理者當局。
那麼下一個問題就來了,如果沒有一個管理者進行數據的管理,如何保證這個分布式數據庫中的數據是可信任的呢?這就要提到下一個不可修改的特性了。

區塊鏈上的數據是不可篡改的,大家都這樣說。但其實,數據是可以改的,只是說改了以後就你自己認,而且被修改數據所在區塊之後的所有區塊都會失效。區塊鏈網絡有一個同步邏輯,整個區塊鏈網絡總是保持所有節點使用最長的鏈,那麼你修改完之後,一聯網同步,修改的東西又會被覆蓋。這是不可篡改的一個方面。
更有意思的是,區塊鏈通過加密校驗,保證了數據存取需要經過嚴格的驗證,而這些驗證幾乎又是不可偽造的,所以也很難篡改。加密並不代表不可篡改,但不可篡改是通過加密以及經濟學原理搭配實現的。這還有點玄學的味道,一個純技術實現的東西,還要靠理論來維持。但事實就是這樣。這就是傳說中的挖礦。
挖礦過程其實是礦工爭取創建一個區塊的過程,一旦挖到礦,也就代表這個礦工有資格創建新區塊。怎麼算挖到礦呢?通過一系列複雜的加密算法,從0開始到∞,找到一個滿足難度的hash值,得到這個值,就是挖到礦。這個算法過程被稱為「共識機制」,也就是通過什麼形式來決定誰擁有記賬權,共識機制有很多種,區塊鏈採用哪種共識機制最佳,完全是由區塊鏈的實際目的結合經濟學道理來選擇。
除了這些,區塊鏈裡面的加密比比皆是,這些加密規則和算法,使得整個區塊鏈遵循一種規律,讓篡改數據的成本特別高,以至於參與的人對篡改數據都沒有興趣,甚至忌憚。這又是玄學的地方。
針對這些不可篡改的特性,我們是不是能夠解決一開始提出的問題呢。
用js來寫一段區塊鏈的代碼,來解決小明的困惑。
三、【實戰】用JavaScript來寫一個基本的區塊鏈demo。實現一個基本的區塊鏈區塊鏈是由許許多多的區塊鏈接在一起的(這聽上去好像沒毛病..)。鏈上的區塊通過某種方式允許我們檢測到是否有人操縱了之前的任何區塊。
那麼我們如何確保數據的完整性呢?每個區塊都包含一個基於其內容計算出來的hash。同時也包含了前一個區塊的hash。
下面是一個區塊類用JavaScript寫出來大致的樣子:採用構造函數初始化區塊的屬性。
在這裡的哈希值是無法修改的。我們能夠看到,哈希值是由多個元素組成的,一旦一個哈希值受到了修改,意味着previousHash被修改了,這個時候如果想要繼續修改就要對下一個區塊進行操作,否則修改的區塊就不具有意義了。而哈希值的計算非常耗時,同時修改51%以上的節點基本不可能,所以,這種聯動機制也就保證了其不可修改的特性。
constcrypto=require('crypto');classBlock{constructor(previousHash,timestamp,data){this.previousHash=previousHash;this.timestamp=timestamp;this.data=data;this.nonce=0;this.hash=this.calculateHash();}//計算區塊的哈希值calculateHash(){returncrypto.createHash('sha256').update(this.previousHash+this.timestamp+JSON.stringify(this.data)+this.nonce).digest('hex');}}我們通過創建包含創世區塊的數組來初始化整個鏈。這樣一來,第一個區塊是特殊的,因為他並沒有指向前一個區塊。並且添加了兩個方法:
getLatestBlock()返回我們區塊鏈上最新的區塊。addBlock()負責將新的區塊添加到我們的鏈上。為此,我們將前一個區塊的hash添加到我們新的區塊中。這樣我們就可以保持整個鏈的完整性。因為只要我們變更了最新區塊的內容,我們就需要重新計算它的hash。當計算完成後,我將把這個區塊推進鏈里(一個數組)。最後,我創建一個isChainValid()來確保沒有人篡改過區塊鏈。它會遍歷所有的區塊來檢查每個區塊的hash是否正確。它會通過比較previousHash來檢查每個區塊是否指向正確的上一個區塊。如果一切都沒有問題它會返回true否則會返回false。
classBlockchain{constructor(){this.chain=[this.createGenesisBlock()];}//創建當前時間下的區塊(創世塊)createGenesisBlock(){returnnewBlock(0,"20/05/2022","Genesisblock","0");}//獲得區塊鏈上最新的區塊getLatestBlock(){returnthis.chain[this.chain.length-1];}//將新的區塊添加到鏈上addBlock(newBlock){newBlock.previousHash=this.getLatestBlock().hash;newBlock.hash=newBlock.calculateHash();this.chain.push(newBlock);}//驗證區塊鏈是否被篡改。//遍歷每個區塊的hash值是否正確&&每個區塊的指向previousHash是否正確。isChainValid(){for(leti=1;i<this.chain.length;i++){constcurrentBlock=this.chain[i];constpreviousBlock=this.chain[i-1];if(currentBlock.hash!==currentBlock.calculateHash()){returnfalse;}if(currentBlock.previousHash!==previousBlock.hash){returnfalse;}}returntrue;}}我們的區塊鏈類已經寫完啦,可以真正的開始使用它了!
這裡,我們創建了一個區塊鏈的實例,並在其中添加區塊。其中的數據就寫成了小明對於世界盃冠軍的預言。
letfirstClain=newBlockchain();firstClain.addBlock(newBlock(0,"21/05/2022",{champion:'Spain'}));firstClain.addBlock(newBlock(1,"22/05/2022",{champion:'China'}));//檢查是否有效(將會返回true)console.log('firstClainvalid?'+firstClain.isChainValid(),firstClain.chain);//現在嘗試操作變更數據firstClain.chain[1].data={champion:'korea'};//再次檢查是否有效(將會返回false)console.log("firstClainvalid?"+firstClain.isChainValid(),firstClain.chain);我會在一開始通過運行isChainValid()來驗證整個鏈的完整性。我們操作過任何區塊,所以它會返回true。
之後我將鏈上的第一個(索引為1)區塊的數據進行了變更。之後我再次檢查整個鏈的完整性,發現它返回了false。我們的整個鏈不再有效了。
//檢查是否有效(將會返回true)console.log('firstClainvalid?'+firstClain.isChainValid(),firstClain.chain);//現在嘗試操作變更數據firstClain.chain[1].data={champion:'korea'};//再次檢查是否有效(將會返回false)console.log("firstClainvalid?"+firstClain.isChainValid(),firstClain.chain);POW(proof-of-work)工作量證明POW是在第一個區塊鏈被創造之前就已經存在的一種機制。這是一項簡單的技術,通過一定數量的計算來防止濫用。工作量是防止垃圾填充和篡改的關鍵。如果它需要大量的算力,那麼填充垃圾就不再值得。
比特幣通過要求hash以特定0的數目來實現POW。這也被稱之為難度
不過等一下!一個區塊的hash怎麼可以改變呢?在比特幣的場景下,一個區塊包含有各種金融交易信息。我們肯定不希望為了獲取正確的hash而混淆了那些數據。
為了解決這個問題,區塊鏈添加了一個nonce值。Nonce是用來查找一個有效Hash的次數。而且,因為無法預測hash函數的輸出,因此在獲得滿足難度條件的hash之前,只能大量組合嘗試。尋找到一個有效的hash(創建一個新的區塊)在圈內稱之為挖礦。
在比特幣的場景下,POW確保每10分鐘只能添加一個區塊。你可以想象垃圾填充者需要多大的算力來創造一個新區塊,他們很難欺騙網絡,更不要說篡改整個鏈。
暫時無法在飛書文檔外展示此內容
我們該如何實現呢?我們先來修改我們區塊類並在其構造函數中添加Nonce變量。我會初始化它並將其值設置為0。
constructor(previousHash,timestamp,data){this.previousHash=previousHash;this.timestamp=timestamp;this.data=data;//工作量this.nonce=0;this.hash=this.calculateHash();}我們還需要一個新的方法來增加Nonce,直到我們獲得一個有效hash。強調一下,這是由難度決定的。所以我們會收到作為參數的難度。
//工作量計算mineBlock(difficulty){while(this.hash.substring(0,difficulty)!==Array(difficulty+1).join('0')){this.nonce++;this.hash=this.calculateHash();}最後,我們還需要更改一下calculateHash()函數。因為目前他還沒有使用Nonce來計算hash。
//計算區塊的哈希值calculateHash(){returncrypto.createHash('sha256').update(this.previousHash+this.timestamp+JSON.stringify(this.data)+this.nonce).digest('hex');}將它們結合在一起,你會得到如下所示的區塊類:
classBlock{constructor(previousHash,timestamp,data){this.previousHash=previousHash;this.timestamp=timestamp;this.data=data;//工作量this.nonce=0;this.hash=this.calculateHash();}//計算區塊的哈希值calculateHash(){returncrypto.createHash('sha256').update(this.previousHash+this.timestamp+JSON.stringify(this.data)+this.nonce).digest('hex');}//工作量計算mineBlock(difficulty){while(this.hash.substring(0,difficulty)!==Array(difficulty+1).join('0')){this.nonce++;this.hash=this.calculateHash();}}}修改區塊鏈現在,我們的區塊已經擁有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個區塊。
letfirstClain=newBlockchain();firstClain.addBlock(newBlock(0,"21/05/2022",{champion:'Spain'}));firstClain.addBlock(newBlock(1,"22/05/2022",{champion:'China'}));//檢查是否有效(將會返回true)console.log('firstClainvalid?'+firstClain.isChainValid(),firstClain.chain);//現在嘗試操作變更數據firstClain.chain[1].data={champion:'korea'};//再次檢查是否有效(將會返回false)console.log("firstClainvalid?"+firstClain.isChainValid(),firstClain.chain);如果你運行了上面的代碼,你會發現添加新區塊依舊非常快。這是因為目前的難度只有2(或者你的電腦性能非常好)。
如果你創建了一個難度為5的區塊鏈實例,你會發現你的電腦會花費大概十秒鐘來挖礦。隨着難度的提升,你的防禦攻擊的保護程度越高。
實際的難度係數與hash值上面計算hash的過程其實就是一個簡略版本的挖礦過程,也就是計算機來計算出一個相應的hash值,但就像上面的所提及的並不是所有的hash都能夠滿足,這個條件比較苛刻,使得絕大多數的hash都不能夠滿足要求,需要重新計算。
在區塊鏈的協議中,有一個標準的常量和一個目標值。只有小於目標值的hash才可以被使用。用常量除以難度係數,可以得到目標值,顯然,難度係數越大,目標值越小。
target=const/diffculty否則,hash無效只能重新計算,而nonce的大小就計算了相應的工作量證明。
整體代碼貼在下方
constcrypto=require('crypto');classBlock{constructor(previousHash,timestamp,data){this.previousHash=previousHash;this.timestamp=timestamp;this.data=data;//工作量this.nonce=0;this.hash=this.calculateHash();}//計算區塊的哈希值calculateHash(){returncrypto.createHash('sha256').update(this.previousHash+this.timestamp+JSON.stringify(this.data)+this.nonce).digest('hex');}//工作量計算mineBlock(difficulty){while(this.hash.substring(0,difficulty)!==Array(difficulty+1).join('0')){this.nonce++;this.hash=this.calculateHash();}}}classBlockchain{constructor(){this.chain=[this.createGenesisBlock()];this.difficulty=5;}//創建當前時間下的區塊(創世塊)createGenesisBlock(){returnnewBlock(0,"20/05/2022","Genesisblock","0");}//獲得區塊鏈上最新的區塊getLatestBlock(){returnthis.chain[this.chain.length-1];}//將新的區塊添加到鏈上addBlock(newBlock){newBlock.previousHash=this.getLatestBlock().hash;newBlock.mineBlock(this.difficulty);this.chain.push(newBlock);}//驗證區塊鏈是否被篡改。//遍歷每個區塊的hash值是否正確&&每個區塊的指向previousHash是否正確。isChainValid(){for(leti=1;i<this.chain.length;i++){constcurrentBlock=this.chain[i];constpreviousBlock=this.chain[i-1];if(currentBlock.hash!==currentBlock.calculateHash()){returnfalse;}if(currentBlock.previousHash!==previousBlock.hash){returnfalse;}}returntrue;}}module.exports.Blockchain=Blockchain;module.exports.Block=Block;const{Block,Blockchain}=require('./block-chain');letfirstClain=newBlockchain();firstClain.addBlock(newBlock(0,"21/05/2022",{champion:'Spain'}));firstClain.addBlock(newBlock(1,"22/05/2022",{champion:'China'}));//檢查是否有效(將會返回true)console.log('firstClainvalid?'+firstClain.isChainValid(),firstClain.chain);//現在嘗試操作變更數據firstClain.chain[1].data={champion:'korea'};//再次檢查是否有效(將會返回false)console.log("firstClainvalid?"+firstClain.isChainValid(),firstClain.chain);四、總結回到一開始的問題.
小明用js用區塊鏈的形式在世界本的開始之前把預測的內容存儲在了這裡。並且成功預測.

這一次,終於沒有之一,成功的在女朋友面前秀了一把。
本文從一個小故事引出區塊鏈的相關內容,其作為一門新的技術和思路,提供了一些不可篡改,分布式數據庫的觀念,並用前端的js代碼來寫了一個小的demo。
當然其作為一種無人管理的不可隨意篡改的分布式數據庫確實沒有很大的問題,但也有一些弊端,首先是鍊表的結構與hash值計算的困難導致其寫入是數據的效率並不高,需要一定的時間才能保證所有的節點同步。第二、區塊的計算所需要的一些無意義的計算,也是較為消耗能源的。
最後本文作為純技術分享,無任何投資建議。希望大家喜歡~
參考文章https://juejin.cn/post/6844903541903982606https://juejin.cn/post/6844903557649399821https://juejin.cn/post/6844903575617798157https://juejin.cn/post/6844903734837772301https://mp.weixin.qq.com/s/feo6YuBv4x-UcsLOooLGlAhttps://juejin.cn/post/6844903607343513613❤️謝謝支持