Pythonでブロックチェーンを学ぶ -3-
前回は 各VMから取得してきたステータスをブロックチェーンに追加うトランザクション処理を行ったり、プルーフ・オブ・ワークでノンス値を探したりするプログラムを書きました。最後の第3回目では、トランザクション処理を待ち受けるAPIをFlaskで実装してきます。
API実装の大まかな流れ
とりあえずpipでflaskを入れて、本体をimport。 ここからデコレータでパスを指定し、そこにアクセスが来たときに行う処理を関数で定義するという形で作っていく。
パスの作成は @app.route('/xxx/yyy', methods=['']) という形で API叩く用にURLのpathを用意してあげて、RESTAPIのmethod (POST/GET/etc..)を書いてあげる。
================主な流れ===================
- Flaskノード作成して ユニークアドレスを作る
- ブロックチェーンクラスをインスタンス化する
- トランザクション追加用API 作成 (method=POST)
- マイニングをトリガーするAPI作成(method=GET)
- ブロックチェーンをリターン(chain エントリを作る)
- 複数ノード間での同期処理 (チェーンの置き換え)
- port5000でサーバを起動
==========================================
'''前回(第2回目)の最初に定義したが、念の為ここでも定義'''
from uuid import uuid4
from flask import Flask, jsonify, request
'''ノードを作る
Flaskについて詳しくはこちらを読んでほしい http://flask.pocoo.org/docs/0.12/quickstart/#a-minimal-application'''
app = Flask(__name__)
'''このノードのグローバルにユニークなアドレスを作る'''
node_identifire = str(uuid4()).replace('-', '')
'''ブロックチェーンクラスをインスタンス化する'''
blockchain = Blockchain()
'''トランザクションを加えるためのメソッド'''
@app.route('/transactions/new', methods=['POST'])
def new_transaction():
values = request.get_json()
''' POSTされたデータに必要なデータがあるかを確認 '''
required = ['cpuPct', 'maxMemAlloc', 'memFree', 'modTs']
if not all(k in values for k in required):
return 'Missing values', 400
index = blockchain.new_transaction(values['cpuPct'], values['maxMemAlloc'], values['memFree'], values['modTs'])
#obj = {"cpuPct": cpuPct1, "maxMemAlloc": maxMemAlloc1, "memFree": memFree1, "modTs": modTs1}
response = {'message': f'トランザクションはブロック{index}に追加されたました'}
return jsonify(response), 201
'''マイニングをトリガーする処理
メソッドは GETで /mine エンドポイントを作る'''
@app.route('/mine', methods=['GET'])
def mine():
# 次のプルーフを見つけるためプルーフ・オブ・ワークアルゴリズムを使用する
last_block = blockchain.last_block
last_proof = last_block['proof']
proof = blockchain.proof_of_work(last_proof)
# プルーフを見つけたことに対する報酬を得る
# 送信者は採掘者が新しいコインを採掘したことを表すため、"0" とする
blockchain.new_transaction(
cpuPct="this transaction is for result of mining",
maxMemAlloc="n/a",
memFree="n/a",
modTs="n/a"
)
#チェーンに新しいブロックを加えることで、新しいブロックを採掘する
block = blockchain.new_block(proof)
response = {
'messages': '新しいブロックを追加しました',
'index':block['index'],
'transactions':block['transactions'],
'proof':block['proof'],
'previous_hash':block['previous_hash']
}
return jsonify(response), 200
''' ブロックチェーンをリターンする /chain エントリを作る '''
@app.route('/chain', methods=['GET'])
def full_chain():
response = {
'chain': blockchain.chain,
'length': len(blockchain.chain),
}
return jsonify(response), 200
@app.route('/nodes/register', methods=['POST'])
def register_node():
values = request.get_json()
nodes = values.get('nodes')
if nodes is None:
return "Error: 有効なノードではないリストです", 400
for node in nodes:
blockchain.register_node(node)
response = {
'message': '新しいノードが追加されました',
'total_nodes': list(blockchain.nodes),
}
return jsonify(response), 201
''' 複数ノード間での同期処理 チェーンの置き換え '''
@app.route('/nodes/resolve', methods=['GET'])
def consensus():
replaced = blockchain.resolve_conflicts()
if replaced:
response = {
'message': 'チェーンが置き換えられました',
'new_chain': blockchain.chain
}
else:
response = {
'message': 'チェーンが確認されました',
'chain': blockchain.chain
}
return jsonify(response), 200
''' port5000でサーバを軌道 '''
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
これで一先ずブロックチェーンの処理を行うプログラムは完成です。Raspberry Pi 1台だとCPU性能が低いので Proof of Workの計算を簡単にしていますが、基本的な仮想通貨のマイニングはこのような処理で行われていることが理解できます。
理論としてはシンプルですが、そのセキュリティは超堅牢で様々なマーケットへの拡張性も優れており、個人的には AI、人工知能に比べて社会変革を起こすテクノロジーとして期待しています。
参考:第2回と第3回で書いたプログラムをまとめたのが以下。
# coding: UTF-8
import hashlib
import json
from time import time
from uuid import uuid4
from flask import Flask, jsonify, request
from urllib.parse import urlparse
import requests
class Blockchain(object):
def __init__(self):
"""
コンストラクトの設定。ブロックを作る初期設定
"""
self.chain = []
self.current_transactions = []
self.nodes = set()
self.new_block(previous_hash=1, proof=100)
def new_block(self, proof, previous_hash=None):
"""
ブロックチェーンに新しいブロックを作る
:param proof: <int> プルーフ・オブ・ワークアルゴリズムから得られるプルーフ
:param previous_hash: (オプション) <str> 前のブロックのハッシュ
"""
block = {
'index': len(self.chain) + 1,
'timestamp': time(),
'transactions': self.current_transactions,
'proof': proof,
'previous_hash': previous_hash or self.hash(self.chain[-1]),
}
# 現在のトランザクションをリセット
self.current_transactions = []
# 新しいブロックを作り、チェーンに加える
self.chain.append(block)
return block
def new_transaction(self, cpuPct, maxMemAlloc, memFree, modTs):
"""
新しいトランザクションをリストに加える
次に採掘されるブロックに加える新しいトランザクションを作る
:param cpuPct: <int> CPU使用率
:param maxMemAlloc: <int> メモリ割当量
:param memFree: <int> Freeメモリ
:param modT: <int> 上記パラメーターの最終更新時間
"""
self.current_transactions.append({
'cpuPct': cpuPct,
'maxMemAlloc': maxMemAlloc,
'memFree': memFree,
'modTs': modTs
})
return self.last_block['index'] + 1
def proof_of_work(self, last_proof):
"""
プルーフ・オブ・ワークのアルゴリズム:
- hash(xx') の最初の4つが0となるような x' を探す
- x は1つ前のブロックのプルーフ、 x' は新しいブロックのプルーフ
:param last_proof: <int>
:return: <int>
"""
proof = 0
while self.valid_proof(last_proof, proof) is False:
proof += 1
return proof
def register_node(self, address):
"""
ノードリストに新しいノードを加える
:param address: <str> ノードのアドレス 例: 'http://192.168.0.5:5000'
:return: None
"""
parsed_url = urlparse(address)
self.nodes.add(parsed_url.netloc)
def valid_chain(self, chain):
"""
ブロックチェーンが正しいかを確認する
:param chain: <list> ブロックチェーン
:return: <bool> True であれば正しく、 False であればそうではない
"""
last_block = chain[0]
current_index = 1
while current_index < len(chain):
block = chain[current_index]
print(f'{last_block}')
print(f'{block}')
print("\n-------------------\n")
#ブロックのハッシュが正しいかを確認
if block['previous_hash'] != self.hash(last_block):
return False
# PoWが正しいかどうかを確認
if not self.valid_proof(last_block['proof'], block['proof']):
return False
last_block = block
current_index += 1
return True
def resolve_conflicts(self):
"""
コンセンサスアルゴリズム
ネットワーク上の最も長いチェーンで置き換える
これによって複数ノードで同期した場合のコンフリクトを解消する
:return: <bool> 自らのチェーンが置き換えられると True 、そうでなれけば False
"""
neighbours = self.nodes
new_chain = None
"""
自らのチェーンより長いチェーンを探す
"""
max_length = len(self.chain)
"""
他のすべてのノードのチェーンを確認
"""
for node in neighbours:
response = requests.get(f'http://{node}/chain')
print(response)
if response.status_code == 200:
length = response.json()['length']
chain = response.json()['chain']
"""
そのチェーンがより長いか、有効かを確認
"""
if length > max_length and self.valid_chain(chain):
max_length = length
new_chain = chain
"""
もし自らのチェーンより長く、かつ有効なチェーンを見つけた場合それで置き換える
"""
if new_chain:
self.chain = new_chain
return True
return False
@staticmethod
def hash(block):
"""
ブロックの SHA-256 ハッシュを作る
:param block: <dict> ブロック
:return: <str>
"""
#必ずdict objectがソートされている必要がある。
block_string = json.dumps(block, sort_keys=True).encode()
return hashlib.sha256(block_string).hexdigest()
@staticmethod
def valid_proof(last_proof, proof):
"""
hash(xx') の最初の4つが0となるような x' を探す
"""
guess = f'{last_proof}{proof}'.encode()
# f文字列について-> http://atsuoishimoto.hatenablog.com/entry/2016/12/25/122220
guess_hash = hashlib.sha256(guess).hexdigest()
return guess_hash[:4] == "0000"
@property
def last_block(self):
# チェーンの最後のブロックをリターンする
return self.chain[-1]
'''ノードを作る
Flaskについて詳しくはこちらを読んでほしい http://flask.pocoo.org/docs/0.12/quickstart/#a-minimal-application'''
app = Flask(__name__)
'''このノードのグローバルにユニークなアドレスを作る'''
node_identifire = str(uuid4()).replace('-', '')
'''ブロックチェーンクラスをインスタンス化する'''
blockchain = Blockchain()
'''トランザクションを加えるためのメソッド'''
@app.route('/transactions/new', methods=['POST'])
def new_transaction():
values = request.get_json()
''' POSTされたデータに必要なデータがあるかを確認 '''
required = ['cpuPct', 'maxMemAlloc', 'memFree', 'modTs']
if not all(k in values for k in required):
return 'Missing values', 400
index = blockchain.new_transaction(values['cpuPct'], values['maxMemAlloc'], values['memFree'], values['modTs'])
#obj = {"cpuPct": cpuPct1, "maxMemAlloc": maxMemAlloc1, "memFree": memFree1, "modTs": modTs1}
response = {'message': f'トランザクションはブロック{index}に追加されたました'}
return jsonify(response), 201
'''マイニングをトリガーする処理
メソッドは GETで /mine エンドポイントを作る'''
@app.route('/mine', methods=['GET'])
def mine():
# 次のプルーフを見つけるためプルーフ・オブ・ワークアルゴリズムを使用する
last_block = blockchain.last_block
last_proof = last_block['proof']
proof = blockchain.proof_of_work(last_proof)
# プルーフを見つけたことに対する報酬を得る
# 送信者は採掘者が新しいコインを採掘したことを表すため、"0" とする
blockchain.new_transaction(
cpuPct="this transaction is for result of mining",
maxMemAlloc="n/a",
memFree="n/a",
modTs="n/a"
)
#チェーンに新しいブロックを加えることで、新しいブロックを採掘する
block = blockchain.new_block(proof)
response = {
'messages': '新しいブロックを追加しました',
'index':block['index'],
'transactions':block['transactions'],
'proof':block['proof'],
'previous_hash':block['previous_hash']
}
return jsonify(response), 200
''' ブロックチェーンをリターンする /chain エントリを作る '''
@app.route('/chain', methods=['GET'])
def full_chain():
response = {
'chain': blockchain.chain,
'length': len(blockchain.chain),
}
return jsonify(response), 200
@app.route('/nodes/register', methods=['POST'])
def register_node():
values = request.get_json()
nodes = values.get('nodes')
if nodes is None:
return "Error: 有効なノードではないリストです", 400
for node in nodes:
blockchain.register_node(node)
response = {
'message': '新しいノードが追加されました',
'total_nodes': list(blockchain.nodes),
}
return jsonify(response), 201
''' 複数ノード間での同期処理 チェーンの置き換え '''
@app.route('/nodes/resolve', methods=['GET'])
def consensus():
replaced = blockchain.resolve_conflicts()
if replaced:
response = {
'message': 'チェーンが置き換えられました',
'new_chain': blockchain.chain
}
else:
response = {
'message': 'チェーンが確認されました',
'chain': blockchain.chain
}
return jsonify(response), 200
''' port5000でサーバを軌道 '''
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
今回の"note"を気に入って頂けましたら、是非サポートをお願いいたします!