使用node.js+ts開(kāi)發(fā)一個(gè)加密貨幣01

#1: 一個(gè)小型可工作區(qū)塊鏈

概述

區(qū)塊鏈的基本概念很簡(jiǎn)單:一個(gè)分布式數(shù)據(jù)庫(kù),保持不斷增長(zhǎng)的命令列表記錄惧辈。在這一章里,我們將實(shí)現(xiàn)這樣的區(qū)塊鏈精簡(jiǎn)具版本帅容。最后一章我們將在區(qū)塊鏈中加入以下基本功能:

  • 定義一個(gè)區(qū)塊休弃,并構(gòu)造區(qū)塊鏈

  • 實(shí)現(xiàn)一個(gè)能帶任意數(shù)據(jù)并形成新區(qū)塊的方法

  • 區(qū)塊鏈的節(jié)點(diǎn)之間的通信與同步

  • 一個(gè)簡(jiǎn)單的HTTP API控制節(jié)點(diǎn)

本章中實(shí)現(xiàn)的完整代碼,可以在這里找到盅抚。


區(qū)塊的結(jié)構(gòu)

我們需要定義塊的基本結(jié)構(gòu)房待,這里只定義了一些最基本的屬性

  • index:區(qū)塊的高度

  • data:任何塊中包含的數(shù)據(jù)

  • timestamp:一個(gè)時(shí)間戳

  • hash:通過(guò)sha256加密塊的內(nèi)容得到的hash

  • previousHash:一個(gè)散列引用前面的塊怔球。 這個(gè)值顯式地定義了前面的塊

image

就像下面的代碼一樣:

class Block {

    public index: number; //區(qū)塊的索引
    public hash: string; //區(qū)塊的hash
    public previousHash: string; //前一個(gè)區(qū)塊的hash
    public timestamp: number; //生成區(qū)塊的時(shí)間戳
    public data: string; //交易數(shù)據(jù)

    constructor(index: number, hash: string, previousHash: string, timestamp: number, data: string) {
        this.index = index;
        this.previousHash = previousHash;
        this.timestamp = timestamp;
        this.data = data;
        this.hash = hash;
    }
}

區(qū)塊的哈希(hash)

區(qū)塊的hash是重要的屬性之一嚼酝,hash是通過(guò)區(qū)塊的所有數(shù)據(jù)計(jì)算得到。這就意味著任何hash改變竟坛,原來(lái)的hash都會(huì)失效闽巩。hash也可以認(rèn)為是一個(gè)區(qū)塊的唯一標(biāo)識(shí)符。

我們通過(guò)下面的代碼計(jì)算得到hash

const calculateHash = (index: number, previousHash: string, timestamp: number, data: string): string =>
    CryptoJS.SHA256(index + previousHash + timestamp + data).toString();

應(yīng)該注意,hash還不能被挖掘,因?yàn)檫€沒(méi)有使用proof-of-work來(lái)解決担汤。我們用hash來(lái)保存完整的塊和顯式地引用前面的塊涎跨。

一個(gè)重要的一點(diǎn),一個(gè)區(qū)塊中==hash==和==previousHash==不能被改變崭歧,除非改變所有的區(qū)塊隅很。

在下面的例子中,如果第44區(qū)塊的數(shù)據(jù)從==desert==變?yōu)?=street==率碾,所有的區(qū)塊都應(yīng)該全部改變外构。這是由于==hash==依賴于==previoushash==普泡。(除此之外)

image

當(dāng)介紹POW(工作量證明)時(shí),這是一個(gè)重要的概念审编,區(qū)塊的高度越高撼班,就越難被修改,因?yàn)橐薷乃羞B續(xù)的區(qū)塊垒酬。


創(chuàng)世區(qū)塊

創(chuàng)世區(qū)塊是區(qū)塊鏈的第一條區(qū)塊砰嘁,這是唯一一個(gè)沒(méi)有previoushash的區(qū)塊。我們將通過(guò)編碼生成第一條區(qū)塊勘究。

const genesisBlock: Block = new Block(
    0, '816534932c2b7154836da6afc367695e6337db8a921823784c14378abed4f7d7', null, 1465154705, 'my genesis block!!'
);

生成一個(gè)區(qū)塊

生成一個(gè)區(qū)塊之前矮湘,我們先需要知道前一個(gè)區(qū)塊的hash和當(dāng)前區(qū)塊的索引、hash口糕、數(shù)據(jù)缅阳、時(shí)間戳。區(qū)塊的數(shù)據(jù)將由最終的用戶給定景描。代碼如下十办。

const generateNextBlock = (blockData: string) => {
    const previousBlock: Block = getLatestBlock();
    const nextIndex: number = previousBlock.index + 1;
    const nextTimestamp: number = new Date().getTime() / 1000;
    const nextHash: string = calculateHash(nextIndex, previousBlock.hash, nextTimestamp, blockData);
    const newBlock: Block = new Block(nextIndex, nextHash, previousBlock.hash, nextTimestamp, blockData);
    return newBlock;
};

