JS 实现区块链

比较基础的区块链实现,并不一定要用 JS 写,TS 也可以,只是一个 prototype,很多随机、验证没有做,顺便加一些自己的学习笔记相关。

简介

顾名思义,区块链是由一个个区块(block) 串联 起来的产物,而每一个 block 至少包含以下 5 个属性:

interface Block {index: number; // 可选timestamp: number;transactions: Transaction[];nonce: number;hash: string;previousBlockHash: string;}

其中 index 并不是必须的,这个属性只是为了方便获取最后一个 block,其他的包含时间戳、当前 block 所包含的交易(transactions)、nonce、hash 和上一个 block 的 hash,除了 nonce 之外的其他属性都比较好理解。

nonce 是一个电脑生成的随机数,它的用途是用来生成当前 block 的特定 hash 值,和证明整个 block chain 的合法性。nonce 又被称之为 proof of work(工作证明)或是 magical number(神奇数字)。

它也是保证整个 blockchain 无法被修改的原因,每一个 block 的 hash value 都会基于上一个 block 的 hash value 和 nonce,如果有人想要黑掉中间某一个 block,那么它就必须重新计算被黑掉的 block 后所有 block 的 hash value。基于加密算法的复杂性,以及现在挖一个矿都要耗费大量的时间成本和能源成本,我觉得与其说是无法被修改……不如说是基于个人能力无法被修改吧,如果是企业级以上说不定真的有可能……

而 transaction 的定义如下:

interface Transaction {amount: number;sender: string;recipient: string;}

当然,这个实现中参考的是货币的定义,如果是其他应用长,transaction 也可以根据具体的业务需求进行修改。

代码实现

下面的代码也可以不用 JS 写。

构造函数及基本定义

import sha256 from 'sha256';interface Block {index: number;timestamp: number;transactions: Transaction[];nonce: number;hash: string;previousBlockHash: string;}interface Transaction {amount: number;sender: string;recipient: string;}class Blockchain {chain: Block[];pendingTransactions: Transaction[];constructor() {this.chain = [];this.pendingTransactions = [];}}

interface 之前都提到过了,这里讲一下构造函数中包含的内容,也就是 chainpendingTransactions

chain 比较好理解,就是当前真个 block chain 的信息,这里使用数组实现,当然也可以使用链表等其他数据结构。

pendingTransactions 指的是被加到当前 block 的交易,因为只有当前的 block 被挖矿(mining)挖出来之后,当前的 block 才会被加到整个区块链中,同样交易才会被推到当前的区块链中。

因此 transactions 和 block 是有依存关系的,只有当前的 block 被挖出来了,那么当前的 transactions 才算是被挖出来,也才算是真的落实了。

创建新的 block

class Blockchain {constructor() {this.chain = [];this.pendingTransactions = [];// arbitrary valuesthis.createNewBlock(100, '0', '0');}createNewBlock = (nonce: number,previousBlockHash: string,hash: string): Block => {const newBlock: Block = {index: this.chain.length + 1,timestamp: Date.now(),transactions: this.pendingTransactions,nonce,hash,previousBlockHash,};this.pendingTransactions = [];this.chain.push(newBlock);return newBlock;};}

这个方法用来创建一个新的 block,根据之前提到的定义,只有当前的 block 被 mine(发掘/创建),当前的 transaction 才会被推入整个 blockchain。

另外这个 constructor 的值只是随便用了几个数字代替并创建当前 chain 中的第一个 block,第一个 block 又被称之为 Genesis Block。基于整个 blockchain system 是一个链式结构,并且只有一条链 [ 3 ],当前的设计用来做 demo 是够了。

获取最后一个 block

getLastBlock = (): Block => {return this.chain[this.chain.length - 1];};

创建新的交易

createNewTransaction = (amount: number, sender: string, recipient: string) => {const newTransaction = {amount,sender,recipient,};this.pendingTransactions.push(newTransaction);return this.getLastBlock()['index'] + 1;};

该实现也是基于之前的定义,也就是当前 block 没有被 mine 之前,所有的 transaction 都是在 pending 状态。

hash block

hashBlock = (prevBlockHash: string,currBlockData: Transaction | Transaction[],nonce: number) => {const dataAsString: string =prevBlockHash + nonce.toString() + JSON.stringify(currBlockData);const hash: string = sha256(dataAsString);return hash;};

这也是基于之前的理论:当前 block 的 hash value 是基于之前 block 的 hash value、当前 block 的数据,以及 nonce。一个合法的 hash 前四位数字必须都是 0,即 0000XXXXX

proof of work

// repeatedly hash block until it finds correct hash => '0000XXXXXXX'// use the current block for the hash, but also the prevBlockHash// continuously changes nonce value until it finds the correct hash// return the nonce value that creates the correct hashproofOfWork = (prevBlockHash: string,currBlockData: Transaction | Transaction[]) => {let nonce = 0;let hash = this.hashBlock(prevBlockHash, currBlockData, nonce);while (hash.substring(0, 4) !== '0000') {hash = this.hashBlock(prevBlockHash, currBlockData, ++nonce);}return nonce;};

这里是一个比较傻瓜和暴力的做法,也就是从 0 开始一个个往上加,知道满足 hash value 开头是 0000。在实际应用中,nonce 则是一个半随机的数字,而且 bicoin 的机制会设置一个数字,使得矿工必须要找到一个 nonce 小于等于机制提出的数字,这些限定都会让挖矿的难度往上翻几个等级。

reference

  • 1. How Long Does a Bitcoin Transaction Take?

  • 2. Block Header in Blockchain Explained! How it Works

  • 3. How do you make a Genesis block?