ホームページ > ウェブフロントエンド > jsチュートリアル > JavaScript はブロックチェーンを実装します

JavaScript はブロックチェーンを実装します

亚连
リリース: 2018-05-30 14:26:17
オリジナル
2325 人が閲覧しました

多くの友人はビットコインやイーサリアムなどの暗号通貨について聞いたことがあると思いますが、その背後に隠されたテクノロジーを理解している人はほんのわずかです。 次に、この記事を通じて、JavaScript を使用してそれらをデモンストレーションする簡単なブロックチェーンを作成する方法を紹介します。社内で働いている方、興味のある友人、見てみましょう

ほぼ誰もがビットコインやイーサリアムのような暗号通貨について聞いたことがあるでしょうが、その背後に隠されたテクノロジーを理解している人はごくわずかです。この記事では、JavaScript を使用して簡単なブロックチェーンを作成し、ブロックチェーンが内部でどのように機能するかを示します。それをSavjeeCoinと呼ぶことにします!

全文は 3 つのパートに分かれています:

  1. part1: 基本的なブロックチェーンの実装

  2. part2: POW の実装

  3. part3: トランザクションとマイニング報酬

パート 1: の実装基本的なブロックチェーン

ブロックチェーン

ブロックチェーンは、誰でもアクセスできるブロックで構成される公開データベースです。これは特別なことのように思えないかもしれませんが、それらには不変であるという興味深い特性があります。ブロックがブロックチェーンに追加されると、残りのブロックを無効にすることなく変更することはできません。

これが、暗号通貨がブロックチェーンに基づいている理由です。取引終了後に取引を変更されるのは望ましくありません。

ブロックを作成する

ブロックチェーンは、相互にリンクされた多数のブロックで構成されています(これは大丈夫そうです...)。チェーン上のブロックには、誰かが以前のブロックを操作したかどうかを検出できる方法があります。

では、データの整合性を確保するにはどうすればよいでしょうか?各ブロックには、その内容に基づいて計算されたハッシュが含まれています。前のブロックのハッシュも含まれます。

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ライブラリを導入しました。次に、ブロックのプロパティを初期化するコンストラクターを定義しました。各ブロックには、チェーン全体におけるこのブロックの位置を示すインデックス属性が与えられます。また、タイムスタンプと、ブロックに保存する必要があるいくつかのデータも生成しました。最後に、前のブロックのハッシュがあります。

チェーンを作成する

これで、Blockchain クラスでブロックをリンクできるようになりました。 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;
 }
}
ログイン後にコピー

コンストラクターで、genesis ブロックを含む配列を作成してチェーン全体を初期化します。最初のブロックは前のブロックを指すことができないため、特別です。次の 2 つのメソッドも追加しました:

  • getlatestBlock() はブロックチェーン上の最新のブロックを返します。

  • addBlock() は、チェーンに新しいブロックを追加する役割を果たします。これを行うには、前のブロックのハッシュを新しいブロックに追加します。このようにして、チェーン全体の整合性を維持できます。最新のブロックの内容を変更する限り、そのハッシュを再計算する必要があるからです。計算が完了したら、ブロックをチェーン (配列) にプッシュします。

最後に、各ブロックが前の正しいブロックを指しているかどうかを確認するための isChainValid() 来确保没有人篡改过区块链。它会遍历所有的区块来检查每个区块的hash是否正确。它会通过比较 previousHash を作成します。すべてがOKな場合は true を返し、それ以外の場合は false を返します。

ブロックチェーンの使用

ブロックチェーン クラスが作成されたので、実際に使用を開始できます。

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(&#39;Blockchain valid? &#39; + savjeeCoin.isChainValid());
// 现在尝试操作变更数据
savjeeCoin.chain[1].data = { amount: 100 };
// 再次检查是否有效 (将会返回false)
console.log("Blockchain valid? " + savjeeCoin.isChainValid());
ログイン後にコピー

最初に isChainValid() を実行して、チェーン全体の整合性を検証します。任意のブロックを操作したので、true が返されます。

その後、チェーンの最初(インデックス1)のブロックのデータを変更しました。その後、チェーン全体の整合性を再度チェックしたところ、 false が返されたことがわかりました。私たちのチェーン全体がもう機能しません。

結論

この小さな栗は完成には程遠いです。他のマイナーと通信するための POW (プルーフ・オブ・ワーク・メカニズム) や P2P ネットワークは実装されていません。

しかし、彼はブロックチェーンがどのように機能するかを証明しました。多くの人は原理が非常に複雑だと考えていますが、この記事はブロックチェーンの基本概念が非常に理解しやすく、実装しやすいことを証明しています。

Part2: POW(proof-of-work: 作業証明)の導入

在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 = &#39;&#39;) {
 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 = &#39;&#39;) {
 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(&#39;Mining block 1&#39;);
savjeeCoin.addBlock(new Block(1, "20/07/2017", { amount: 4 }));
console.log(&#39;Mining block 2&#39;);
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 = &#39;&#39;) {
 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(&#39;Creating some transactions...&#39;);
savjeeCoin.createTransaction(new Transaction(&#39;address1&#39;, &#39;address2&#39;, 100));
savjeeCoin.createTransaction(new Transaction(&#39;address2&#39;, &#39;address1&#39;, 50));
ログイン後にコピー

这些交易目前都处于等待状态,为了让他们得到证实,我们必须开始挖矿:

console.log(&#39;Starting the miner...&#39;);
savjeeCoin.minePendingTransactions(&#39;xaviers-address&#39;);
ログイン後にコピー

当我们开始挖矿,我们也会传递一个我们想要获得挖矿奖励的地址。在这种情况下,我的地址是 xaviers-address (非常复杂!)。

之后,让我们检查一下 xaviers-address 的账户余额:

console.log(&#39;Balance of Xaviers address is&#39;, savjeeCoin.getBalanceOfAddress(&#39;xaviers-address&#39;));
// 输出: 0
ログイン後にコピー


我的账户输出竟然是0?!等等,为什么?难道我不应该得到我的挖矿奖励么?那么,如果你仔细观察代码,你会看到系统会创建一个交易,然后将您的挖矿奖励添加为新的待处理交易。这笔交易将会包含在下一个区块中。所以如果我们再次开始挖矿,我们将收到我们的100枚硬币奖励!

console.log(&#39;Starting the miner again!&#39;);
savjeeCoin.minePendingTransactions("xaviers-address");
console.log(&#39;Balance of Xaviers address is&#39;, savjeeCoin.getBalanceOfAddress(&#39;xaviers-address&#39;));
// 输出: 100
ログイン後にコピー

局限性与结论

现在我们的区块链已经可以在一个区块上存储多笔交易,并且可以为矿工带来回报。

ただし、まだいくつかの欠点があります。通貨を送金する際、送金元が実際に取引を行うのに十分な残高があるかどうかを確認しません。ただし、これは実際には簡単に修正できます。また、新しいウォレットを作成したり、トランザクションに署名したりしませんでした (従来は公開/秘密キー暗号化を使用して行われていました)。

上記は私があなたのためにまとめたものです。

関連記事:

vue-cli作成プロジェクト、マルチページ実装メソッドの設定

web3.jsにeth.getRawTransactionByHash(txhash)メソッドの手順を追加

nodejs mysqlデータベースへの簡単なアクセスと操作メソッドの例

以上がJavaScript はブロックチェーンを実装しますの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート