大家好,我是Frank。
今天,我们来简单入个门,学习下区块链相关基础知识以及简单的开发,希望通过实践,我们能更好的理解区块链技术。需要说明的是,由于本教程内容较多,因此我分成了上下两篇,第一篇重点讲解基础,第二篇讲解进阶内容,希望能帮你由浅入深,更好的了解区块链技术,通过本教程我们可以学习到:
- 上篇
- 区块链相关的基础概念
- 区块链的运行原理
- 用JavaScript实现简单的区块链
- 下篇
- 通过Solidy开发智能合约
什么是区块链?
我们可以把区块链理解为是一个分布式账本,每个人持有的账本都是一模一样的,而且,这些账本都是互相强关联的,每个账本都持有前一个账本的哈希值,这些账本串联起来形成了一个链,就叫区块链,而区块就包含了交易的信息、当前区块的哈希值和前一个区块的哈希值。
区块链是如何工作的?
区块链的安全性
从前面对区块链的描述来看,如果一个黑客想要攻击并篡改一个区块的信息,他就必须把跟它相关联的下一个区块的哈希值也改变了,由此类类推,直到把后面的整个链条中的区块信息都哈希一遍,而我们知道,做哈希运算是有成本的,需要消耗计算资源,因此,这种账本强关联的关系结构保证了其安全性。
交易过程
一笔交易从发起到确认会经历以下几个步骤:
- 创建交易:发起方需要指定交易的发送方(自己)、接收方、和交易的资产数量,最后用自己的私钥,对交易进行签名,以证明交易自己发起的
- 广播交易:签名完成后,交易信息被广播到网络中的节点,节点之间再继续相互传播,目的就是让所有节点都知道这笔交易的存在
- 验证交易:节点收到交易后,进行验证,包括检查交易格式是否正确、发送方是否有足够资金进行交易,签名是否有效,验证通过后,交易被放入待交易处理池中,等待打包进区块
- 区块打包:当待交易池里有足够多交易后,矿工就负责把这些交易打包进新的区块
- 区块验证:矿工打包后会把新的区块发送到网络中,接着其他节点将会验证新区块的交易合法性和矿工的工作量证明(如果是工作量证明POW的共识机制的话),工作量证明的过程即验证新区块的哈希值是否符合区块链网络的标准。如果区块验证通过,该新区块将被添加到区块链上。
- 交易确认:一旦新区块添加成功,就意味着交易被节点所接受和承认,其将会被永久记录,不能篡改。
区块链实践
通过前面对区块链运行原理的了解,我们可以开始尝试编写一个简单的区块链了,在写代码前,我们先对哈希运算做个初步的了解。
哈希运算
我们将使用的是最普遍用到的SHA256哈希算法,任何长度的字符串,经过运算后都会得到同等长度为64个字符的字符串。在区块链中,为了进一步保证安全性,在SHA256的基础上还额外要求哈希值的前N位要满足特定条件,比如:前三位是0。因此,矿工通常需要对区块进行多次哈希运算,直到符合特定的哈希值需求。
SHA256("hello world").toString();
// 输出结果:2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
到这里,你可能会有疑惑了,区块的内容都是一样的,哈希算法的结果也是固定的,算多少次也是一样的呀。
没错,接下来我们要引入一个递增的数字——“nonce(number only once)”,在每次哈希运算前都加上一个nonce,每次递增+1,直到匹配到符合要求的哈希值,这样一来,每次就能得到不同的哈希值了。
let nonce = 0;
let hash = SHA256("hello").toString();
while (hash.substring(0, 3) !== "000") {
nonce++;
hash = SHA256("hello" + nonce).toString();
}
console.log(nonce);
console.log(hash);
// 输出结果:
// 10284
// 0006bc9ad4253c42e32b546dc17e5ea3fedaecdabef371b09906cea9387e8695
可以看到,经过10284次哈希运算,我们最终得到了符合要求的哈希值,即:前三位为0。
编码
接下来,我们正式开始编写一个简单的区块链代码,首先,我们创建一个blockchanin.js文件
// blockchain.js
class Blockchain {
constructor() {
// 初始化区块链,包含创世区块
this.chain = [this.createGenesisBlock()];
// 待处理交易池
this.pendingTransactions = [];
}
...
这里createGenesisBlock()函数的作用是创建一个创世区块,也就是区块链中的第一个区块。 接下来,我们开始编写createGenesisBlock函数:
createGenesisBlock() {
return {
index: 1, // 区块序号
timestamp: Date.now(), // 时间戳
transactions: [], // 包含的交易事务,为空
nonce: 0, // 随机数
hash: "hash", // 当前区块的哈希值
previousBlockHash: "previousBlockHash", // 上一个区块的哈希值,可以是任意值
}
}
我们注意到,由于创世区块之前没有区块了,所以前一个区块的哈希值previousBlockHash可是任意固定值,并且,不包含任何的事务。由于构造函数只会运行一次,因此,createGenesisBlock也是只会被运行一次,也就意味着只会有一个创世区块。
接下来,为了创建新的区块,我们还需要做如下工作:
1.首先,需要获取到最后一个区块,这样才能拿到上一个区块的哈希值:
getLastBlock() {
return this.chain[this.chain.length - 1];
};
2.接着,创建新的交易事务:
createNewTracsaction(amount, sender, receipient) {
const transaction = {
amount,
sender,
receipient,
};
this.pendingTransactions.push(transaction);
}
3.生成新的区块的哈希值:
generateHash(previousBlockHash, timeStamp, transactions) {
let hash = "";
let nonce = 0;
while (hash.substring(0, 3) !== "000") {
nonce++;
hash = SHA256(
previousBlockHash + timeStamp + nonce + JSON.stringify(transactions)
).toString();
}
return { hash, nonce };
}
4.最后,创建新的区块:
createNewBlock() {
const timeStamp = Date.now();
const transactions = this.pendingTransactions;
const previousBlockHash = this.getLastBlock().hash;
const { hash, nonce } = this.generateHash(
previousBlockHash,
timeStamp,
transactions
);
const newBlock = {
index: this.chain.length + 1,
timeStamp,
transactions,
previousBlockHash,
hash,
nonce,
};
// 清空待处理交易池
this.pendingTransactions = [];
// 添加新的区块
this.chain.push(newBlock);
// 返回新的区块
return newBlock;
}
测试
为了测试区块链代码能正常运行,我们新建一个test.mjs文件,
引入Blockchain类
import { Blockchain } from "./blockchain.mjs";
创建一个区块链,并打印
const bitcoin = new Blockchain();
console.log(bitcoin);
结果:
Blockchain {
chain: [
{
index: 1,
timeStamp: 1713336500950,
transactions: [],
previousBlockHash: 'previousBlockHash',
hash: 'hash',
nonce: 0
}
],
pendingTransactions: []
}
可以看到目前区块链中只包含一个创世块,且没有任何待处理的交易,接下来,我们创建两笔交易,
bitcoin.createNewTracsaction(100, "zhangsan", "lisi");
bitcoin.createNewTracsaction(200, "wangwu", "lisi");
Blockchain {
chain: [
{
index: 1,
timeStamp: 1713337264961,
transactions: [],
previousBlockHash: 'previousBlockHash',
hash: 'hash',
nonce: 0
}
],
pendingTransactions: [
{ amount: 100, sender: 'zhangsan', receipient: 'lisi' },
{ amount: 200, sender: 'wangwu', receipient: 'lisi' }
]
}
从打印结果可以看到,刚创建的两笔交易已经被添加到待处理交易池里了,接下来创建新的区块
bitcoin.createNewBlock();
Blockchain {
chain: [
{
index: 1,
timeStamp: 1713337963968,
transactions: [],
previousBlockHash: 'previousBlockHash',
hash: 'hash',
nonce: 0
},
{
index: 2,
timeStamp: 1713337963968,
transactions: [Array],
previousBlockHash: 'hash',
hash: '000d4993ba944d639d9a8b0174c92a7b45824c356a652409031a6ff1590b9923',
nonce: 725
}
],
pendingTransactions: []
}
新的区块中包含了两笔交易,且pendingTransactions已经被清空,我们看看这两笔交易:
console.log(bitcoin.chain[1].transactions);
[
{ amount: 100, sender: 'zhangsan', receipient: 'lisi' },
{ amount: 200, sender: 'wangwu', receipient: 'lisi' }
]
正是我们刚才创建的那两笔交易。
最后
至此,我们已经初步理解了区块链的运行原理,而且还通过JavaScript编写了一个简单的区块链,但我们离开发一款真实的DApp应用还有很长的路要走,我们需要学习像Ethers.js和Web3.js这样的三方库,还要掌握一门专门用于编写智能合约的语言,如Solidy,在本教程下篇,我就将介绍,如何通过Solidy来编写智能合约。