區(qū)塊的存儲(chǔ)

現(xiàn)在我們將通過(guò)JavaScript的數(shù)組來(lái)保存區(qū)塊鏈,這就意味著當(dāng)節(jié)點(diǎn)結(jié)束時(shí)超棺,數(shù)據(jù)不會(huì)被保存向族。

const blockchain: Block[] = [genesisBlock];

驗(yàn)證區(qū)塊的完整性

在必要時(shí)候,我們驗(yàn)證一個(gè)區(qū)塊的完整性或者整個(gè)區(qū)塊鏈的完整性棠绘,我們就要從其他節(jié)點(diǎn)接受區(qū)塊件相,并選擇接受與否。

一個(gè)區(qū)塊的有效性必須滿足一下條件

  • 塊的索引必須是一個(gè)數(shù)字且比前一個(gè)區(qū)塊的索引大
  • 區(qū)塊的previousHash能匹配前一個(gè)塊的hash
  • 區(qū)塊本身的hash必須是有效的

下面通過(guò)代碼演示這點(diǎn)

const isValidNewBlock = (newBlock: Block, previousBlock: Block) => {
    if (previousBlock.index + 1 !== newBlock.index) {
        console.log('invalid index');
        return false;
    } else if (previousBlock.hash !== newBlock.previousHash) {
        console.log('invalid previoushash');
        return false;
    } else if (calculateHashForBlock(newBlock) !== newBlock.hash) {
        console.log(typeof (newBlock.hash) + ' ' + typeof calculateHashForBlock(newBlock));
        console.log('invalid hash: ' + calculateHashForBlock(newBlock) + ' ' + newBlock.hash);
        return false;
    }
    return true;
};

我們還必須驗(yàn)證區(qū)塊的結(jié)構(gòu):

const isValidBlockStructure = (block: Block): boolean => {
    return typeof block.index === 'number'
        && typeof block.hash === 'string'
        && typeof block.previousHash === 'string'
        && typeof block.timestamp === 'number'
        && typeof block.data === 'string';
};

到這里我們已經(jīng)驗(yàn)證了一個(gè)單獨(dú)的區(qū)塊氧苍,現(xiàn)在我們驗(yàn)證整個(gè)區(qū)塊鏈夜矗。首先我們要檢驗(yàn)第一個(gè)區(qū)塊==genesisBlock==,后面我們用同意的方法來(lái)驗(yàn)證后面所有連續(xù)的塊让虐。下面是給出的代碼侯养。

const isValidChain = (blockchainToValidate: Block[]): boolean => {
    const isValidGenesis = (block: Block): boolean => {
        return JSON.stringify(block) === JSON.stringify(genesisBlock);
    };

    if (!isValidGenesis(blockchainToValidate[0])) {
        return false;
    }

    for (let i = 1; i < blockchainToValidate.length; i++) {
        if (!isValidNewBlock(blockchainToValidate[i], blockchainToValidate[i - 1])) {
            return false;
        }
    }
    return true;
};

選擇最長(zhǎng)的區(qū)塊鏈

在一定時(shí)間內(nèi),只有一條鏈?zhǔn)呛戏ǖ某胃伞T跊_突的情況下(例如,有兩個(gè)節(jié)點(diǎn)生成塊編號(hào)72)我們應(yīng)該選擇最長(zhǎng)的區(qū)塊鏈逛揩。 在以下示例中,區(qū)塊72:a350235b00不會(huì)區(qū)塊鏈中,因?yàn)樗鼘⒂砷L(zhǎng)鏈覆蓋。

image

以下是代碼實(shí)現(xiàn):

const replaceChain = (newBlocks: Block[]) => {
    if (isValidChain(newBlocks) && newBlocks.length > getBlockchain().length) {
        console.log('Received blockchain is valid. Replacing current blockchain with received blockchain');
        blockchain = newBlocks;
        broadcastLatest();
    } else {
        console.log('Received blockchain invalid');
    }
};

節(jié)點(diǎn)間的通信

一個(gè)節(jié)點(diǎn)的基本功能是與其他節(jié)點(diǎn)共享并同最新的區(qū)塊鏈麸俘,下面的規(guī)則是用于同步辩稽。

  • 當(dāng)一個(gè)節(jié)點(diǎn)生成一個(gè)新的塊,將它廣播到網(wǎng)絡(luò)中
  • 當(dāng)一個(gè)節(jié)點(diǎn)連接到一個(gè)新的廣播時(shí)同步最新的區(qū)塊塊
  • 當(dāng)一個(gè)節(jié)點(diǎn)遇到一塊索引大于當(dāng)前已知的塊,將放棄當(dāng)前的鏈并同步最新的鏈。


    image

    我們將使用websockets作為點(diǎn)對(duì)點(diǎn)通信从媚。 積極為每個(gè)節(jié)點(diǎn)存儲(chǔ)在套接字const sockets: WebSocket[]變量逞泄。 不使用自動(dòng)對(duì)等的發(fā)現(xiàn)。 同行的位置(= Websocket url)必須手動(dòng)添加。

