1. 深入淺出區(qū)塊鏈技術(shù)

        共 15946字,需瀏覽 32分鐘

         ·

        2022-07-11 14:01

        術(shù)    


        本文為純粹區(qū)塊鏈技術(shù)分享,沒有任何投資建議。希望大家喜歡~

        一、故事導(dǎo)讀

        開始分享之前,引用自網(wǎng)上一個段子來引導(dǎo)大家。

        《小明的故事》

        小明是誰?小明是一名前端工程師,也是一個足球迷。

        他有一項神奇的技能:他對足球有很深的理解,能夠在每屆世界杯開賽之前準確預(yù)測出最終奪冠的球隊

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

        女朋友很自然地提出質(zhì)疑,而小明并沒有證據(jù)證明自己,只能啞口無言。

        小明痛定思痛,決定寫一個網(wǎng)站來提前記錄自己的預(yù)言。

        1. 小明自己設(shè)計了網(wǎng)頁界面。
        1. 找小伙伴幫忙寫了一個后端服務(wù),提供兩個接口。
        1. 小明基于這兩個接口,寫了一個純前端渲染的網(wǎng)站。

        最終網(wǎng)站看起來是這個樣子的:

        接下來,小明靜靜等待下一屆世界杯的到來。

        時間過得很快,轉(zhuǎn)眼到了 2014 年。這一次,小明再次正確預(yù)測出了冠軍得主。

        有網(wǎng)站記錄預(yù)言,小明心想,這次女朋友應(yīng)該會相信自己了吧!

        然而……

        女朋友也是懂技術(shù)的,她這次仍然提出了一個合理的質(zhì)疑。小明再次無言以對。

        那么問題來了,該怎么辦能夠讓女朋友相信自己呢?

        如果現(xiàn)在還有沒結(jié)論,可以繼續(xù)向下看。

        二、基礎(chǔ)概念

        區(qū)塊鏈技術(shù)中有很多新的概念,對于一些并不深入這個領(lǐng)域的同學(xué)來說,相對不是很友好。本文先對一些技術(shù)的概念進行講解。作為前置的知識。

        區(qū)塊鏈的概念

        特殊的分布式數(shù)據(jù)庫。

        一種鏈表結(jié)構(gòu),鏈表中元素作為一個區(qū)塊。而每個鏈表的結(jié)構(gòu)包括:

        • timestamp: 區(qū)塊產(chǎn)生時間戳
        • nonce: 與區(qū)塊頭的hash值共同證明計算量(工作量)
        • data: 區(qū)塊鏈上存儲的數(shù)據(jù)
        • previousHash: 上一個區(qū)塊的hash
        • hash: 本區(qū)塊鏈的hash,由上述幾個屬性進行哈希計算而得

        暫時無法在飛書文檔外展示此內(nèi)容

        一些特點

        1. 去中心化存儲

        分布式數(shù)據(jù)庫很早之前就已經(jīng)出現(xiàn),但與之不同的是區(qū)塊鏈是一個沒有管理者的、無中心化的分布式數(shù)據(jù)庫。其起初的設(shè)計目標就是防止出現(xiàn)位于中心地位的管理者當局。

        那么下一個問題就來了,如果沒有一個管理者進行數(shù)據(jù)的管理,如何保證這個分布式數(shù)據(jù)庫中的數(shù)據(jù)是可信任的呢?這就要提到下一個不可修改的特性了。

        1. 不可篡改

        區(qū)塊鏈上的數(shù)據(jù)是不可篡改的,大家都這樣說。但其實,數(shù)據(jù)是可以改的,只是說改了以后就你自己認,而且被修改數(shù)據(jù)所在區(qū)塊之后的所有區(qū)塊都會失效。區(qū)塊鏈網(wǎng)絡(luò)有一個同步邏輯,整個區(qū)塊鏈網(wǎng)絡(luò)總是保持所有節(jié)點使用最長的鏈,那么你修改完之后,一聯(lián)網(wǎng)同步,修改的東西又會被覆蓋。這是不可篡改的一個方面。

        更有意思的是,區(qū)塊鏈通過加密校驗,保證了數(shù)據(jù)存取需要經(jīng)過嚴格的驗證,而這些驗證幾乎又是不可偽造的,所以也很難篡改。加密并不代表不可篡改,但不可篡改是通過加密以及經(jīng)濟學(xué)原理搭配實現(xiàn)的。這還有點玄學(xué)的味道,一個純技術(shù)實現(xiàn)的東西,還要靠理論來維持。但事實就是這樣。這就是傳說中的挖礦。

        挖礦過程其實是礦工爭取創(chuàng)建一個區(qū)塊的過程,一旦挖到礦,也就代表這個礦工有資格創(chuàng)建新區(qū)塊。怎么算挖到礦呢?通過一系列復(fù)雜的加密算法,從0開始到∞,找到一個滿足難度的hash值,得到這個值,就是挖到礦。這個算法過程被稱為“共識機制”,也就是通過什么形式來決定誰擁有記賬權(quán),共識機制有很多種,區(qū)塊鏈采用哪種共識機制最佳,完全是由區(qū)塊鏈的實際目的結(jié)合經(jīng)濟學(xué)道理來選擇。

        除了這些,區(qū)塊鏈里面的加密比比皆是,這些加密規(guī)則和算法,使得整個區(qū)塊鏈遵循一種規(guī)律,讓篡改數(shù)據(jù)的成本特別高,以至于參與的人對篡改數(shù)據(jù)都沒有興趣,甚至忌憚。這又是玄學(xué)的地方。

        針對這些不可篡改的特性,我們是不是能夠解決一開始提出的問題呢。

        用js來寫一段區(qū)塊鏈的代碼,來解決小明的困惑。

        三、【實戰(zhàn)】用JavaScript來寫一個基本的區(qū)塊鏈demo。

        實現(xiàn)一個基本的區(qū)塊鏈

        1. 創(chuàng)建區(qū)塊

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

        那么我們?nèi)绾未_保數(shù)據(jù)的完整性呢?每個區(qū)塊都包含一個基于其內(nèi)容計算出來的hash。同時也包含了前一個區(qū)塊的hash。

        下面是一個區(qū)塊類用JavaScript寫出來大致的樣子:采用構(gòu)造函數(shù)初始化區(qū)塊的屬性。

        在這里的哈希值是無法修改的。我們能夠看到,哈希值是由多個元素組成的,一旦一個哈希值受到了修改,意味著previousHash被修改了,這個時候如果想要繼續(xù)修改就要對下一個區(qū)塊進行操作,否則修改的區(qū)塊就不具有意義了。而哈希值的計算非常耗時,同時修改51%以上的節(jié)點基本不可能,所以,這種聯(lián)動機制也就保證了其不可修改的特性。

        const crypto = require('crypto');

        class Block {

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

            // 計算區(qū)塊的哈希值
            calculateHash() {
              return crypto.createHash('sha256').update(this.previousHash + this.timestamp + JSON.stringify(this.data) + this.nonce).digest('hex');
            }
        }
        1. 創(chuàng)建鏈

        我們通過創(chuàng)建包含創(chuàng)世區(qū)塊的數(shù)組來初始化整個鏈。這樣一來,第一個區(qū)塊是特殊的,因為他并沒有指向前一個區(qū)塊。并且添加了兩個方法:

        • getLatestBlock()返回我們區(qū)塊鏈上最新的區(qū)塊。
        • addBlock()負責將新的區(qū)塊添加到我們的鏈上。為此,我們將前一個區(qū)塊的hash添加到我們新的區(qū)塊中。這樣我們就可以保持整個鏈的完整性。因為只要我們變更了最新區(qū)塊的內(nèi)容,我們就需要重新計算它的hash。當計算完成后,我將把這個區(qū)塊推進鏈里(一個數(shù)組)。

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

        class Blockchain{
            constructor() {
              this.chain = [this.createGenesisBlock()];
            }
          
            // 創(chuàng)建當前時間下的區(qū)塊(創(chuàng)世塊)
            createGenesisBlock() {
              return new Block(0, "20/05/2022""Genesis block""0");
            }
          
            // 獲得區(qū)塊鏈上最新的區(qū)塊
            getLatestBlock() {
              return this.chain[this.chain.length - 1];
            }
          
            // 將新的區(qū)塊添加到鏈上
            addBlock(newBlock) {
              newBlock.previousHash = this.getLatestBlock().hash;
              newBlock.hash = newBlock.calculateHash();
              this.chain.push(newBlock);
            }
          
            // 驗證區(qū)塊鏈是否被篡改。
            // 遍歷每個區(qū)塊的hash值是否正確&&每個區(qū)塊的指向previousHash是否正確。
            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;
            }
        }
        1. 使用

        我們的區(qū)塊鏈類已經(jīng)寫完啦,可以真正的開始使用它了!

        這里,我們創(chuàng)建了一個區(qū)塊鏈的實例,并在其中添加區(qū)塊。其中的數(shù)據(jù)就寫成了小明對于世界杯冠軍的預(yù)言。


        let firstClain = new Blockchain();
        firstClain.addBlock(new Block(0, "21/05/2022", { champion: 'Spain'}));
        firstClain.addBlock(new Block(1, "22/05/2022", { champion: 'China'}));

        // 檢查是否有效(將會返回true)
        console.log('firstClain valid? ' + firstClain.isChainValid(), firstClain.chain);

        // 現(xiàn)在嘗試操作變更數(shù)據(jù)
        firstClain.chain[1].data = {  champion: 'korea'  }; 

        // 再次檢查是否有效 (將會返回false)
        console.log("firstClain valid? " + firstClain.isChainValid(), firstClain.chain); 
        1. 嘗試修改數(shù)據(jù)

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

        之后我將鏈上的第一個(索引為1)區(qū)塊的數(shù)據(jù)進行了變更。之后我再次檢查整個鏈的完整性,發(fā)現(xiàn)它返回了false。我們的整個鏈不再有效了。

        // 檢查是否有效(將會返回true)
        console.log('firstClain valid? ' + firstClain.isChainValid(), firstClain.chain);

        // 現(xiàn)在嘗試操作變更數(shù)據(jù)
        firstClain.chain[1].data = {  champion: 'korea'  }; 

        // 再次檢查是否有效 (將會返回false)
        console.log("firstClain valid? " + firstClain.isChainValid(), firstClain.chain); 

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

        POW是在第一個區(qū)塊鏈被創(chuàng)造之前就已經(jīng)存在的一種機制。這是一項簡單的技術(shù),通過一定數(shù)量的計算來防止濫用。工作量是防止垃圾填充和篡改的關(guān)鍵。如果它需要大量的算力,那么填充垃圾就不再值得。

        比特幣通過要求hash以特定0的數(shù)目來實現(xiàn)POW。這也被稱之為難度

        不過等一下!一個區(qū)塊的hash怎么可以改變呢?在比特幣的場景下,一個區(qū)塊包含有各種金融交易信息。我們肯定不希望為了獲取正確的hash而混淆了那些數(shù)據(jù)。

        為了解決這個問題,區(qū)塊鏈添加了一個nonce值。Nonce是用來查找一個有效Hash的次數(shù)。而且,因為無法預(yù)測hash函數(shù)的輸出,因此在獲得滿足難度條件的hash之前,只能大量組合嘗試。尋找到一個有效的hash(創(chuàng)建一個新的區(qū)塊)在圈內(nèi)稱之為挖礦。

        在比特幣的場景下,POW確保每10分鐘只能添加一個區(qū)塊。你可以想象垃圾填充者需要多大的算力來創(chuàng)造一個新區(qū)塊,他們很難欺騙網(wǎng)絡(luò),更不要說篡改整個鏈。

        暫時無法在飛書文檔外展示此內(nèi)容

        我們該如何實現(xiàn)呢?我們先來修改我們區(qū)塊類并在其構(gòu)造函數(shù)中添加Nonce變量。我會初始化它并將其值設(shè)置為0。

          constructor(previousHash, timestamp, data) {
            this.previousHash = previousHash;
            this.timestamp = timestamp;
            this.data = data;
            // 工作量
            this.nonce = 0;
            this.hash = this.calculateHash();
         }

        我們還需要一個新的方法來增加Nonce,直到我們獲得一個有效hash。強調(diào)一下,這是由難度決定的。所以我們會收到作為參數(shù)的難度。

            // 工作量計算
            mineBlock(difficulty) {
              while (this.hash.substring(0, difficulty) !== Array(difficulty + 1).join('0')) {
                this.nonce++;
                this.hash = this.calculateHash();
            }

        最后,我們還需要更改一下calculateHash()函數(shù)。因為目前他還沒有使用Nonce來計算hash。

            // 計算區(qū)塊的哈希值
            calculateHash() {
              return crypto.createHash('sha256').update(this.previousHash + this.timestamp + JSON.stringify(this.data) + this.nonce).digest('hex');
            }

        將它們結(jié)合在一起,你會得到如下所示的區(qū)塊類:

        class Block {

          constructor(previousHash, timestamp, data) {
            this.previousHash = previousHash;
            this.timestamp = timestamp;
            this.data = data;
            // 工作量
            this.nonce = 0;
            this.hash = this.calculateHash();
          }

            // 計算區(qū)塊的哈希值
            calculateHash() {
              return crypto.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();
            }
          }
        }

        修改區(qū)塊鏈

        現(xiàn)在,我們的區(qū)塊已經(jīng)擁有Nonce并且可以被開采了,我們還需要確保我們的區(qū)塊鏈支持這種新的行為。讓我們先在區(qū)塊鏈中添加一個新的屬性來跟蹤整條鏈的難度。我會將它設(shè)置為2(這意味著區(qū)塊的hash必須以2個0開頭)。

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

        現(xiàn)在剩下要做的就是改變addBlock()方法,以便在將其添加到鏈中之前確保實際挖到該區(qū)塊。下面我們將難度傳給區(qū)塊。

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

        大功告成!我們的區(qū)塊鏈現(xiàn)在擁有了POW來抵御攻擊了。

        測試

        現(xiàn)在讓我們來測試一下我們的區(qū)塊鏈,看看在POW下添加一個新區(qū)塊會有什么效果。我將會使用之前的代碼。我們將創(chuàng)建一個新的區(qū)塊鏈實例然后往里添加2個區(qū)塊。

        let firstClain = new Blockchain();
        firstClain.addBlock(new Block(0, "21/05/2022", { champion: 'Spain'}));
        firstClain.addBlock(new Block(1, "22/05/2022", { champion: 'China'}));

        // 檢查是否有效(將會返回true)
        console.log('firstClain valid? ' + firstClain.isChainValid(), firstClain.chain);

        // 現(xiàn)在嘗試操作變更數(shù)據(jù)
        firstClain.chain[1].data = {  champion: 'korea'  }; 

        // 再次檢查是否有效 (將會返回false)
        console.log("firstClain valid? " + firstClain.isChainValid(), firstClain.chain); 

        如果你運行了上面的代碼,你會發(fā)現(xiàn)添加新區(qū)塊依舊非??臁_@是因為目前的難度只有2(或者你的電腦性能非常好)。

        如果你創(chuàng)建了一個難度為5的區(qū)塊鏈實例,你會發(fā)現(xiàn)你的電腦會花費大概十秒鐘來挖礦。隨著難度的提升,你的防御攻擊的保護程度越高。

        實際的難度系數(shù)與hash值

        上面計算hash的過程其實就是一個簡略版本的挖礦過程,也就是計算機來計算出一個相應(yīng)的hash值,但就像上面的所提及的并不是所有的hash都能夠滿足,這個條件比較苛刻,使得絕大多數(shù)的hash都不能夠滿足要求,需要重新計算。

        在區(qū)塊鏈的協(xié)議中,有一個標準的常量和一個目標值。只有小于目標值的hash才可以被使用。用常量除以難度系數(shù),可以得到目標值,顯然,難度系數(shù)越大,目標值越小。

        target = const / diffculty

        否則,hash無效只能重新計算,而nonce的大小就計算了相應(yīng)的工作量證明。

        整體代碼貼在下方

        const crypto = require('crypto');

        class Block {

          constructor(previousHash, timestamp, data) {
            this.previousHash = previousHash;
            this.timestamp = timestamp;
            this.data = data;
            // 工作量
            this.nonce = 0;
            this.hash = this.calculateHash();
          }

            // 計算區(qū)塊的哈希值
            calculateHash() {
              return crypto.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();
            }
          }
        }

        class Blockchain{
            constructor() {
              this.chain = [this.createGenesisBlock()];
              this.difficulty = 5;
            }
          
            // 創(chuàng)建當前時間下的區(qū)塊(創(chuàng)世塊)
            createGenesisBlock() {
              return new Block(0, "20/05/2022""Genesis block""0");
            }
          
            // 獲得區(qū)塊鏈上最新的區(qū)塊
            getLatestBlock() {
              return this.chain[this.chain.length - 1];
            }
          
            // 將新的區(qū)塊添加到鏈上
            addBlock(newBlock) {
              newBlock.previousHash = this.getLatestBlock().hash;
              newBlock.mineBlock(this.difficulty);
              this.chain.push(newBlock);
            }
          
            // 驗證區(qū)塊鏈是否被篡改。
            // 遍歷每個區(qū)塊的hash值是否正確&&每個區(qū)塊的指向previousHash是否正確。
            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;
            }
        }
          
        module.exports.Blockchain = Blockchain;
        module.exports.Block = Block;
        const { Block, Blockchain } = require('./block-chain');

        let firstClain = new Blockchain();
        firstClain.addBlock(new Block(0, "21/05/2022", { champion: 'Spain'}));
        firstClain.addBlock(new Block(1, "22/05/2022", { champion: 'China'}));

        // 檢查是否有效(將會返回true)
        console.log('firstClain valid? ' + firstClain.isChainValid(), firstClain.chain);

        // 現(xiàn)在嘗試操作變更數(shù)據(jù)
        firstClain.chain[1].data = {  champion: 'korea'  }; 

        // 再次檢查是否有效 (將會返回false)
        console.log("firstClain valid? " + firstClain.isChainValid(), firstClain.chain); 

        四、總結(jié)

        回到一開始的問題.

        小明用js用區(qū)塊鏈的形式在世界本的開始之前把預(yù)測的內(nèi)容存儲在了這里。并且成功預(yù)測.

        這一次,終于沒有之一,成功的在女朋友面前秀了一把。

        本文從一個小故事引出區(qū)塊鏈的相關(guān)內(nèi)容,其作為一門新的技術(shù)和思路,提供了一些不可篡改,分布式數(shù)據(jù)庫的觀念,并用前端的js代碼來寫了一個小的demo。

        當然其作為一種無人管理的不可隨意篡改的分布式數(shù)據(jù)庫確實沒有很大的問題,但也有一些弊端,首先是鏈表的結(jié)構(gòu)與hash值計算的困難導(dǎo)致其寫入是數(shù)據(jù)的效率并不高,需要一定的時間才能保證所有的節(jié)點同步。第二、區(qū)塊的計算所需要的一些無意義的計算,也是較為消耗能源的。

        最后本文作為純技術(shù)分享,無任何投資建議。希望大家喜歡~

        參考文章

        1. https://juejin.cn/post/6844903541903982606
        1. https://juejin.cn/post/6844903557649399821
        1. https://juejin.cn/post/6844903575617798157
        1. https://juejin.cn/post/6844903734837772301
        1. https://mp.weixin.qq.com/s/feo6YuBv4x-UcsLOooLGlA
        1. https://juejin.cn/post/6844903607343513613

        ?? 謝謝支持

        以上便是本次分享的全部內(nèi)容,希望對你有所幫助^_^

        喜歡的話別忘了 分享、點贊、收藏 三連哦~。

        歡迎關(guān)注公眾號 趣談前端 收貨大廠一手好文章~

        瀏覽 61
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
          
          

            1. 国产又爽又色 | 免费看男女做爰 | 国产婬片一级A片AAA毛片AⅤ | 各种少妇正面bbw撒尿免费看 | 97色涩 |