自己紹介
こんにちは、tabataitです。
都内の某大学に通っている文系エンジニアです。
普段はLaravel frameworkで開発をしていますが、最近Pythonに触れるようになりました。
なぜこの記事を書いたのか
最近何かと話題に上がるブロックチェーンですが、かつて勉強をしようとし、挫折した記憶があります。
そこで、実際にネット上に転がっている文献を調べつつ、フローを追うという点のみに注目し、
最小構成で実装できないかと考えていました。
また、最近コーディングをする際にテストコードから書く機会が多く、
「実際に呼び出すロジックを書いたほうがわかりやすいのでは」
と思い、思い切って書いてみました。
対象とする読者
- ブロックチェーンで出てくる用語自体は知っているが、どのように連携するのかがあまりよくわかっていない
- 実装イメージが全く沸かない
大まかなフロー
本当はトランザクションの部分に関しても詳細に書きたかったのですが、
一旦部分的にブロックチェーン部分のみを実装し、後で追加していこうかなと思います。
- トランザクションを行い、その結果として一時データとしてブロックチェーンクラスに格納
- 前のブロックのハッシュ値とトランザクションを参考にナンスを探す
- ナンスを見つけてブロックを作る
- ブロックをチェーンに追加する
機能テスト
from Blockchain import Blockchain
import unittest
class ExecutionBlockChain(unittest.TestCase):
def test_block_chain(self):
'''テストで扱うトランザクションデータの用意'''
transaction1 = {
"data": 'Transaction data 1'
}
transaction2 = {
"data": 'Transaction data 2'
}
transaction3 = {
"data": 'Transaction data 3'
}
transaction4 = {
"data": 'Transaction data 4'
}
'''トランザクションを追加。ここで様々な取引が行われる'''
blockchain = Blockchain()
blockchain.add_transaction(transaction1)
blockchain.add_transaction(transaction2)
blockchain.add_transaction(transaction3)
blockchain.add_transaction(transaction4)
'''ここからはBlockを作成する'''
previous_block = blockchain.get_previous_block()
'''まずはnonceの発見から'''
previous_hash = blockchain.hash(previous_block)
current_transactions = blockchain.current_transactions
nonce = blockchain.find_nonce(previous_hash, current_transactions)
'''nonceが見つかったので、それを元に新しいブロックを作る'''
new_block = blockchain.create_block(nonce,
previous_hash,
current_transactions)
'''トランザクションでブロックを作ったので、リセットをかける'''
blockchain.reset_current_transactions()
'''生成されたブロックを追加'''
blockchain.add_block(new_block)
self.assertEquals(len(blockchain.current_transactions), 0)
self.assertEquals(len(blockchain.chain), 2)
self.assertEquals(len(blockchain.current_transactions), 0)
self.assertEquals(blockchain.get_previous_block(), new_block)
if __name__ == '__main__':
unittest.main()
assertionはかなり甘めですが、大まかなフロー の部分でまとめた通りの呼び出しを行いました。
このブロックチェーンインスタンスをそれぞれのノードが保持し、記帳をし続けることで、ブロックチェーンが生成されていきます。
ここで、各データ型について触れていきます。
トランザクション
取引一回の単位で用いられるデータの最小単位です。
ことビットコインにおいてはこのようなデータ型です。
transaction = {
status: confirmation,
date: 2018-04-30,
transaction fee: -0.0002BTC,
ammount: -13.1923484BTC,
etc....
}
いくらの送金があったか、いつ取引があったのか、正式な取引として承認されているものであるのかどうかなど、
取引において用いられる実データになります。
ブロック
トランザクションが確定され、ブロックチェーンへの記録をする際の最小単位の情報です。
どの取引が確定されたのか、また、今のブロックは何番目のブロックなのか、
前のブロックのハッシュ値は何かなどの情報を保持しています。
block = [
index: 100,
transactions: [
{
status: confirmation,
date: 2018-04-29,
transaction fee: -0.0001BTC,
ammount: -9.1923484BTC,
},
{
status: confirmation,
date: 2018-04-30,
transaction fee: -0.0002BTC,
ammount: -13.1923484BTC,
}
],
prev_hash: "123456789xxxxxxxxx",
nonce: 20,
]
それでは、実装を見ていきます。
実装
from datetime import datetime
import hashlib
import json
class Blockchain():
def __init__(self):
self.chain = []
self.current_transactions = []
genesis_block = self.create_block(nonce="", previous_hash="", transactions=[])
self.chain.append(genesis_block)
def create_block(self, nonce, previous_hash, transactions):
block = {
'index': len(self.chain) + 1,
'timestamp': datetime.now().strftime('%Y/%m/%d'),
'transactions': transactions,
'nonce': nonce,
'previous_hash': previous_hash,
}
return block
def add_block(self, block):
self.chain.append(block)
return self.chain
def reset_current_transactions(self):
self.current_transactions = []
def add_transaction(self, transaction):
self.current_transactions.append(transaction)
return self.current_transactions
def get_previous_block(self):
return self.chain[-1]
def find_nonce(self, previous_block, transactions):
nonce = 0
while True:
if self.__is_valid_nonce(nonce, self.hash(previous_block), transactions):
break
nonce += 1
return nonce
def __is_valid_nonce(self, nonce, previous_hash, transactions):
transactions_hash = ','.join(map(self.hash, transactions))
hash_string = hashlib.sha256((previous_hash + transactions_hash + str(nonce)).encode()).hexdigest()
if hash_string[:3] == '000':
return True
return False
def hash(self, dict):
hash_string = json.dumps(dict).encode()
return hashlib.sha256(hash_string).hexdigest()
簡潔ですが、メソッドについて概要を記載します。
create_block(nonce, previous_hash, transactions)
ナンス、前のブロックのハッシュ値、ブロックに固める対象のトランザクションを元にブロックを生成します。add_block(block)
ブロックをチェーンに追加します。reset_current_transactions()
一時データとして格納していたトランザクションを消去します。
ブロックをチェーンへ追加し終えた後に呼び出します。get_previous_block()
ブロックチェーン内の最新のブロックを取り出します。find_nonce(previous_block, transactions)
ナンスを探し出します。ナンスを見つけることで初めてブロックが生成できます。
いわゆる一般的にはここでの行為がマイニングと言われることが多いみたいです。
(本来はナンスが権限のあるノードに承認されるまで)hash(dict)
ハッシュ値を計算します。ブロックに記載するために必要な、
前のブロックのハッシュ値を作るときに呼び出したり、
トランザクションのハッシュ値を作るときに呼び出します。
実装を進めていく上でかなり分かりづらかったこと
ナンスを決めるための条件
ナンスがかなり分かりづらかったのは、定義を見ても全くイメージがわきませんでした。
ナンスとは?
この計算において「nonce」は、「ハッシュ値」というものを探すために用いられます。いち早くある条件の「ハッシュ値」を見つけ出すことで、ビットコインの新規発行を行うことが可能になります。
条件ってなんぞや?
生成された「ハッシュ値」がある値以下になっていれば、「ハッシュ値」探しは成功です。
ある値以下ってどういうこと?
と思っていたのですが、整理すると
- ハッシュ値 = ナンス × 前のブロックのハッシュ値などの定数
- ハッシュ値 < 条件X となっていればナンスとして成立
- この条件Xはブロックチェーンのネットワークにより様々である
とのこと。
この条件がどのような難易度なのか、またどのような仕様なのかという点で、
ブロックチェーンのネットワークによる違いがあるそうです。
ブロックの整合性を保つための処理
今回ここの実装は行いませんでした。いわゆる、コンセンサスという取引を承認作業するフローです。
要約すると、今まで作られているブロックチェーンが本当に正しいものなのかどうかを判断するための処理です。
ブロックチェーンに関する講義等で用いられているブロックチェーンネットワークでは、
proof of workを用いて解説をすることが多く、
コンセンサス=proof of workと思われがちですが、(僕はそう思っていました)
この部分のアルゴリズムについては、取引の承認時間やシステムの堅牢性を担保するなど、
ネットワークにより様々な特性があり、差別化を図っているようです。
そのため、使用する用途の要求と取引の承認プロセスが本当に一貫したものになるのかどうかについては、検討する必要がありそうです。
まとめ
ビットコインなどの仮想通貨のみに用いられている技術だと完全に認識していたので、
かなり汎用性の高そうな仕組みになっているのは驚きでした。
最近もAWSでblockchain templateがリリースされ、
ますます身近になりつつありますが、様々なブロックチェーンを比較検討して採用してみるのもかなり面白そうな印象があります。
また、まだまだこのコード自体、改善すべきポイントが沢山あったり、
テスト運用を想定して、dockerを用いてローカルの仮想環境化にネットワークを構築してみるのも面白そうなので、挑戦してみようかと思います。
ではでは。
p.s.
一応githubにpushしましたので、よろしければ参考いただければと思います〜
https://github.com/hayatetabata/BlockChainOnPython
参考
https://postd.cc/learn-blockchains-by-building-one/
https://programmingblockchain.gitbooks.io/programmingblockchain-japanese/content/bitcoin_transfer/blockchain.html