主页 > 下载官方imtoken钱包 > 30分钟自己写一个区块链(一)
30分钟自己写一个区块链(一)
阅读时间:10 分钟聊天
去年在币圈动荡、币价飞涨的时候,我们看到了很多优秀的区块链产品在背后出现,也看到了很多以集资为目的的“空气币”。 很多人对区块链的兴趣也越来越浓,那么加密货币背后的区块链到底有多少技术含量呢? 我们甚至可以从头开始实施区块链吗? 答案当然是可以的,我们可以创建一条链。 今天,就简单的说一下吧。
(图为初生Ethercat,价值250ETH,截至发稿时价值约28万美元)
什么是加密货币?
在大多数实现中,加密货币只是其区块链中相应账户的余额。 对于每一笔交易,当绝大多数区块链节点证明交易合法时,余额从一个账户转移到另一个账户并在整个网络中更新。 同时,为算力付费的节点可以获得交易手续费,就像银行转账手续费一样。
什么是区块链?
一般来说,区块链(BlockChain)是由一个个称为区块的账本组成。 所有分类帐在维护区块链网络的每个节点上都有完整的副本。 区块的内容经过哈希处理(Hash),并在分布式系统中链接在一起。 每个块可以包含许多事务、文件或任意数据。 新区块被发现后,会将这些数据作为一个整体进行哈希处理,成为一个很短的哈希码,存储在新区块中。 如果不清楚什么是hash,可以看原版的另一篇文章:什么是hash? 5分钟让你看懂