控制節(jié)點(diǎn)

用戶必須通過(guò)一些方式控制節(jié)點(diǎn)喷众,這是設(shè)置一個(gè)http服務(wù)器各谚。

const initHttpServer = ( myHttpPort: number ) => {
    const app = express();
    app.use(bodyParser.json());

    app.get('/blocks', (req, res) => {
        res.send(getBlockchain());
    });
    app.post('/mineBlock', (req, res) => {
        const newBlock: Block = generateNextBlock(req.body.data);
        res.send(newBlock);
    });
    app.get('/peers', (req, res) => {
        res.send(getSockets().map(( s: any ) => s._socket.remoteAddress + ':' + s._socket.remotePort));
    });
    app.post('/addPeer', (req, res) => {
        connectToPeers(req.body.peer);
        res.send();
    });

    app.listen(myHttpPort, () => {
        console.log('Listening http on port: ' + myHttpPort);
    });
};

可以看到,用戶可以通過(guò)節(jié)點(diǎn)實(shí)現(xiàn)以下功能

  • 列出所有塊
  • 創(chuàng)建一個(gè)新的塊內(nèi)容
  • 列表或添加平行節(jié)點(diǎn)

最直接的方式通過(guò)curl來(lái)控制節(jié)點(diǎn)

#get all blocks from the node
> curl http://localhost:3001/blocks

體系結(jié)構(gòu)

應(yīng)該注意的是,實(shí)際上暴露了兩個(gè)web服務(wù)器的節(jié)點(diǎn):一個(gè)用于用戶控制的節(jié)點(diǎn)(HTTP服務(wù)器)和一個(gè)對(duì)等節(jié)點(diǎn)之間的通信到千。 (Websocket HTTP服務(wù)器)

[圖片上傳失敗...(image-ddda7f-1523368323591)]

結(jié)論

Naivecoin現(xiàn)在還是一個(gè)低級(jí)區(qū)塊程序昌渤。 此外,這一章顯示區(qū)塊鏈的一些基本原理可以用很簡(jiǎn)單的方式來(lái)實(shí)現(xiàn)。 在下一章,我們將添加工作量證明算法(挖礦)功能憔四。

本章中實(shí)現(xiàn)的完整代碼,可以在這里找到膀息。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市了赵,隨后出現(xiàn)的幾起案子潜支,更是在濱河造成了極大的恐慌,老刑警劉巖柿汛,帶你破解...
    沈念sama閱讀 211,123評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件冗酿,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡络断,警方通過(guò)查閱死者的電腦和手機(jī)裁替,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)妓羊,“玉大人,你說(shuō)我怎么就攤上這事稍计≡瓿瘢” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,723評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵臣嚣,是天一觀的道長(zhǎng)净刮。 經(jīng)常有香客問(wèn)我,道長(zhǎng)硅则,這世上最難降的妖魔是什么淹父? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,357評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮怎虫,結(jié)果婚禮上暑认,老公的妹妹穿的比我還像新娘。我一直安慰自己大审,他們只是感情好蘸际,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著徒扶,像睡著了一般粮彤。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,760評(píng)論 1 289
  • 那天导坟,我揣著相機(jī)與錄音屿良,去河邊找鬼。 笑死惫周,一個(gè)胖子當(dāng)著我的面吹牛尘惧,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播闯两,決...
    沈念sama閱讀 38,904評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼褥伴,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了漾狼?” 一聲冷哼從身側(cè)響起重慢,我...
    開(kāi)封第一講書(shū)人閱讀 37,672評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎逊躁,沒(méi)想到半個(gè)月后似踱,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,118評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡稽煤,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評(píng)論 2 325
  • 正文 我和宋清朗相戀三年核芽,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片酵熙。...
    茶點(diǎn)故事閱讀 38,599評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡轧简,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出匾二,到底是詐尸還是另有隱情哮独,我是刑警寧澤,帶...
    沈念sama閱讀 34,264評(píng)論 4 328
  • 正文 年R本政府宣布察藐,位于F島的核電站皮璧,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏分飞。R本人自食惡果不足惜悴务,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望譬猫。 院中可真熱鬧讯檐,春花似錦、人聲如沸染服。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,731評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)肌索。三九已至蕉拢,卻和暖如春特碳,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背晕换。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,956評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工午乓, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人闸准。 一個(gè)月前我還...
    沈念sama閱讀 46,286評(píng)論 2 360
  • 正文 我出身青樓益愈,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親夷家。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蒸其,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容