#概要
9月27日, 10月4日に開催される「ブロックチェーンアプリ開発集中講座無料体験会 : JSでブロックチェーンのデータ構造を作ってみよう」のイベントのハンズオン資料です.
本イベントはブロックチェーンアプリ開発集中講座の体験会となっております.
JavaScriptでブロックチェーンのデータ構造とマイニングアルゴリズムを実装します.
P2Pネットワークなどは実装しないのでご注意ください.
#実装していく
##セットアップ
Node.jsをインストールしていない方はこちらからインストールを行ってください.
次にプロジェクトのセットアップです.
$ mkdir js-blockchain && cd js-blockchain
$ npm init -y
$ touch blockchain.js
$ touch test.js
##コンストラクタ
コンストラクタ(初期化時のみ実行される関数)を作りましょう.
module.exports = class Blockchain {
constructor() {
this.chain = [];
this.pendingTransactions = [];
}
}
chainはブロックを格納する配列, pendingTransactionsが未確定のトランザクションを格納する配列です.
##ブロックを生成するメソッド
次にブロックを生成するメソッドを実装します.
newBlock
はブロックのデータ構造です.
ブロックにはトランザクションを格納します.
-
newBlock
を作成 -
chain
の配列にnewBlock
を追加 -
newBlock
を返す
module.exports = class Blockchain {
constructor() {
this.chain = [];
this.pendingTransactions = [];
}
createNewBlock(nonce, previousBlockHash, hash) {
const newBlock = {
index: this.chain.length + 1,
timestamp: Date.now(),
transaction: this.pendingTransactions,
nonce: nonce,
hash: hash,
previousBlockHash: previousBlockHash
};
this.pendingTransactions = [];
this.chain.push(newBlock);
return newBlock;
}
}
test.jsファイルでcreateNewBlock
を呼び出してみましょう.
引数は適当に設定します.
const Blockchain = require('./blockchain');
const bitcoin = new Blockchain();
bitcoin.createNewBlock(7653, "00KNWRUBWEJWENFOJNWO", "07HDFSKBWESUFBWEIBWIEUFBNW");
console.log(bitcoin);
コンソールでnode test.js
を実行し,結果を確認します.
以下のように返ってきたら成功です.
これが1つのブロックで, これが繋がっていきます.
$ node test.js
Blockchain {
chain:
[ { index: 1,
timestamp: 1563284119109,
transactions: [],
nonce: 7653,
hash: '00NDGHEWNEJWHBRNMNWO',
previousBlockHash: '00KNWRUBWEJWENFOJNWO' } ],
pendingTransactions: [] }
データを3つに増やしてみます.
const Blockchain = require('./blockchain');
const bitcoin = new Blockchain();
bitcoin.createNewBlock(7653, "00KNWRUBWEJWENFOJNWO", "00NDGHEWNEJWHBRNMNWO");
bitcoin.createNewBlock(8971, "00HDNFHEWEDGRBCHRNKG", "00HDYENRHFBKDURNFHNE");
bitcoin.createNewBlock(9761, "00JOIRNNOIHWEOUBNEWO", "00NJKRUOQWNOIWHRNOWQ");
console.log(bitcoin);
この時点ではブロックは繋がっていませんが, 増えるとこんな感じになります.
$ node test.js
Blockchain {
chain:
[ { index: 1,
timestamp: 1563284451328,
transactions: [],
nonce: 7653,
hash: '00NDGHEWNEJWHBRNMNWO',
previousBlockHash: '00KNWRUBWEJWENFOJNWO' },
{ index: 2,
timestamp: 1563284451328,
transactions: [],
nonce: 8971,
hash: '00HDYENRHFBKDURNFHNE',
previousBlockHash: '00HDNFHEWEDGRBCHRNKG' },
{ index: 3,
timestamp: 1563284451328,
transactions: [],
nonce: 9761,
hash: '00NJKRUOQWNOIWHRNOWQ',
previousBlockHash: '00JOIRNNOIHWEOUBNEWO' } ],
pendingTransactions: [] }
##最新のブロックを返すメソッド
最新のブロックを返すだけです.
getLastBlock() {
return this.chain[this.chain.length - 1];
}
##トランザクションを作成するメソッド
newTransactionというオブジェクトを作成して配列にpushします.
トランザクションとはAさんからBさんに1BTC送るというような取引の記録です.
createNewTransaction(amount, sender, recipient) {
const newTransaction = {
amount: amount,
sender: sender,
recipient: recipient
}
this.pendingTransactions.push(newTransaction);
}
createNewTransactionをテストしましょう.
1BTCをAliceからBobに送ってみます.
まずブロックを作成し, トランザクションを作成します.
const Blockchain = require("./blockchain");
const bitcoin = new Blockchain();
bitcoin.createNewBlock(80103, "00KNWRUBWEJWENFOJNWO", "00NDGHEWNEJWHBRNMNWO");
bitcoin.createNewTransaction(
1,
"ALICEJSJSNWNN",
"BOBDKENINOMDO"
);
console.log(bitcoin);
実行結果は以下です.
トランザクションがpendingTransactions
に入っています.
この時点ではトランザクションはブロックに含まれておらず, 未確定の状態になっています.
トランザクションはブロックをチェーンに追加する「マイニング」によって確定されます.
$ node test.js
Blockchain {
chain:
[ { index: 1,
timestamp: 1563285267935,
transactions: [],
nonce: 80103,
hash: '00NDGHEWNEJWHBRNMNWO',
previousBlockHash: '00KNWRUBWEJWENFOJNWO' } ],
pendingTransactions:
[ { amount: 1,
sender: 'ALICEJSJSNWNN',
recipient: 'BOBDKENINOMDO' } ] }
test.jsに新しいブロックを追加しましょう.
これは即興のマイニング処理です.
const Blockchain = require("./blockchain");
const bitcoin = new Blockchain();
bitcoin.createNewBlock(80103, "00KNWRUBWEJWENFOJNWO", "00NDGHEWNEJWHBRNMNWO");
bitcoin.createNewTransaction(
1,
"ALICEJSJSNWNN",
"BOBDKENINOMDO"
);
bitcoin.createNewBlock(99282, "00HDNFHEWEDGRBCHRNKG", "00HDYENRHFBKDURNFHNE");
console.log(bitcoin);
以下が実行結果です. pendingTransactions
がなくなり, トランザクションは2番目のブロックに格納されました.
$ node test.js
Blockchain {
chain:
[ { index: 1,
timestamp: 1563285569054,
transactions: [],
nonce: 80103,
hash: '00NDGHEWNEJWHBRNMNWO',
previousBlockHash: '00KNWRUBWEJWENFOJNWO' },
{ index: 2,
timestamp: 1563285569054,
transactions: [Array],
nonce: 99282,
hash: '00HDYENRHFBKDURNFHNE',
previousBlockHash: '00HDNFHEWEDGRBCHRNKG' } ],
pendingTransactions: [] }
##ブロックにハッシュ関数をかけるメソッド
sha256のnpmパッケージをインストールします.
$ npm i sha256
ファイルの先頭でrequire
して使える状態にします.
const sha256 = require('sha256');
ブロックにハッシュ関数をかけるメソッドを作成します.
引数を足し合わせてハッシュ関数にかけます.
previousBlockHashが前のブロックのハッシュ, currentBlockDataが現在のブロックのデータ, nonceはマイニングでブロックをチェーンに追加するために必要な数字です.
※「前のブロックのハッシュを現在のブロックの情報に加える」ということが繰り返されるので,ブロックが「チェーン」のように繋がっているのです.
hashBlock(previousBlockHash, currentBlockData, nonce) {
const dataAsString = previousBlockHash + nonce.toString() + JSON.stringify(currentBlockData);
const hash = sha256(dataAsString);
return hash;
}
ではこのメソッドをテストしましょう.
この時点でのテストデータも適当です.
トランザクションは3つでテストを行います.
const Blockchain = require("./blockchain");
const bitcoin = new Blockchain();
const previousBlockHash = "0AA0IAIJIJUIGGUGUYG";
const currentBlockData = [
{
amount: 10,
sender: "ALICE090970FYFFYFYFIF",
recipient: "BOB797789790JFJFFGFJF"
},
{
amount: 30,
sender: "ALICGHIUGUGOOIGODYGDHFD",
recipient: "BOBTYSHGHOUHOHOHOHOHO"
},
{
amount: 200,
sender: "ALICEHJGUGUTETEEUUCVVUVUV",
recipient: "BOBGIUGIUGIUDRTESREAREUY"
}
];
const nonce = 100;
console.log(bitcoin.hashBlock(previousBlockHash, currentBlockData, nonce));
テストコード通りに実行すると以下のハッシュが返ってくるはずです.
$ node test.js
86e8acd0646dd795aa3604031df124af403113e50e8f23c7be846b990c5d0b42
#Proof Of Workメソッド
最後はProof Of Work(PoW)メソッドです.
本来ならたくさんマイナーがいてP2Pでマイニング競争をするのですが, この記事ではマイナーは自分一人だけのケースを実装します.
proofOfWork(previousBlockHash, currentBlockData) {
let nonce = 0;
let hash = this.hashBlock(previousBlockHash, currentBlockData, nonce);
while(hash.substring(0, 4) !== '0000') {
nonce++;
hash = this.hashBlock(previousBlockHash, currentBlockData, nonce);
}
return nonce;
}
hashの先頭が0000
になるまでブロックのハッシュ化を繰り返します.その度にnonce
を増やしていきます.
これがProof Of Workです. ハッシュ化を繰り返すことがマシンパワーをつぎ込むということになり, マシンパワーをつぎ込んだことが取引の正当性の担保になります.
逆に取引内容を改ざんすると, そのトランザクションが含まれているブロックとそれ以降のブロックのハッシュを再計算しなくてはならず, 相当なマシンパワーが必要になります.
マイニング報酬をもらいたいというインセンティブでマイナーはマイニングをするのです. つまり, ビットコインでは人の欲望が取引の正当性を担保しているということになります.
では proofOfWork
メソッドをテストしましょう.
const Blockchain = require("./blockchain");
const bitcoin = new Blockchain();
const previousBlockHash = "0AA0IAIJIJUIGGUGUYG";
const currentBlockData = [
{
amount: 10,
sender: "ALICE090970FYFFYFYFIF",
recipient: "BOB797789790JFJFFGFJF"
},
{
amount: 30,
sender: "ALICGHIUGUGOOIGODYGDHFD",
recipient: "BOBTYSHGHOUHOHOHOHOHO"
},
{
amount: 200,
sender: "ALICEHJGUGUTETEEUUCVVUVUV",
recipient: "BOBGIUGIUGIUDRTESREAREUY"
}
];
console.log(bitcoin.proofOfWork(previousBlockHash, currentBlockData));
少し, 表示されるのに時間がかかったことがわかります.
上記のテストコードをそのまま実行すれば以下のnonce
になります.
105106回ハッシュ化を行なったということになります.
$ node test.js
105106
正解のnonceをhashBlockに与えて正しいかどうかを検証します.
const Blockchain = require("./blockchain");
const bitcoin = new Blockchain();
const previousBlockHash = "0AA0IAIJIJUIGGUGUYG";
const currentBlockData = [
{
amount: 10,
sender: "ALICE090970FYFFYFYFIF",
recipient: "BOB797789790JFJFFGFJF"
},
{
amount: 30,
sender: "ALICGHIUGUGOOIGODYGDHFD",
recipient: "BOBTYSHGHOUHOHOHOHOHO"
},
{
amount: 200,
sender: "ALICEHJGUGUTETEEUUCVVUVUV",
recipient: "BOBGIUGIUGIUDRTESREAREUY"
}
];
console.log(bitcoin.hashBlock(previousBlockHash, currentBlockData, 105106));
先頭に0が4つ並んでいますね!
このようにマイニングには時間がかかりますが, 検証は一瞬ですみます.
この検証をビットコインノードたちは繰り返し行ってるわけです.
$ node test.js
0000eaf3ccc12559217ff4c0786a422935f36823936be0a7343c3e6b0e0ba48b
##GenesisBlockの作成
最後に一番はじめのブロックGenesis Blockを作成しておきます.
これは最初のブロックを初期化することです.
constructor() {
this.chain = [];
this.pendingTransaction= [];
this.createNewBlock(100, '0', '0');
}
テストすると
const Blockchain = require("./blockchain");
const bitcoin = new Blockchain();
console.log(bitcoin);
以下のように初期化されます.
$ node test.js
Blockchain {
chain:
[ { index: 1,
timestamp: 1563288921693,
transactions: [],
nonce: 100,
hash: '0',
previousBlockHash: '0' } ],
pendingTransactions: [] }
最後に一連の流れをテストしましょう.
トランザクションの生成, マイニング, ブロックの生成という流れです.
const Blockchain = require('./blockchain');
const bitcoin = new Blockchain();
bitcoin.createNewTransaction(
100,
"ALICE090970FYFFYFYFIF",
"BOB797789790JFJFFGFJF"
);
function mining(bitcoin) {
//前のブロックを取得
const lastBlock = bitcoin.getLastBlock();
//前のブロックハッシュを取得
const previousBlockHash = lastBlock["hash"];
//現在のブロックのデータ
const currentBlockData = {
transactions: bitcoin.pendingTransactions,
index: lastBlock["index"] + 1
};
const nonce = bitcoin.proofOfWork(previousBlockHash, currentBlockData);
console.log(nonce);
const blockHash = bitcoin.hashBlock(
previousBlockHash,
currentBlockData,
nonce
);
const newBlock = bitcoin.createNewBlock(nonce, previousBlockHash, blockHash);
};
mining(bitcoin);
bitcoin.createNewTransaction(
200,
"ALICE090970FYFFYFYFIF",
"BOB797789790JFJFFGFJF"
);
mining(bitcoin);
bitcoin.createNewTransaction(
300,
"ALICE090970FYFFYFYFIF",
"BOB797789790JFJFFGFJF"
);
mining(bitcoin);
console.log(bitcoin);
3回マイニングしました.
現在のブロックのハッシュが次のブロックのpreviousBlockHash
に入っていると成功です.
ブロックが連結できました!
$ node test.js
63679
18278
142244
Blockchain {
chain:
[ { index: 1,
timestamp: 1563289082675,
transactions: [],
nonce: 100,
hash: '0',
previousBlockHash: '0' },
{ index: 2,
timestamp: 1563289083265,
transactions: [Array],
nonce: 63679,
hash:
'0000736e9955903284bece68af5677725595659fd30c5ee5bd43842407434cf4',
previousBlockHash: '0' },
{ index: 3,
timestamp: 1563289083369,
transactions: [Array],
nonce: 18278,
hash:
'0000512c406dcbf6bf694fa549248a26a2b9f828a3b6fa698cdf7fbf794207bb',
previousBlockHash:
'0000736e9955903284bece68af5677725595659fd30c5ee5bd43842407434cf4' },
{ index: 4,
timestamp: 1563289084129,
transactions: [Array],
nonce: 142244,
hash:
'00002f343be1ced1e1878551b7aad6a5e94b07248f4efdf5f7d4174ddffe8873',
previousBlockHash:
'0000512c406dcbf6bf694fa549248a26a2b9f828a3b6fa698cdf7fbf794207bb' } ],
pendingTransactions: [] }
最終のblockchain.js
の状態は以下です
const sha256 = require('sha256');
module.exports = class Blockchain {
constructor() {
this.chain = [];
this.pendingTransactions = [];
this.createNewBlock(100, '0', '0')
}
createNewBlock(nonce, previousBlockHash, hash) {
const newBlock = {
index: this.chain.length + 1,
timestamp: Date.now(),
transactions: this.pendingTransactions,
nonce: nonce,
hash: hash,
previousBlockHash: previousBlockHash
};
this.pendingTransactions = [];
this.chain.push(newBlock);
return newBlock;
}
getLastBlock() {
return this.chain[this.chain.length - 1];
}
createNewTransaction(amount, sender, recipient) {
const newTransaction = {
amount: amount,
sender: sender,
recipient: recipient
}
this.pendingTransactions.push(newTransaction);
}
hashBlock(previousBlockHash, currentBlockData, nonce) {
const dataAsString = previousBlockHash + nonce.toString() + JSON.stringify(currentBlockData);
const hash = sha256(dataAsString);
return hash;
}
proofOfWork(previousBlockHash, currentBlockData) {
let nonce = 0;
let hash = this.hashBlock(previousBlockHash, currentBlockData, nonce);
while(hash.substring(0, 4) !== '0000') {
nonce++;
hash = this.hashBlock(previousBlockHash, currentBlockData, nonce);
}
return nonce;
}
}
#最後に
ブロックチェーンのデータ構造が作れました!
ブロックが連結しているということが, 実際に実装することで実感できたのではないかと思います!
以下のようにproofOfWorkメソッドのhashの先頭の0の数を増やすと, マイニングに時間がかかってよりリアルさが増すので試してみてください.
proofOfWork(previousBlockHash, currentBlockData) {
let nonce = 0;
let hash = this.hashBlock(previousBlockHash, currentBlockData, nonce);
while(hash.substring(0, 5) !== '00000') {
nonce++;
hash = this.hashBlock(previousBlockHash, currentBlockData, nonce);
}
return nonce;
}