(比特币的区块链图,每个区块存储前一个区块所有内容的hash)
区块链的不变性就是通过这样的链哈希来实现的。 试想一下,如果我要改变一个区块中的内容,它必然会改变它的哈希值,这意味着后续所有区块中的哈希值都必然会改变,维护网络的节点当然不会同意,除非我的算力可以大于全网算力的50%(在某些情况下,攻击所需的算力更低)。
如何从技术上实现区块链?
首先,假定读者具有基本的编程技能。 虽然本文是用 Javascript 编写的,但是掌握任何一种编程语言的读者阅读起来应该没有任何困难。
只想看最终代码? 点击下方Github链接原文。
开发准备
我们首先需要安装最新版本的Node.js,可以通过NVM安装。
curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.8/install.sh | bash
nvm install 9.4.0
node -v
第一步是建立区块链模型
让我们首先构建一个可以创建新块和创建交易的区块链模型。
'use strict';
//定义一个class,叫Blockchain,每一个区块链都是这个class的实例
class Blockchain {
constructor() {
this.chain = []; // 储存所有区块
this.difficulty = 1; // 挖矿的难度
}
createBlock() {
// 创造一个新区块
}
createTransaction() {
// 创建一个交易
}
static hash(block) {
// 对一个区块进行哈希
}
static latesBlock() {
// 取得链上的最后一个区块
}
}
这里我想解释一下区块(block)和交易(transaction)是什么样子的。 虽然不同区块链的区块模型差异很大,但一些最基本的元素是相同的。 基本块如下所示:
var block = {
timestamp: 1516245715528,
id: 0,
proof: 786453290000,
previousBlockHash: "12f79cda4fb3f084531de2034e6b4acf"
transactions: [{
sender: "0xca35b7d915458ef540ade6068dfe2f44e8fa733c",
receiver: "0x14723a09acff6d2a60dcdf7aa4aff308fddc160c",
value: 100
}]
}
可以看出,一个区块包含了它被挖出时的时间戳(timestamp)、它在区块链中的位置(id)、它的证明(proof,后面会讲到更多)、前一个区块的整体哈希值( previousBlockHash),包括交易(transactions)。 作为最基本的交易模型,每笔交易只包括发送方的地址(sender)、接收方的地址(receiver)和本次交易的价值(value)。
第二步实现基本功能工具功能
这里我们首先实现一个效用函数Hash(block),它会帮助我们散列一个块。 这个函数会在我们挖矿(发现新区块)的时候用到。
static hash(block) {
// 对一个区块进行哈希:
// 现将block转换成base64
// 将得到的结果进行SHA哈希
var blockStr = JSON.stringify(block);
var blockB64 = new Buffer(blockStr).toString('base64');
var newHash = crypto.createHash('sha256');
newHash.update(blockB64);
return newHash.digest('hex');
}
此函数将一个块(一个 Javascript 对象)散列为一个字符串。 我们使用的是 crypto 工具,它已经内置在最新版本的 Node.js 中,所以我们不需要安装它。
创建新交易
接下来,我们实现创建新事务的方法。
createTransaction(sender, receiver, value) {
// 创建一个交易
// 根据提供的sender, receiver地址,以及转账的价值,建立一个交易
// 并把它加入到我们的区块链里
var transaction = {
sender: sender,
receiver: receiver,
value: value
};
this.lastBlock.transactions.push(transaction);
return this.lastBlock.id;
}
非常直观,我们只是创建了一个对象,将它添加到区块链并返回了它。
创建一个新块
现在让我们实现创建块的代码。 当我们的区块链甚至没有区块时,我们需要创建第一个区块(创世区块)比特币几分钟产生一个区块,我们在构造函数中实现它。
constructor() {
this.chain = []; // 储存所有区块
this.difficulty = 1; // 挖矿的难度
this.chain.push(this.createBlock(1)); // 创建第一个区块
}
createBlock(previousHash = undefined) {
// 创造一个新区块
// 一开始的proof是0,不一定是有效的,所以我们需要mineProof来找到有效的proof
var block = {
timestamp: Date.now(),
id: this.chain.length,
proof: 0,
previousBlockHash: previousHash || this.constructor.hash(this.lastBlock()),
transactions: []
};
self.mineProof(block);
this.chain.push(block);
}
在创建新区块时,我们使用当前时间的时间戳,使用当前区块链的长度作为id,将初始证明设置为0(证明将在下一步中详细讨论),并转换之前的区块整体Hash并赋值给previousBlockHash。 在创建创世块时,我们将 previousBlockHash 设置为 1。为了便于理解,我们在创建新块时不附加任何交易。 实际情况是,矿工可以选择包含哪些交易,需要对这些交易进行处理比特币几分钟产生一个区块,得到一棵默克尔树。
了解挖矿:寻找有效证据
读者朋友应该听说过工作量证明。 POW 是区块链中用于创建区块的核心算法或机制。 POW本身的目的就是为了找到一个数来解决一个数学问题,而且找到这个数的难度越来越高,但是一旦找到了,就很容易证明它解决了这个数学问题。 可以很快做到。 当然,除了工作量证明,我们还有空间证明和权益证明。 在代码中,我们使用 proof 来表示我们找到的数字。 那么这道数学题到底是什么? 让我们用一个例子来回答。
给定一个数A,我们要找到一个数B,使得Hash(A*B)的结果C的最后一位等于0。即C可以是Hash(A*B)=2ba83... 6d0,因为它的最后一位是0。如果我们用Javascript找到这个B,我们可以这样做:
const crypto = require('crypto');
var A = 10; // 我们给定A一个值
var B = 0; // B从0开始
while (true) {
var multiplied = A * B;
var hash = crypto.createHash('sha256').update(multiplied.toString()).digest('hex');
if (hash.substr(hash.length - 1) == "0") {
console.log(hash);
break;
} else {
B += 1;
}
}
console.log(B);
让我们尝试在终端中运行这段代码,我们可以看到我们找到了一个满足这个条件的数字 24。
node test.js
6af1f692e9496c6d0b668316eccb93276ae6b6774fa728aac31ff40a38318760
24
可以想象,如果我们要求最终哈希值的最后2位为0,甚至3位、4位,那么找到B的难度应该更高,这就是POW调整挖矿难度的方式。
实现挖矿
知道如何挖矿后,我们将上面的代码集成到我们的区块链模型中。
isProofValid(tentativeBlock) {
// 这里我们判断newProof是不是一个合法的proof的方法是
// 将整个区块进行哈希
// 如果得到的散列值指的最后n位都是0,那么这是一个valid proof
// 其中,n = difficulty
var result = this.constructor.hash(tentativeBlock);
return result.substr(result.length - this.difficulty) == '0'.repeat(this.difficulty);
}
mineProof(tentativeBlock) {
while (!this.isProofValid(tentativeBlock)) {
tentativeBlock.proof += 1; // 如果不是可用的proof,我们就接着枚举
}
}
createBlock(previousHash = undefined) {
// 创造一个新区块
// 一开始的proof是0,不一定是有效的,所以我们需要mineProof来找到有效的proof
var block = {
timestamp: Date.now(),
id: this.chain.length,
proof: 0,
previousBlockHash: previousHash || this.constructor.hash(this.lastBlock()),
transactions: []
};
self.mineProof(block);
this.chain.push(block);
}
因为我们从一开始就定义了如何散列一个块,所以我们可以在这里重用它。 至此,我们基本实现了区块链中创建区块和创建交易的功能。 整体代码贴在下面。 下一次,我们会将我们编写的区块链API化并部署到多个节点,敬请期待。
'use strict';
const crypto = require('crypto');
//定义一个class,叫Blockchain,每一个区块链都是这个class的实例
class Blockchain {
constructor() {
this.chain = []; // 储存所有区块
this.difficulty = 1; // 挖矿的难度
this.chain.push(this.createBlock(1)); // 创建第一个区块
}
isProofValid(tentativeBlock) {
// 这里我们判断newProof是不是一个合法的proof的方法是
// 将整个区块进行哈希
// 如果得到的散列值指的最后n位都是0,那么这是一个valid proof
// 其中,n = difficulty
var result = this.constructor.hash(tentativeBlock);
return result.substr(result.length - this.difficulty) == '0'.repeat(this.difficulty);
}
mineProof(tentativeBlock) {
while (!this.isProofValid(tentativeBlock)) {
tentativeBlock.proof += 1; // 如果不是可用的proof,我们就接着枚举
}
}
createBlock(previousHash = undefined) {
// 创造一个新区块
// 一开始的proof是0,不一定是有效的,所以我们需要mineProof来找到有效的proof
var block = {
timestamp: Date.now(),
id: this.chain.length,
proof: 0,
previousBlockHash: previousHash || this.constructor.hash(this.lastBlock()),
transactions: []
};
self.mineProof(block);
this.chain.push(block);
}
createTransaction(sender, receiver, value) {
// 创建一个交易
// 根据提供的sender, receiver地址,以及转账的价值,建立一个交易
// 并把它加入到我们的区块链里
var transaction = {
sender: sender,
receiver: receiver,
value: value
};
this.lastBlock.transactions.push(transaction);
return this.lastBlock.id;
}
static hash(block) {
// 对一个区块进行哈希:
// 现将block 转换成base64
// 将得到的结果进行SHA哈希
var blockStr = JSON.stringify(block);
var blockB64 = new Buffer(blockStr).toString('base64');
var newHash = crypto.createHash('sha256');
newHash.update(blockB64);
return newHash.digest('hex');
}
lastBlock() {
// 取得链上的最后一个区块
return this.chain[this.chain.length - 1];
}
}