ブロックチェーンのデータ構造をJavascriptで作ろう
今回はJavascriptで、ブロックチェーンのデータ構造を実装します。ブロックチェーンのデータ構造を実際に作ってみると、その堅牢性が理解できると思います。nonceを求めるマイニング・アルゴリズムも実装します。
なお、P2Pでノード間で同期を取ったり、コンセンサス・アルゴリズムで合意形成を取るところまでは、実装しませんので悪しからず。
ブロックチェーンは「万能薬」でありません。他のプログラムと一緒で、あくまで、アルゴリズムを効率的に実現するデータ構造です。
分散環境下で、セキュアにデータをもつためのデータ構造とも言えます。
つまり、手段です。導入したからといって、問題が自動的に解決したりしません。
それでは早速、作ってみましょう。実際に作業するファイルは2つです。
mkdir project
cd project
npm init
touch blockchan.js
touch test.js
まずはコンストラクタを作ってみましょう。(class表記でも構いません。)chainが実際のブロックを格納する配列です。newTransactionがトランザクションを格納する配列です。
blockchain.js
function Blockchain() {
this.chain = [];
this.pendingTransaction = [];
}
次に、ブロックを生成するメソッドを実装します。ブロックチェーンのデータ構造になります。オブジェクトだということがわかりますね。ポイントは、ブロックにtransactionsを格納していきます。ブロックはトランザクションの格納庫です。まだ、引数の意味については知る必要ないです。
1.オブジェクトnewBlockを生成
2.コンストラクタの配列chainにpush
3. newBlockを返す
blockchain.js
Blockchain.prototype.createNewBlock = function(nonce, previousBlockHash, hash) {
const newBlock = {
index: this.chain.length + 1,
timestamp: Date.now(),
transactions: this.pendingTransaction,
nonce: nonce,
hash: hash,
previousBlockHash: previousBlockHash
};
this.pendingTransaction = [];
this.chain.push(newBlock);
return newBlock;
};
module.exports = Blockchain;
早速、作ったcreateNewBlockメソッドを動かしてみましょう。test.jsファイルを作って、createNewBlockを呼び出してみます。引数は適当で構いません。
test.js
const Blockchain = require("./blockchain");
const bitcoin = new Blockchain();
bitcoin.createNewBlock(2389, "00IAIJIJUIGGUGUYG", "09IAIJHIFYFTUFIFDIJU");
console.log(bitcoin);
コンソールから実行してみて、以下が出力されたら成功!
配列、にオブジェクトが格納されていることがわかりますね。これがブロックチェーンの1ブロック分です。これが数珠繋ぎになっていきます。
>node test.js
Blockchain {
chain:
[ { index: 1,
timestamp: 1552737295416,
transactions: [],
nonce: 2389,
hash: '09IAIJHIFYFTUFIFDIJU',
previousBlockHash: '00IAIJIJUIGGUGUYG' } ],
pendingTransaction: [] }
データを3つに増やしてみましょう。
test.js
const Blockchain = require("./blockchain");
const bitcoin = new Blockchain();
bitcoin.createNewBlock(2389, "00IAIJIJUIGGUGUYG", "09IAIJHIFYFTUFIFDIJU");
bitcoin.createNewBlock(4124, "00IAIJIJUDRTDDYD", "09IAIJHIFYFTUHIUGIUG");
bitcoin.createNewBlock(3463, "00IAIJIJUIGGUHIHH", "0IGUGIGITUFIFDIJU");
console.log(bitcoin);
配列にオブジェクトが続いていますね。この時点では、hashが適当なので繋がってはいません。
>node test.js
Blockchain {
chain:
[ { index: 1,
timestamp: 1552737406974,
transactions: [],
nonce: 2389,
hash: '09IAIJHIFYFTUFIFDIJU',
previousBlockHash: '00IAIJIJUIGGUGUYG' },
{ index: 2,
timestamp: 1552737406974,
transactions: [],
nonce: 4124,
hash: '09IAIJHIFYFTUHIUGIUG',
previousBlockHash: '00IAIJIJUDRTDDYD' },
{ index: 3,
timestamp: 1552737406974,
transactions: [],
nonce: 3463,
hash: '0IGUGIGITUFIFDIJU',
previousBlockHash: '00IAIJIJUIGGUHIHH' } ],
pendingTransaction: [] }
次に、最新のブロックを返すメソッドを作りましょう。単純に最新のchainを返します。
blockchain.js
Blockchain.prototype.getLastBlock = function() {
return this.chain[this.chain.length - 1];
};
次に、トランザクションを作成するメソッドを作りましょう。こちらもオブジェクトを生成して、配列にpushするだけです。
トランザションとは、AさんからBさんに1BTC送ったというレコードです。
blockchain.js
Blockchain.prototype.createNewTransaction = function(
amount,
sender,
recipient
) {
const newTransaction = {
amount: amount,
sender: sender,
recipient: recipient
};
this.pendingTransaction.push(newTransaction);
return this.getLastBlock()["index"] + 1;
};
では、createNewTransactionをテストしてみましょう。100BTCをAliceからBobに送ってみます。まずはブロックを生成。次にトランザクションを生成します。引数は適当です。
test.js
const Blockchain = require("./blockchain");
const bitcoin = new Blockchain();
bitcoin.createNewBlock(987798, "0AA0IAIJIJUIGGUGUYG", "09BBIAIJHIFYFTUFIFDIJU");
bitcoin.createNewTransaction(
100,
"ALICE090970FYFFYFYFIF",
"BOB797789790JFJFFGFJF"
);
console.log(bitcoin);
実行結果です。トランザクションが、pendingTransactions内に入っているのがわかります。
この時点ではデータは確定していません。トランザクションデータは、マイニングよって確定し、次のブロックに追加されます。
>node test.js
Blockchain {
chain:
[ { index: 1,
timestamp: 1552740003699,
transactions: [],
nonce: 987798,
hash: '09BBIAIJHIFYFTUFIFDIJU',
previousBlockHash: '0AA0IAIJIJUIGGUGUYG' } ],
pendingTransactions:
[ { amount: 100,
sender: 'ALICE090970FYFFYFYFIF',
recipient: 'BOB797789790JFJFFGFJF' } ] }
test.jsに、新たなcreateNewBlockを追加してみましょう。現時点でのマイニング処理になります。
bitcoin.createNewBlock(689689, "0AA0IAIJIJUHHJHUYG", "09BBIKJKGKHIFYFTUFIFU");
実行結果です。トランザクションが2個目のブロック:transactionsに入りましたね。transactions:[Array]がそうです。
>node test.js
Blockchain {
chain:
[ { index: 1,
timestamp: 1552740128038,
transactions: [],
nonce: 987798,
hash: '09BBIAIJHIFYFTUFIFDIJU',
previousBlockHash: '0AA0IAIJIJUIGGUGUYG' },
{ index: 2,
timestamp: 1552740128038,
transactions: [Array],
nonce: 689689,
hash: '09BBIKJKGKHIFYFTUFIFU',
previousBlockHash: '0AA0IAIJIJUHHJHUYG' } ],
pendingTransactions: [] }
ここから面白くなっていきます。いよいよ、ブロックをハッシュに通していきます。ハッシュ関数はsha256を使うので、インストールしておきましょう。
>npm i sha256
ファイルの先頭でrequireします。
blockchain.js
const sha256 = require("sha256");
ブロックをハッシュにかけるメソッドを作成します。引数を連結して、ハッシュにかけます。previousBlockHashが、前のブロックのハッシュです。currentBlockDataが現在のブロックデータ、nonceは、プルーフ・オブ・ワーク(Pow)です使う数字です。
ポイントは、previousBlockHashと、現在のブロックの情報をハッシュにかけます。前のブロックの情報と、現在のブロックの情報が合わさります。つまり過去の情報を元に最新のハッシュを作っていくわけです。これが延々と続くのでハッシュが「チェーン」と言われる所以です。
blockchain.js
Blockchain.prototype.hashBlock = function(
previousBlockHash,
currentBlockData,
nonce
) {
const dataAsString =
previousBlockHash + nonce.toString() + JSON.stringify(currentBlockData);
const hash = sha256(dataAsString);
return hash;
};
では早速、作ったハッシュメソッドをテストしましょう。なお、この時点のテストデータは適当で構いません。トランザクションは3つあるものとします。
test.js
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));
上記のデータだと、以下のハッシュが生成されるはずです。値を1文字変えたりして、色々と遊んでみてください。全く違うハッシュが生成されるはずです。
>node test.js
86e8acd0646dd795aa3604031df124af403113e50e8f23c7be846b990c5d0b42
いよいよ、ProofOfWork(Pow)メソッドです!ビットコインの真正性を支えている根幹です。本記事では、P2Pで競争はしないで、ロジックだけ実装します。
blockchain.js
Blockchain.prototype.proofOfWork = function(
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の先頭「0」が0000になるまで、nonceをインクリメントしていきます。最新のnonceを元に、再度hash関数を繰り返していきます。これを正解が出るまで、延々と繰り返します。
中々、骨が折れますね。。w
これがプルーフ・オブ・ワークです。正解を得るために、わざわざCPUパワーをつぎ込んだということが、ブロックに正当性を与えます。逆に過去に遡って、チェーンを改ざんしようとすると相当面倒ですね。
続くブロックの全てのハッシュを再計算しないといけません。
ビットコインが欲しいというインセンティブがあるから、マイナーはPow日々実行しています。人間の欲望が、チェーンに真正性を与えているのです。よくできていますね。
本気記事は、難易度が超低いので計算はあっという間に終わります。
では、proofOfWorkメソッドをテストしてみましょう。
test.js
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回計算したということです。hashをconsole.logして見ると面白いです。(凄いことになりますが。。)
>node test.js
105106
正解のnonceをhashBlockに与えてみて、正しいか検証してみましょう。
test.js
console.log(bitcoin.hashBlock(previousBlockHash, currentBlockData, 105106));
先頭に「0」が4つ並んでますね。正解です!このように、正解を得るのには、もの凄い労力がかかるのですが、検証は一瞬で終わります。
>node test.js
0000eaf3ccc12559217ff4c0786a422935f36823936be0a7343c3e6b0e0ba48b
最後に、GenesisBlockを作成しておきましょう。これは最初のブロックを初期化することです。コンストラクタにthis.createNewBlock(100, "0", "0");を追加してください。値は適当で大丈夫です。
blockchain.js
function Blockchain() {
this.chain = [];
this.pendingTransactions = [];
this.currentNodeUrl = currentNodeUrl;
this.networkNodes = [];
this.createNewBlock(100, "0", "0");
}
テストすると
test.js
const Blockchain = require("./blockchain");
const bitcoin = new Blockchain();
console.log(bitcoin);
以下のように初期化されると思います。
>node test.js
Blockchain {
chain:
[ { index: 1,
timestamp: 1552790828352,
transactions: [],
nonce: 100,
hash: '0',
previousBlockHash: '0' } ],
pendingTransactions: [] }
最後に、今まで作ってきた機能を実行してみましょう。トランザクションを生成して、マイニングして、ブロックを生成します。miningメソッドに全部まとめて、実行していきます。
test.js
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
};
//Powでnonceを求める。
const nonce = bitcoin.proofOfWork(previousBlockHash, currentBlockData);
//前回のブロックのハッシュ、今回のブロックのデータ、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回マイニングしました。ブロック同士がハッシュで連結できましたね!現在のブロックhashが、次のブロックのpreviousBlockHashに入ったら成功です。オブジェクト同士が、hashチェーンで繋がったと言うことです。
>node test.js
Blockchain {
chain:
[ { index: 1,
timestamp: 1552797811338,
transactions: [],
nonce: 100,
hash: '0',
previousBlockHash: '0' },
{ index: 2,
timestamp: 1552797811946,
transactions: [Array],
nonce: 63679,
hash:
'0000736e9955903284bece68af5677725595659fd30c5ee5bd43842407434cf4',
previousBlockHash: '0' },
{ index: 3,
timestamp: 1552797812050,
transactions: [Array],
nonce: 18278,
hash:
'0000512c406dcbf6bf694fa549248a26a2b9f828a3b6fa698cdf7fbf794207bb',
previousBlockHash:
'0000736e9955903284bece68af5677725595659fd30c5ee5bd43842407434cf4' },
{ index: 4,
timestamp: 1552797813034,
transactions: [Array],
nonce: 142244,
hash:
'00002f343be1ced1e1878551b7aad6a5e94b07248f4efdf5f7d4174ddffe8873',
previousBlockHash:
'0000512c406dcbf6bf694fa549248a26a2b9f828a3b6fa698cdf7fbf794207bb' } ],
pendingTransactions: [] }
blockchain.js 完成版
const sha256 = require("sha256");
function Blockchain() {
this.chain = [];
this.pendingTransactions = [];
this.createNewBlock(100, "0", "0");
}
Blockchain.prototype.createNewBlock = function(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;
};
Blockchain.prototype.getLastBlock = function() {
return this.chain[this.chain.length - 1];
};
Blockchain.prototype.createNewTransaction = function(
amount,
sender,
recipient
) {
const newTransaction = {
amount: amount,
sender: sender,
recipient: recipient
};
this.pendingTransactions.push(newTransaction);
return this.getLastBlock()["index"] + 1;
};
Blockchain.prototype.hashBlock = function(
previousBlockHash,
currentBlockData,
nonce
) {
const dataAsString =
previousBlockHash + nonce.toString() + JSON.stringify(currentBlockData);
const hash = sha256(dataAsString);
return hash;
};
Blockchain.prototype.proofOfWork = function(
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;
};
module.exports = Blockchain;
いかがでしたか?
私は最初作った時には感動しましたよ。
今回はあくまで、簡略化したデータ構造で、ブロックチェーンの全てではありませんが、コアな部分が理解できたと思います。
ブロックチェーンが、後方参照するハッシュポインタをもつ連結リストだということがわかると思います。