많은 친구들이 비트코인이나 이더리움과 같은 암호화폐에 대해 들어봤지만 그 뒤에 숨겨진 기술을 이해하는 사람은 소수에 불과합니다. 다음으로 이 글을 통해 JavaScript를 사용하여 이를 시연하는 간단한 블록체인을 만드는 방법을 소개하겠습니다. 내부적으로 일하시나요? 관심 있는 친구들과 함께 살펴볼까요
비트코인이나 이더리움 같은 암호화폐에 대해 들어본 사람은 거의 없지만 그 뒤에 숨겨진 기술을 이해하는 사람은 극소수입니다. 이 기사에서는 JavaScript를 사용하여 간단한 블록체인을 만들어 내부적으로 어떻게 작동하는지 보여드리겠습니다. 저는 그것을 SavjeeCoin이라고 부르겠습니다! ∎ 전체 텍스트는 세 부분으로 나뉩니다.
part1 : 구현 a. 기본 블록체인
Blockchain블록체인은 누구나 접근할 수 있는 블록으로 구성된 공용 데이터베이스입니다. 이것은 특별한 것이 아닌 것처럼 보일 수도 있지만 흥미로운 속성을 가지고 있습니다: 불변입니다. 블록이 블록체인에 추가되면 나머지 블록을 무효화하지 않고는 변경할 수 없습니다.
이것이 암호화폐가 블록체인을 기반으로 하는 이유입니다. 사람들이 거래가 완료된 후에 거래를 변경하는 것을 원하지 않습니다!
블록 만들기블록체인은 서로 연결된 여러 블록으로 구성됩니다(이것도 괜찮은 것 같네요...). 체인의 블록에는 누군가가 이전 블록을 조작했는지 여부를 감지할 수 있는 방법이 있습니다.
그렇다면 데이터 무결성을 어떻게 보장할 수 있을까요? 각 블록에는 해당 내용을 기반으로 계산된 해시가 포함되어 있습니다. 또한 이전 블록의 해시도 포함됩니다. 다음은 JavaScript로 작성된 블록 클래스의 대략적인 모습입니다.
const SHA256 = require("crypto-js/sha256"); class Block { constructor(index, timestamp, data, previousHash = '') { this.index = index; this.previousHash = previousHash; this.timestamp = timestamp; this.data = data; this.hash = this.calculateHash(); } calculateHash() { return SHA256(this.index + this.previousHash + this.timestamp + JSON.stringify(this.data)).toString(); } }
JavaScript는 sha256을 지원하지 않기 때문에 crypto-js 라이브러리를 도입했습니다. 그런 다음 블록의 속성을 초기화하는 생성자를 정의했습니다. 각 블록에는 전체 체인에서 이 블록의 위치를 알려주는 인덱스 속성이 제공됩니다. 또한 블록에 저장해야 하는 타임스탬프와 일부 데이터도 생성했습니다. 마지막으로 이전 블록의 해시가 있습니다.
체인 만들기이제 블록체인 클래스에서 블록을 서로 연결할 수 있습니다! 다음은 JavaScript로 구현된 코드입니다.
class Blockchain{
constructor() {
this.chain = [this.createGenesisBlock()];
}
createGenesisBlock() {
return new Block(0, "01/01/2017", "Genesis block", "0");
}
getLatestBlock() {
return this.chain[this.chain.length - 1];
}
addBlock(newBlock) {
newBlock.previousHash = this.getLatestBlock().hash;
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];
if (currentBlock.hash !== currentBlock.calculateHash()) {
return false;
}
if (currentBlock.previousHash !== previousBlock.hash) {
return false;
}
}
return true;
}
}
생성자에서 제네시스 블록이 포함된 배열을 생성하여 전체 체인을 초기화합니다. 첫 번째 블록은 이전 블록을 가리킬 수 없기 때문에 특별합니다. 또한 다음 두 가지 메서드를 추가했습니다.
getLatestBlock()은 블록체인의 최신 블록을 반환합니다.
를 만들어 각 블록이 올바른 이전 블록을 가리키는지 확인합니다. 모든 것이 정상이면 true를 반환하고 그렇지 않으면 false를 반환합니다.
isChainValid()
来确保没有人篡改过区块链。它会遍历所有的区块来检查每个区块的hash是否正确。它会通过比较 previousHash
블록체인 수업이 작성되었으며 실제로 사용할 수 있습니다!
let savjeeCoin = new Blockchain();
savjeeCoin.addBlock(new Block(1, "20/07/2017", { amount: 4 }));
savjeeCoin.addBlock(new Block(2, "20/07/2017", { amount: 8 }));
여기서 방금 블록체인 인스턴스를 생성하고 이름을 SavjeeCoin으로 지정했습니다! 그 후 체인에 블록 몇 개를 추가했습니다. 블록에는 원하는 데이터가 포함될 수 있지만 위 코드에서는 amount 속성이 있는 개체를 추가하기로 선택했습니다.
사용해 보세요!서문에서 블록체인은 불변이라고 말씀드렸습니다. 일단 추가되면 블록을 변경할 수 없습니다. 시도해 보자!
// 检查是否有效(将会返回true)
console.log('Blockchain valid? ' + savjeeCoin.isChainValid());
// 现在尝试操作变更数据
savjeeCoin.chain[1].data = { amount: 100 };
// 再次检查是否有效 (将会返回false)
console.log("Blockchain valid? " + savjeeCoin.isChainValid());
처음에 isChainValid()를 실행하여 전체 체인의 무결성을 확인하겠습니다. 우리는 모든 블록에서 작업을 수행했으므로 true를 반환합니다.
이후 체인의 첫 번째(인덱스 1) 블록의 데이터를 변경했습니다. 그 후 전체 체인의 무결성을 다시 검사한 결과 거짓이 반환된 것으로 나타났습니다. 전체 체인이 더 이상 작동하지 않습니다.
결론
이 작은 밤나무는 완성과는 거리가 멀습니다. 다른 채굴자와 통신하기 위한 POW(작업 증명 메커니즘) 또는 P2P 네트워크를 구현하지 않았습니다. 하지만 그는 블록체인이 어떻게 작동하는지 증명했습니다. 많은 사람들이 원리가 매우 복잡할 것이라고 생각하지만, 이 기사는 블록체인의 기본 개념이 이해하고 구현하기 매우 쉽다는 것을 증명합니다.
2부: POW 구현(작업 증명: 작업 증명)
在part1中我们用JavaScript创建了一个简单的区块链来演示区块链的工作原理。不过这个实现并不完整,很多人发现依旧可以篡改该系统。没错!我们的区块链需要另一种机制来抵御攻击。那么让我们来看看我们该如何做到这一点!
问题
现在我们可以很快的创造区块然后非常迅速的将它们添加进我们的区块链中。不过这导致了三个问题:
第一:人们可以快速创建区块然后在我们的链里塞满垃圾。大量的区块会导致我们区块链过载并让其无法使用。
第二:因为创建一个有效的区块太容易了,人们可以篡改链中的某一个区块,然后重新计算所有区块的hash。即使它们已经篡改了区块,他们仍然可以以有效的区块来作为结束。
第三:你可以通过结合上述两个破绽来有效控制区块链。区块链由p2p网络驱动,其中节点会将区块添加到可用的最长链中。所以你可以篡改区块,然后计算所有其他的区块,最后添加多任意你想要添加的区块。你最后会得到一个最长的链,所有的其它节点都会接受它然后往上添加自己的区块。
显然我们需要一个方案来解决这些问题:POW。
什么是POW
POW是在第一个区块链被创造之前就已经存在的一种机制。这是一项简单的技术,通过一定数量的计算来防止滥用。工作量是防止垃圾填充和篡改的关键。如果它需要大量的算力,那么填充垃圾就不再值得。
比特币通过要求hash以特定0的数目来实现POW。这也被称之为 难度
不过等一下!一个区块的hash怎么可以改变呢?在比特币的场景下,一个区块包含有各种金融交易信息。我们肯定不希望为了获取正确的hash而混淆了那些数据。
为了解决这个问题,区块链添加了一个 nonce 值。Nonce是用来查找一个有效Hash的次数。而且,因为无法预测hash函数的输出,因此在获得满足难度条件的hash之前,只能大量组合尝试。寻找到一个有效的hash(创建一个新的区块)在圈内称之为挖矿。
在比特币的场景下,POW确保每10分钟只能添加一个区块。你可以想象垃圾填充者需要多大的算力来创造一个新区块,他们很难欺骗网络,更不要说篡改整个链。
实现POW
我们该如何实现呢?我们先来修改我们区块类并在其构造函数中添加Nonce变量。我会初始化它并将其值设置为0。
constructor(index, timestamp, data, previousHash = '') { this.index = index; this.previousHash = previousHash; this.timestamp = timestamp; this.data = data; this.hash = this.calculateHash(); this.nonce = 0; }
我们还需要一个新的方法来增加Nonce,直到我们获得一个有效hash。强调一下,这是由难度决定的。所以我们会收到作为参数的难度。
mineBlock(difficulty) { while (this.hash.substring(0, difficulty) !== Array(difficulty + 1).join("0")) { this.nonce++; this.hash = this.calculateHash(); } console.log("BLOCK MINED: " + this.hash); }
最后,我们还需要更改一下 calculateHash()
函数。因为目前他还没有使用Nonce来计算hash。
calculateHash() { return SHA256(this.index + this.previousHash + this.timestamp + JSON.stringify(this.data) + this.nonce ).toString(); }
将它们结合在一起,你会得到如下所示的区块类:
class Block { constructor(index, timestamp, data, previousHash = '') { this.index = index; this.previousHash = previousHash; this.timestamp = timestamp; this.data = data; this.hash = this.calculateHash(); this.nonce = 0; } calculateHash() { return SHA256(this.index + this.previousHash + this.timestamp + JSON.stringify(this.data) + this.nonce).toString(); } mineBlock(difficulty) { while (this.hash.substring(0, difficulty) !== Array(difficulty + 1).join("0")) { this.nonce++; this.hash = this.calculateHash(); } console.log("BLOCK MINED: " + this.hash); } }
修改区块链
现在,我们的区块已经拥有Nonce并且可以被开采了,我们还需要确保我们的区块链支持这种新的行为。让我们先在区块链中添加一个新的属性来跟踪整条链的难度。我会将它设置为2(这意味着区块的hash必须以2个0开头)。
constructor() { this.chain = [this.createGenesisBlock()]; this.difficulty = 2; }
现在剩下要做的就是改变 addBlock() 方法,以便在将其添加到链中之前确保实际挖到该区块。下面我们将难度传给区块。
addBlock(newBlock) { newBlock.previousHash = this.getLatestBlock().hash; newBlock.mineBlock(this.difficulty); this.chain.push(newBlock); }
大功告成!我们的区块链现在拥有了POW来抵御攻击了。
测试
现在让我们来测试一下我们的区块链,看看在POW下添加一个新区块会有什么效果。我将会使用之前的代码。我们将创建一个新的区块链实例然后往里添加2个区块。
let savjeeCoin = new Blockchain(); console.log('Mining block 1'); savjeeCoin.addBlock(new Block(1, "20/07/2017", { amount: 4 })); console.log('Mining block 2'); savjeeCoin.addBlock(new Block(2, "20/07/2017", { amount: 8 }));
如果你运行了上面的代码,你会发现添加新区块依旧非常快。这是因为目前的难度只有2(或者你的电脑性能非常好)。
如果你创建了一个难度为5的区块链实例,你会发现你的电脑会花费大概十秒钟来挖矿。随着难度的提升,你的防御攻击的保护程度越高。
免责声明
就像之前说的:这绝不是一个完整的区块链。它仍然缺少很多功能(像P2P网路)。这只是为了说明区块链的工作原理。
并且:由于单线程的原因,用JavaScript来挖矿并不快。
Part3:交易与挖矿奖励
在前面两部分我们创建了一个简单的区块链,并且加入了POW来抵御攻击。然而我们在途中也偷了懒:我们的区块链只能在一个区块中存储一笔交易,而且矿工没有奖励。现在,让我们解决这个问题!
重构区块类
现在一个区块拥有 index , previousHash , timestamp , data , hash 和 nonce 属性。这个 index 属性并不是很有用,事实上我甚至不知道为什么开始我要将它添加进去。所以我把它移除了,同时将 data 改名为 transactions 来更语义化。
class Block{ constructor(timestamp, transactions, previousHash = '') { this.previousHash = previousHash; this.timestamp = timestamp; this.transactions = transactions; this.hash = this.calculateHash(); this.nonce = 0; } }
当我们改变区块类时,我们也必须更改 calculateHash() 函数。现在它还在使用老旧的 index 和 data 属性。
calculateHash() { return SHA256(this.previousHash + this.timestamp + JSON.stringify(this.transactions) + this.nonce).toString(); }
交易类
在区块内,我们将可以存储多笔交易。因此我们还需要定义一个交易类,一边我们可以锁定交易应当具有的属性:
class Transaction{ constructor(fromAddress, toAddress, amount){ this.fromAddress = fromAddress; this.toAddress = toAddress; this.amount = amount; } }
这个交易例子非常的简单,仅仅包含了发起方( fromAddress )和接受方( toAddress )以及数量。如果有需求,你也可以在里面加入更多字段,不过这个只是为了最小实现。
调整我们的区块链
当前的最大任务:调整我们的区块链来适应这些新变化。我们需要做的第一件事就是存储待处理交易的地方。
正如你所知道的,由于POW,区块链可以稳定的创建区块。在比特币的场景下,难度被设置成大约每10分钟创建一个新区块。但是,是可以在创造两个区块之间提交新的交易。
为了做到这一点,首先需要改变我们区块链的构造函数,以便他可以存储待处理的交易。我们还将创造一个新的属性,用于定义矿工获得多少钱作为奖励:
class Blockchain{ constructor() { this.chain = [this.createGenesisBlock()]; this.difficulty = 5; // 在区块产生之间存储交易的地方 this.pendingTransactions = []; // 挖矿回报 this.miningReward = 100; } }
下一步,我们将调整我们的 addBlock() 方法。不过我的调整是指删掉并重写它!我们将不再允许人们直接为链上添加区块。相反,他们必须将交易添加至下一个区块中。而且我们将 addBlock() 更名为 createTransaction() ,这看起来更语义化:
createTransaction(transaction) { // 这里应该有一些校验! // 推入待处理交易数组 this.pendingTransactions.push(transaction); }
挖矿
人们现在可以将新的交易添加到待处理交易的列表中。但无论如何,我们需要将他们清理掉并移入实际的区块中。为此,我们来创建一个 minePendingTransactions()
方法。这个方法不仅会挖掘所有待交易的新区块,而且还会向采矿者发送奖励。
minePendingTransactions(miningRewardAddress) { // 用所有待交易来创建新的区块并且开挖.. let block = new Block(Date.now(), this.pendingTransactions); block.mineBlock(this.difficulty); // 将新挖的看矿加入到链上 this.chain.push(block); // 重置待处理交易列表并且发送奖励 this.pendingTransactions = [ new Transaction(null, miningRewardAddress, this.miningReward) ]; }
请注意,该方法采用了参数 miningRewardAddress 。如果你开始挖矿,你可以将你的钱包地址传递给此方法。一旦成功挖到矿,系统将创建一个新的交易来给你挖矿奖励(在这个栗子里是100枚币)。
有一点需要注意的是,在这个栗子中,我们将所有待处理交易一并添加到一个区块中。但实际上,由于区块的大小是有限制的,所以这是行不通的。在比特币里,一个区块的大小大概是2Mb。如果有更多的交易能够挤进一个区块,那么矿工可以选择哪些交易达成哪些交易不达成(通常情况下费用更高的交易容易获胜)。
地址的余额
在测试我们的代码钱让我们再做一件事!如果能够检查我们区块链上地址的余额将会更好。
getBalanceOfAddress(address){ let balance = 0; // you start at zero! // 遍历每个区块以及每个区块内的交易 for(const block of this.chain){ for(const trans of block.transactions){ // 如果地址是发起方 -> 减少余额 if(trans.fromAddress === address){ balance -= trans.amount; } // 如果地址是接收方 -> 增加余额 if(trans.toAddress === address){ balance += trans.amount; } } } return balance; }
测试
好吧,我们已经完成并可以最终一切是否可以正常工作!为此,我们创建了一些交易:
let savjeeCoin = new Blockchain(); console.log('Creating some transactions...'); savjeeCoin.createTransaction(new Transaction('address1', 'address2', 100)); savjeeCoin.createTransaction(new Transaction('address2', 'address1', 50));
这些交易目前都处于等待状态,为了让他们得到证实,我们必须开始挖矿:
console.log('Starting the miner...'); savjeeCoin.minePendingTransactions('xaviers-address');
当我们开始挖矿,我们也会传递一个我们想要获得挖矿奖励的地址。在这种情况下,我的地址是 xaviers-address (非常复杂!)。
之后,让我们检查一下 xaviers-address 的账户余额:
console.log('Balance of Xaviers address is', savjeeCoin.getBalanceOfAddress('xaviers-address')); // 输出: 0
我的账户输出竟然是0?!等等,为什么?难道我不应该得到我的挖矿奖励么?那么,如果你仔细观察代码,你会看到系统会创建一个交易,然后将您的挖矿奖励添加为新的待处理交易。这笔交易将会包含在下一个区块中。所以如果我们再次开始挖矿,我们将收到我们的100枚硬币奖励!
console.log('Starting the miner again!'); savjeeCoin.minePendingTransactions("xaviers-address"); console.log('Balance of Xaviers address is', savjeeCoin.getBalanceOfAddress('xaviers-address')); // 输出: 100
局限性与结论
现在我们的区块链已经可以在一个区块上存储多笔交易,并且可以为矿工带来回报。
그러나 여전히 몇 가지 단점이 있습니다. 통화를 보낼 때 송금인이 실제로 거래를 수행할 만큼 충분한 잔고를 가지고 있는지 확인하지 않습니다. 그러나 이는 실제로 해결하기 쉬운 문제입니다. 또한 우리는 새 지갑을 생성하고 거래에 서명하지 않았습니다(전통적으로 공개/개인 키 암호화를 사용하여 수행됨).
위 내용은 제가 여러분을 위해 정리한 내용입니다. 앞으로 도움이 되길 바랍니다.
관련 기사:
vue-cli 생성 프로젝트, 다중 페이지 구현 방법 구성
web3.js는 eth.getRawTransactionByHash(txhash) 메서드 단계 추가
nodejs mysql 데이터베이스의 간단한 액세스 및 작동 방법의 예
위 내용은 JavaScript는 블록체인을 구현합니다.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!