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 之前都提到过了,这里讲一下构造函数中包含的内容,也就是 chain
和 pendingTransactions
。
chain 比较好理解,就是当前真个 block chain 的信息,这里使用数组实现,当然也可以使用链表等其他数据结构。
pendingTransactions 指的是被加到当前 block 的交易,因为只有当前的 block 被挖矿(mining)挖出来之后,当前的 block 才会被加到整个区块链中,同样交易才会被推到当前的区块链中。
因此 transactions 和 block 是有依存关系的,只有当前的 block 被挖出来了,那么当前的 transactions 才算是被挖出来,也才算是真的落实了。
创建新的 block
class Blockchain {
constructor() {
this.chain = [];
this.pendingTransactions = [];
// arbitrary values
this.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 hash
proofOfWork = (
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 小于等于机制提出的数字,这些限定都会让挖矿的难度往上翻几个等级。