乐趣区

初识区块链 – 用JS构建你自己的区块链

前言
区块链太复杂,那我们就讲点简单的。用 JS 来构建你自己的区块链系统,寥寥几行代码就可以说明区块链的底层数据结构,POW 挖矿思想和交易过程等。当然了,真实的场景远远远比这复杂。本文的目的仅限于让大家初步了解,初步认识区块链。
文章内容主要参考视频:使用 Javascript 建立区块链(https://www.youtube.com/playlist?list=PLzvRQMJ9HDiTqZmbtFisdXFxul5k0F-Q4)
感谢原作者,本文在原视频基础上做了修改补充,并加入了个人理解。
认识区块链
区块链顾名思义是由区块连接而成的链,因此最基本的数据结构是块。每个块都含有时间戳,数据,散列,previousHash 等信息。其中数据用来存储数据,previousHash 是前一个区块的哈希值示意如下:

哈希是对区块信息的摘要存储,哈希的好处是任意长度的信息经过哈希都可以映射成固定长度的字符串,如可用 SHA256:
calculateHash() {
return SHA256(this.previousHash + this.timestamp + JSON.stringify(this.data)).toString();
}

块的数据结构
块的最基本数据结构如下:
class Block {
constructor(timestamp, data, previousHash = ”) {
this.timestamp = timestamp;
this.data = data;
this.previousHash = previousHash;
// 对 hash 的计算必须放在最后,保证所有数据赋值正确后再计算
this.hash = this.calculateHash();
}

calculateHash() {
return SHA256(this.previousHash + this.timestamp + JSON.stringify(this.data)).toString();
}
}

BlockChain 的数据结构
多个区块链接而成 BlockChain,显然可用用数组或链表来表示,如:
class BlockChain {
constructor() {
this.chain = [];
}
}

创世区块
正所谓万物始于一,区块链的第一个区块总是需要人为来手动创建,这个区块的 previousHash 为空,如:
createGenesisBlock() {
return new Block(“2018-11-11 00:00:00”, “Genesis block of simple chain”, “”);
}

区块链的构造方法也应改为:
class BlockChain {
constructor() {
this.chain = [this.createGenesisBlock()];
}
}

添加区块
每新加一个区块,必须保证与原有区块链连接起来,即:
class BlockChain {
getLatestBlock() {
return this.chain[this.chain.length – 1];
}

addBlock(newBlock) {
// 新区块的前一个 hash 值是现有区块链的最后一个区块的 hash 值;
newBlock.previousHash = this.getLatestBlock().hash;
// 重新计算新区块的 hash 值(因为指定了 previousHash);
newBlock.hash = newBlock.calculateHash();
// 把新区块加入到链中;
this.chain.push(newBlock);
}

}

校验区块链
区块链数据结构的核心是保证前后链接,无法篡改,但是如果有人真的篡改了某个区块,我们该如何校验发现呢?最笨也是最自然是想法就是遍历所有情况,逐一校验,如:
isChainValid() {
// 遍历所有区块
for (let i = 1; i < this.chain.length; i++) {
const currentBlock = this.chain[i];
const previousBlock = this.chain[i – 1];
// 重新计算当前区块的 hash 值,若发现 hash 值对不上,说明该区块有数据被篡改,hash 值未重新计算
if (currentBlock.hash !== currentBlock.calculateHash()) {
console.error(“hash not equal: ” + JSON.stringify(currentBlock));
return false;
}
// 判断当前区块的 previousHash 是否真的等于前一个区块的 hash,若不等,说明前一个区块被篡改,虽然 hash 值被重新计算正确,但是后续区块的 hash 值并未重新计算,导致整个链断裂
if (currentBlock.previousHash !== previousBlock.calculateHash) {
console.error(“previous hash not right: ” + JSON.stringify(currentBlock));
return false;
}
}
return true;
}

跑吧
跑起来看看,即:
let simpleChain = new BlockChain();
simpleChain.addBlock(new Block(“2018-11-11 00:00:01”, {amount: 10}));
simpleChain.addBlock(new Block(“2018-11-11 00:00:02”, {amount: 20}));

console.log(JSON.stringify(simpleChain, null, 4));

console.log(“is the chain valid? ” + simpleChain.isChainValid());

结果如下:
ali-186590cc4a7f:simple-chain shanyao$ node main_1.js
{
“chain”: [
{
“timestamp”: “2018-11-11 00:00:00”,
“data”: “Genesis block of simple chain”,
“previousHash”: “”,
“hash”: “fd56967ff621a4090ff71ce88fdd456547d1c92d2e93766b7e8791f7a5f91f89”
},
{
“timestamp”: “2018-11-11 00:00:01”,
“data”: {
“amount”: 10
},
“previousHash”: “fd56967ff621a4090ff71ce88fdd456547d1c92d2e93766b7e8791f7a5f91f89”,
“hash”: “150b196268a0152e9f0e719ac131a722472a809f49bd507965029a78c7400529”
},
{
“timestamp”: “2018-11-11 00:00:02”,
“data”: {
“amount”: 20
},
“previousHash”: “150b196268a0152e9f0e719ac131a722472a809f49bd507965029a78c7400529”,
“hash”: “274a7a13ed20118e8cb745654934a7e24a4d59333ba17dfbf5d4cfe0fa8a6e34”
}
]
}
is the chain valid? true

注意看其中的 previousHash 与哈希,确实是当前区块的 previousHash 指向前一个区块的哈希值。
篡改下试试
都说区块链不可篡改,是真的吗让我们篡改第 2 个区块试试,如?
let simpleChain = new BlockChain();
simpleChain.addBlock(new Block(“2018-11-11 00:00:01”, {amount: 10}));
simpleChain.addBlock(new Block(“2018-11-11 00:00:02”, {amount: 20}));

console.log(“is the chain valid? ” + simpleChain.isChainValid());

// 将第 2 个区块的数据,由 10 改为 15
simpleChain.chain[1].data = {amount: 15};

console.log(“is the chain still valid? ” + simpleChain.isChainValid());
console.log(JSON.stringify(simpleChain, null, 4));

结果如下:
ali-186590cc4a7f:simple-chain shanyao$ node main_1.js
is the chain valid? true
hash not equal: {“timestamp”:”2018-11-11 00:00:01″,”data”:{“amount”:15},”previousHash”:”fd56967ff621a4090ff71ce88fdd456547d1c92d2e93766b7e8791f7a5f91f89″,”hash”:”150b196268a0152e9f0e719ac131a722472a809f49bd507965029a78c7400529″}
is the chain still valid? false
{
“chain”: [
{
“timestamp”: “2018-11-11 00:00:00”,
“data”: “Genesis block of simple chain”,
“previousHash”: “”,
“hash”: “fd56967ff621a4090ff71ce88fdd456547d1c92d2e93766b7e8791f7a5f91f89”
},
{
“timestamp”: “2018-11-11 00:00:01”,
“data”: {
“amount”: 15
},
“previousHash”: “fd56967ff621a4090ff71ce88fdd456547d1c92d2e93766b7e8791f7a5f91f89”,
“hash”: “150b196268a0152e9f0e719ac131a722472a809f49bd507965029a78c7400529”
},
{
“timestamp”: “2018-11-11 00:00:02”,
“data”: {
“amount”: 20
},
“previousHash”: “150b196268a0152e9f0e719ac131a722472a809f49bd507965029a78c7400529”,
“hash”: “274a7a13ed20118e8cb745654934a7e24a4d59333ba17dfbf5d4cfe0fa8a6e34”
}
]
}

显然,篡改了数据之后,哈希值并未重新计算,导致该区块的哈希值对不上。
再篡改下试试
那么,如果我们聪明点,篡改后把哈希值也重新计算会如何?
let simpleChain = new BlockChain();
simpleChain.addBlock(new Block(“2018-11-11 00:00:01”, {amount: 10}));
simpleChain.addBlock(new Block(“2018-11-11 00:00:02”, {amount: 20}));

console.log(“is the chain valid? ” + simpleChain.isChainValid());
// 篡改后重新计算 hash 值
simpleChain.chain[1].data = {amount: 15};
simpleChain.chain[1].hash = simpleChain.chain[1].calculateHash();
console.log(“is the chain still valid? ” + simpleChain.isChainValid());
console.log(JSON.stringify(simpleChain, null, 4));

结果如下:
ali-186590cc4a7f:simple-chain shanyao$ node main_1.js
is the chain valid? true
previous hash not right: {“timestamp”:”2018-11-11 00:00:02″,”data”:{“amount”:20},”previousHash”:”150b196268a0152e9f0e719ac131a722472a809f49bd507965029a78c7400529″,”hash”:”274a7a13ed20118e8cb745654934a7e24a4d59333ba17dfbf5d4cfe0fa8a6e34″}
is the chain still valid? false
{
“chain”: [
{
“timestamp”: “2018-11-11 00:00:00”,
“data”: “Genesis block of simple chain”,
“previousHash”: “”,
“hash”: “fd56967ff621a4090ff71ce88fdd456547d1c92d2e93766b7e8791f7a5f91f89”
},
{
“timestamp”: “2018-11-11 00:00:01”,
“data”: {
“amount”: 15
},
“previousHash”: “fd56967ff621a4090ff71ce88fdd456547d1c92d2e93766b7e8791f7a5f91f89”,
“hash”: “74d139274fb692495b7c805dd5822faa0c5b5e6058b6beef96e87e18ab83a6b1”
},
{
“timestamp”: “2018-11-11 00:00:02”,
“data”: {
“amount”: 20
},
“previousHash”: “150b196268a0152e9f0e719ac131a722472a809f49bd507965029a78c7400529”,
“hash”: “274a7a13ed20118e8cb745654934a7e24a4d59333ba17dfbf5d4cfe0fa8a6e34”
}
]
}

显然,第 3 个区块的 previousHash 并未指向第 2 个区块的哈希值。
是真的无法篡改吗
其实并不是,如果我们再聪明一点,把后续区块的哈希值也重新计算一下,不就 OK 了吗?确实如此,如:
let simpleChain = new BlockChain();
simpleChain.addBlock(new Block(“2018-11-11 00:00:01”, {amount: 10}));
simpleChain.addBlock(new Block(“2018-11-11 00:00:02”, {amount: 20}));

console.log(“is the chain valid? ” + simpleChain.isChainValid());
// 篡改第 2 个区块
simpleChain.chain[1].data = {amount: 15};
simpleChain.chain[1].hash = simpleChain.chain[1].calculateHash();
// 并把第 3 个区块也重新计算
simpleChain.chain[2].previousHash = simpleChain.chain[1].hash;
simpleChain.chain[2].hash = simpleChain.chain[2].calculateHash();
console.log(“is the chain still valid? ” + simpleChain.isChainValid());
console.log(JSON.stringify(simpleChain, null, 4

本文作者:扁鹊他大哥阅读原文
本文为云栖社区原创内容,未经允许不得转载。

退出移动版