Last active
November 4, 2017 14:26
-
-
Save mutsune/9f26ea8fe1f650214807a60dfbe14ae2 to your computer and use it in GitHub Desktop.
ブロックチェーンを作ることで学ぶ 〜ブロックチェーンがどのように動いているのか学ぶ最速の方法は作ってみることだ〜 https://qiita.com/hidehiro98/items/841ece65d896aeaa8a2a のコード。タイポ修正やコメントを微妙に書き加えた
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# coding: UTF-8 | |
import hashlib | |
import json | |
from urllib.parse import urlparse | |
from time import time | |
from uuid import uuid4 | |
import requests | |
from flask import Flask, jsonify, request | |
class Blockchain(object): | |
def __init__(self): | |
self.chain = [] | |
self.current_transactions = [] | |
self.nodes = set() | |
# ジェネシスブロックを作る | |
self.new_block(previous_hash=1, proof=100) | |
def proof_of_work(self, last_proof): | |
""" | |
シンプルなプルーフ・オブ・ワークのアルゴリズム: | |
- hash(pp') の最初の4つが0となるような p' を探す | |
- p は前のプルーフ、 p' は新しいプルーフ | |
:param last_proof: <int> | |
:return: <int> | |
""" | |
proof = 0 | |
while self.valid_proof(last_proof, proof) is False: | |
proof += 1 | |
return proof | |
@staticmethod | |
def valid_proof(last_proof, proof): | |
""" | |
プルーフが正しいかを確認する: hash(last_proof*proof)の最初の4つが0となっているか? | |
:param last_proof: <int> 前のプルーフ | |
:param proof: <int> 現在のプルーフ | |
:return: <bool> 正しければ true 、そうでなれけば false | |
""" | |
guess = f'{last_proof}{proof}'.encode() | |
guess_hash = hashlib.sha256(guess).hexdigest() | |
return guess_hash[:4] == "0000" | |
def new_block(self, proof, previous_hash=None): | |
""" | |
新しいブロックを作り、チェーンに加える | |
ブロックチェーンに新しいブロックを作る | |
:param proof: <int> プルーフ・オブ・ワークアルゴリズムから得られるプルーフ | |
:param previous_hash: (オプション) <str> 前のブロックのハッシュ | |
:return: <dict> 新しいブロック | |
""" | |
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, sender, recipient, amount): | |
""" | |
新しいトランザクションをリストに加える | |
次に採掘されるブロックに加える新しいトランザクションを作る | |
:param sender: <str> 送信者のアドレス | |
:param recipient: <str> 受信者のアドレス | |
:param amount: <int> 量 | |
:return: <int> このトランザクションを含むブロックのアドレス | |
""" | |
# TODO: amount は何の量? | |
self.current_transactions.append({ | |
'sender': sender, | |
'recipient': recipient, | |
'amount': amount, | |
}) | |
return self.last_block['index'] + 1 | |
@staticmethod | |
def hash(block): | |
""" | |
ブロックをハッシュ化する | |
ブロックの SHA-256 ハッシュを作る | |
:param block: <dict> ブロック | |
:return: <str> | |
""" | |
# 必ずディクショナリ(辞書型のオブジェクト)がソートされている必要がある。そうでないと、一貫性のないハッシュとなってしまう | |
block_string = json.dumps(block, sort_keys=True).encode() | |
return hashlib.sha256(block_string).hexdigest() | |
@property | |
def last_block(self): | |
# チェーンの最後のブロックを取得 | |
return self.chain[-1] | |
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 | |
# プルーフ・オブ・ワークが正しいかを確認 | |
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') | |
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 | |
# ノードを作る | |
app = Flask(__name__) | |
# このノードのグローバルにユニークなアドレスを作る | |
node_identifier = str(uuid4()).replace('-', '') | |
# ブロックチェーンクラスをインスタンス化する | |
blockchain = Blockchain() | |
# /transactions/new エンドポイントを作る。メソッドは POST | |
# 新しいトランザクションを追加します | |
@app.route('/transactions/new', methods=['POST']) | |
def new_transactions(): | |
values = request.get_json() | |
# POSTされたデータに必要なデータがあるかを確認 | |
required = ['sender', 'recipient', 'amount'] | |
if not all(k in values for k in required): | |
return 'Missing values', 400 | |
# 新しいトランザクションを作る | |
index = blockchain.new_transaction(values['sender'], values['recipient'], values['amount']) | |
response = {'message': f'トランザクションはブロック {index} に追加されました'} | |
return jsonify(response), 201 | |
# /mine エンドポイントを作る。メソッドは GET | |
# 新しいブロックを採掘する | |
@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( | |
sender="0", | |
recipient=node_identifier, | |
amount=1, | |
) | |
# チェーンに新しいブロックを加えることで、新しいブロックを採掘する | |
block = blockchain.new_block(proof) | |
response = { | |
'message': '新しいブロックを採掘しました', | |
'index': block['index'], | |
'transactions': block['transactions'], | |
'proof': block['proof'], | |
'previous_hash': block['previous_hash'], | |
} | |
return jsonify(response), 200 | |
# /chain エンドポイントを作る。メソッドは GET | |
# すべてのブロックチェーンを返す | |
@app.route('/chain', methods=['GET']) | |
def full_chain(): | |
response = { | |
'chain': blockchain.chain, | |
'length': len(blockchain.chain), | |
} | |
return jsonify(response), 200 # サーバを起動 | |
# /nodes/register エンドポイントを作る。メソッドは POST | |
# ネットワーク上に他のノードを追加する | |
@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 | |
# /nodes/resolve エンドポイントを作る。メソッドは GET | |
# コンフリクトを解消する | |
@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 | |
if __name__ == '__main__': | |
app.run(host='127.0.0.1', port=5000) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[[source]] | |
url = "https://pypi.python.org/simple" | |
verify_ssl = true | |
name = "pypi" | |
[dev-packages] | |
[requires] | |
python_version = "3.6" | |
[packages] | |
flask = "==0.12.2" | |
requests = "==2.18.4" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment