ブロックチェーンのこの部分では、技術的な観点を説明する簡単なブロックチェーンシステムのPythonを達成するために、ブロック鎖の構造と原理をより良く理解します。
但し、1、
-
あなたはブロック鎖のコンセプトの下に理解する必要があります。
概念は、(P2Pある)ポイント送信、その性質に分散ファイルシステムの集中化データベースである、クレジットのビットブロック鎖に(ウィキペディアから取られた)コンピュータ技術コンセンサスパターンメカニズム、暗号化アルゴリズムの新たなアプリケーションを発信しました。
-
私たちは、ハッシュの概念を理解する必要があります。
:あなたはハッシュを理解していない場合は、ほとんど次のアドレスにノウハウの内容を表示することができます
https://www.zhihu.com/question/26762707 -
あなたは、HTTPの動作原理を理解する必要があります。
言葉を理解、自身が百度やGoogleません。
-
インストールカスタマサポートHTTPプロトコル終了
郵便配達やカールが、その他であってもよいです。
-
Python3.6 +をインストールするために必要なその他の文書
後の章「5は、コンパイル済みのPythonのファイルは、」詳細に説明します。
-
送信元アドレス
https://github.com/dvf/blockchain
図2に示すように、ブロックのチェーンを作成します
Pythonプログラムは、テキストエディタやIDEなどサブライン、EditPlus、PyCharm、VSCode、アトムなどのさまざまな記述することができます。
ファイルを格納するために使用されるようにPythonで書かれた「py_blockchain_demo」と名付けされた時間のために、新しいディレクトリを作成し、「blockchain.py」ファイル名を作成します。
2.1概要ブロックチェーン
作成しBlockchain
たクラスのコンストラクタは空のリストの初期化(当社のブロック鎖を格納するため)、および他のストレージトランザクションを作成します。以下は、私たちのクラスの例を示します。
class Blockchain(object):
def __init__(self):
self.chain = []
self.current_transactions = []
def new_block(self):
# Creates a new Block and adds it to the chain
pass
def new_transaction(self):
# Adds a new transaction to the list of transactions
pass
@staticmethod
def hash(block):
# Hashes a Block
pass
@property
def last_block(self):
# Returns the last Block in the chain
pass
Blockchain
クラスは、それがトランザクションを格納し、また、メソッド・チェーン・データに新しいブロックを追加し、チェーン・データを管理する責任があります。さんが拡大するより多くの方法を見てみましょう。
2.2ブロック構造
各ブロックは持っている索引
、时间戳(Unix时间戳)
、事务列表
、校验(稍后详述)
、とします前一个块的散列
。
ここでは、ブロックの例です。
block = {
'index': 1,
'timestamp': 1506057125.900785,
'transactions': [
{
'sender': "8527147fe1f5426f9dd545de4b27ee00",
'recipient': "a77f5cdfa2934df3954a5c7c7da5df1f",
'amount': 5,
}
],
'proof': 324984774000,
'previous_hash': "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
}
この時点で、区块链
コンセプトは明白である-それは、その中にブロックする前に、それぞれの新しいブロックに含まれています散列
。これがあるので、これは、非常に重要である区块链
理由は、変更することはできません。攻撃者が損傷している場合は区块链
、以前のブロックでは、後続のすべてのブロックが不正なハッシュ値が含まれます。
それは理にかなって?これは、ブロック鎖の背後にあるコアアイデアです-あなたは考え出したていない場合は、慎重に考える時間がかかります。私が学んで学校の友人エディターズチョイスのPythonを学ぶ方法がわからない混乱してあります学習qun 315 -346- 913を一緒にまとめて、進捗状況を学ぶことを学ぶことができます!無料動画を共有
2.3取引ブロックに追加
私たちは道をブロックするためにトランザクションを追加する必要があります。私どもの責任のnew_transaction()メソッドはこれです、それは非常に簡単です:
class Blockchain(object):
...
def new_transaction(self, sender, recipient, amount):
"""
Creates a new transaction to go into the next mined Block
:param sender: <str> Address of the Sender
:param recipient: <str> Address of the Recipient
:param amount: <int> Amount
:return: <int> The index of the Block that will hold this transaction
"""
self.current_transactions.append({
'sender': sender,
'recipient': recipient,
'amount': amount,
})
return self.last_block['index'] + 1
new_transaction()
リストに後から次の採掘のためのトランザクションを送信するユーザーに伝えるために便利な---取引の指標がブロックに追加されます返す取引方法を、追加します。
新しいブロックを作成するには、2.4
私たちの場合はBlockchain
バックがインスタンス化され、我々は(何の大手ブロックブロック)の作成をブロックする必要はありません行くためにそれに追加。また、これは採鉱(または作業の証明)の結果であることを証明した私たちの起源のブロックを追加する必要があります。私たちは、後に詳細に採掘を説明します。
コンストラクタでブロックの作成の作成に加えて、我々は補完しnew_block()
、new_transaction()
かつhash()
機能:
import hashlib
import json
from time import time
class Blockchain(object):
def __init__(self):
self.current_transactions = []
self.chain = []
# 创建创世区块
self.new_block(previous_hash=1, proof=100)
def new_block(self, proof, previous_hash=None):
"""
创建一个新的区块到区块链中
:param proof: <int> 由工作证明算法生成的证明
:param previous_hash: (Optional) <str> 前一个区块的 hash 值
: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> 持有本次交易的区块索引
"""
self.current_transactions.append({
'sender': sender,
'recipient': recipient,
'amount': amount,
})
return self.last_block['index'] + 1
@property
def last_block(self):
return self.chain[-1]
@staticmethod
def hash(block):
"""
给一个区块生成 SHA-256 值
:param block: <dict> Block
:return: <str>
"""
# 我们必须确保这个字典(区块)是经过排序的,否则我们将会得到不一致的散列
block_string = json.dumps(block, sort_keys=True).encode()
return hashlib.sha256(block_string).hexdigest()
上記のコードは、コード明確にするために---簡単です、私はいくつかのコメントやドキュメントを追加しました。私たちは、ほとんど私たちのブロック鎖を完了しました。しかし、この時点で、あなたは、新しいブロックが作成されるか疑問偽造や採掘する必要があります。
3、ワークロードは、アルゴリズムを証明しました
3.1証明したワークロードを理解します
新しいブロックを作成したり、ブロック鎖をタップする方法を示すために、(POW)アルゴリズム証明したワークロードを使用してください。捕虜の目標は数値満たす特定の条件を計算することで、この数字は非常に計算に懸念皆のため難しいが、検証するのは簡単でなければなりません。これは証明した作品の背後にあるコアアイデアです。
私たちは、あなたが理解を助けるために簡単な例が表示されます。
仮定整数xは、yは0で終了しなければならない他の整数の積のハッシュ値、すなわち、ハッシュ(Xの*のY)= ac23dc ... 0が乗算されます。X = 5に設定し、Yを見つけますか?Pythonで実装:
from hashlib import sha256
x = 5
y = 0 # We don't know what y should be yet...
while sha256(f'{x*y}'.encode()).hexdigest()[-1] != "0":
y += 1
print(f'The solution is y = {y}')
結果は:yは21 =。ので、生成したハッシュ値の最後にはゼロでなければなりません。
hash(5 * 21) = 1253e9373e...5e3600155e860
ビットコインでは、アルゴリズムを証明するための努力が呼び出されのHashCash、それが上記の質問に非常に似ていますが、計算するのは非常に難しいです。これは、鉱山労働者は、ブロックと競合するコンピューティングを作成する権利のために競争する問題です。一般的に、満たすべき対象と特定の文字列の計算困難の数に比例し、鉱山労働者は、結果を計算するには、ビットコインの報酬(取引)の一定数を取得します。
非常に簡単に、もちろん、結果を確認してください。
3.2証明するために努力
のは、類似した捕虜のアルゴリズムを実装してみましょう。上記実施例と同様のルール:
それは4ゼロの文字列の先頭に前プルーフブロックハッシュ値とスプライスされるように、数Pを見つけます。
import hashlib
import json
from time import time
from uuid import uuid4
class Blockchain(object):
...
def proof_of_work(self, last_proof):
"""
Simple Proof of Work Algorithm:
- Find a number p' such that hash(pp') contains leading 4 zeroes, where p is the previous p'
- p is the previous proof, and p' is the new proof
: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):
"""
Validates the Proof: Does hash(last_proof, proof) contain 4 leading zeroes?
:param last_proof: <int> Previous Proof
:param proof: <int> Current Proof
:return: <bool> True if correct, False if not.
"""
guess = f'{last_proof}{proof}'.encode()
guess_hash = hashlib.sha256(guess).hexdigest()
return guess_hash[:4] == "0000"
アルゴリズムの複雑さを測定する方法は、数字のゼロの始まりを変更することです。デモに4を使用して、あなたはゼロが大幅に結果を計算するのに必要な時間を増加させるよりも多くを見つけるでしょう。
今Blockchainクラスは、基本的には、対話するための次の使用HTTP要求を完了しました。
4、Blockchain APIインタフェース
私たちは、Pythonの関数にマップされたネットワーク要求を促進する軽量Webアプリケーションフレームワークであり、Pythonのフラスコのフレームワークを使用し、今はBlockchainがベースフラスコウェブ上で実行するようにする必要があります。
我々は3つのインターフェースを作成します。
- 新しい/取引/:取引を作成して、ブロックに追加しました
- /マイン:新しいブロックをタップするサーバーを教えて
- /チェーン:チェーン全体のブロックを返します
4.1ノードの作成
私たちの「フラスコサーバ」ブロック鎖は、ネットワーク内のノードを再生します。のは、いくつかのスケルトンコードを追加してみましょう:
import hashlib
import json
from textwrap import dedent
from time import time
from uuid import uuid4
from flask import Flask
class Blockchain(object):
...
# 实例化节点
app = Flask(__name__)
# 为此节点生成一个全球唯一的地址
node_identifier = str(uuid4()).replace('-', '')
# 实例化 Blockchain 类
blockchain = Blockchain()
# 创建 /mine 接口,GET 方式请求
@app.route('/mine', methods=['GET'])
def mine():
return "We'll mine a new Block"
# 创建 /transactions/new 接口,POST 方式请求,可以给接口发送交易数据
@app.route('/transactions/new', methods=['POST'])
def new_transaction():
return "We'll add a new transaction"
# 创建 /chain 接口,返回整个区块链
@app.route('/chain', methods=['GET'])
def full_chain():
response = {
'chain': blockchain.chain,
'length': len(blockchain.chain),
}
return jsonify(response), 200
# 服务器运行端口 5000
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
4.2は、トランザクションを送信します
次のようにノードの構造に送られたのです。
{
"sender": "my address",
"recipient": "someone else's address",
"amount": 5
}
我々はすでにトランザクション・ベースのインターフェイスを追加するので、トランザクションを追加する方法を持っているので、非常に簡単です。トランザクションを追加するための関数を書いてみましょう:
import hashlib
import json
from textwrap import dedent
from time import time
from uuid import uuid4
from flask import Flask, jsonify, request
...
@app.route('/transactions/new', methods=['POST'])
def new_transaction():
values = request.get_json()
# Check that the required fields are in the POST'ed data
required = ['sender', 'recipient', 'amount']
if not all(k in values for k in required):
return 'Missing values', 400
# Create a new Transaction
index = blockchain.new_transaction(values['sender'], values['recipient'], values['amount'])
response = {'message': f'Transaction will be added to Block {index}'}
return jsonify(response), 201
4.3マイニング
鉱業の魔法は、およそ3つのことを行うことは非常に簡単である場合、それは次のようになります。
1.ワークロードが捕虜証明計算し
、新しいトランザクションによって付与された2鉱夫(自分の)通貨
3.新しいブロックを構築し、チェーンに追加します
import hashlib
import json
from time import time
from uuid import uuid4
from flask import Flask, jsonify, request
...
@app.route('/mine', methods=['GET'])
def mine():
# We run the proof of work algorithm to get the next proof...
last_block = blockchain.last_block
last_proof = last_block['proof']
proof = blockchain.proof_of_work(last_proof)
# We must receive a reward for finding the proof.
# The sender is "0" to signify that this node has mined a new coin.
blockchain.new_transaction(
sender="0",
recipient=node_identifier,
amount=1,
)
# Forge the new Block by adding it to the chain
previous_hash = blockchain.hash(last_block)
block = blockchain.new_block(proof, previous_hash)
response = {
'message': "New Block Forged",
'index': block['index'],
'transactions': block['transactions'],
'proof': block['proof'],
'previous_hash': block['previous_hash'],
}
return jsonify(response), 200
トランザクションの受信者は私たち自身のサーバー・ノードであることに注意してください、私たちはほとんどの作業を行うだけでBlockchainクラスのメソッドを中心に対話することです。これまでのところ、私たちも完成チェーンをブロックし、我々は実際に実行します。
5、コンパイルされたPythonファイル
少し問題ここでMacのコンパイラのpython3では、特に説明します。
5.1のMacのインストールのpython3
Pythonは、サードパーティのライブラリのその富の理由の一つ好調に推移しました。ピップは、サードパーティのライブラリのpythonパッケージ管理ツールです。
マックpython2とのpython3が共存以来。したがってのpython3対応するパッケージ管理ツールの戒めはPIP3です。
あなたが自作でのpython3をインストールする場合は、PIP3がインストールされます。自作して直接のpython3をインストールすることが提案されています。
# 安装Python3
brew install python3
# 检查Python3版本(即检查是否安装成功)
python3 -V
5.2インストールPipenv
多くのPythonのバージョン、多くの場合、異なるプロジェクトはまた、我々はより多くの仮想Python環境を構築するために必要なサードパーティのライブラリの異なるバージョンを使用することができる、開発中のPythonの異なるバージョンを使用する必要があります。Pythonの仮想環境をより便利にできます公式の推奨ツールをpipenv。
# 安装pipenv
pip3 install pipenv
Pythonの5.3仮想環境を作成します。
ディレクトリにPythonのファイルは、順番に次のコマンドを実行します
# 创建Python虚拟环境,并指定Python版本
pipenv --python=python3.6
# 安装Flask、requests模块
pipenv install Flask==0.12.2 requests==2.18.4
6、実行ブロックチェーン
あなたは対話するためにcURLのか、郵便配達やAPIを使用することができます。
ポート番号pipenvを指定します(コードデフォルトポート5000は「-p 5000」の後ろに省略することができる)、および断絶を開始します。
pipenv run python blockchain.py -p 5000
端末は、以下に示されています。
wenzildeiMac:py_blockchain_demo wenzil$ pipenv run python blockchain.py -p 5000
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
私たちの要求してみましょう5000 /マイン:// localhostのHTTP(GET)鉱業ました:
採掘
ポストマンでGETリクエストを開始します。
トランザクション要求を作成し、要求5000 /取引/新新:// localhostを:HTTP示すように、(POST)を:
トランザクション要求を作成します。
「ヘッダ」は、このようなキー値を追加し、レポートサーバーがエラー「500 Internal Server Error」を返します。
'content-type':'application/json'
ポストマンを使用していない場合は、cURLの文の使用を見て同じです。
$ curl -X POST -H "Content-Type: application/json" -d '{
"sender": "d4ee26eee15148ee92c6cd394edd974e",
"recipient": "someone-other-address",
"amount": 5
}' "http://localhost:5000/transactions/new"
後に2つの要求を提出し、トランザクションを有効にするために、鉱業(つまり、要求「地雷」であるインタフェース)が必要です。
再びスタート取引をマイニング
この場合、再び要求することにより、HTTPを:// localhostを:5000 /チェーンは、すべての情報ブロックを取得することができます。
{
"chain": [
{
"index": 1,
"previous_hash": "1",
"proof": 100,
"timestamp": 1528705939.274652,
"transactions": []
},
{
"index": 2,
"previous_hash": "22efcab1c2990f7d371e5159fe9f753ea4f5b0f9b173f0e41004eb1b6efcd1f7",
"proof": 24960,
"timestamp": 1528705943.739656,
"transactions": [
{
"amount": 1,
"recipient": "2aaa370d7d604b708678a510f11da5d7",
"sender": "0"
}
]
},
{
"index": 3,
"previous_hash": "0cfdac7a19302f5d3af7d20ebf7997afadd88b5f4f937ade867c24701fc21158",
"proof": 9425,
"timestamp": 1528706168.36694,
"transactions": [
{
"amount": 5,
"recipient": "someone-other-address",
"sender": "d4ee26eee15148ee92c6cd394edd974e"
},
{
"amount": 5,
"recipient": "someone-other-address",
"sender": "d4ee26eee15148ee92c6cd394edd974e"
},
{
"amount": 1,
"recipient": "2aaa370d7d604b708678a510f11da5d7",
"sender": "0"
}
]
}
],
"length": 3
}
図7に示すように、一貫性(コンセンサス)
私たちは、基本的なブロック鎖がお得な情報や採掘を受け入れることができます持っています。しかし、ブロックチェーンシステムは、分散されなければなりません。それが配布されているので、我々は実際にすべてのノードが同じチェーンそれを持っていることを保証するもの取りますか?これは、我々は、ネットワーク上の複数のノードを持つようにしたい一貫性である、整合性アルゴリズムを達成するために必要です
7.1ノードを登録します
コンセンサスアルゴリズムを実装する前に、我々は、ノードがその隣接ノードを知っているようにする方法を見つける必要があります。各ノードは、ネットワーク内の他のノードを含むレコードを維持する必要があります。それでは、いくつかのインターフェイスを追加してみましょう。
1、/ノード/登録:.新しいノードがの形でURLのリストを受け取り、
2、/ノードは/解決:.実装適合アルゴリズムを、任意の競合を解決するために、あなたは正しいチェーンノード持っていることを確認し
、我々はBlockchain下のinit関数を変更し、登録されているノードを提供します方法:
...
from urllib.parse import urlparse
...
class Blockchain(object):
def __init__(self):
...
self.nodes = set()
...
def register_node(self, address):
"""
Add a new node to the list of nodes
:param address: <str> Address of node. Eg. 'http://192.168.0.5:5000'
:return: None
"""
parsed_url = urlparse(address)
self.nodes.add(parsed_url.netloc)
私たちは、繰り返しを避けるためにノードを追加する簡単な方法であるストレージノードを、設定するために使用されます。
7.2コンセンサスアルゴリズムを達成するために
別のノードにあるノードが別のチェーンを持っているとき、それについて話以前と同じように、それは紛争が生成されます。この問題を解決するために、我々は効果的な最長のチェーンは最も権威のルールで開発します。言い換えれば、このネットワークでは最長のチェーンは最も権威あります。私たちは、ネットワーク内のノード間で合意に達するために、このアルゴリズムを使用します。
...
import requests
class Blockchain(object)
...
def valid_chain(self, chain):
"""
Determine if a given blockchain is valid
:param chain: <list> A blockchain
:return: <bool> True if valid, False if not
"""
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")
# Check that the hash of the block is correct
if block['previous_hash'] != self.hash(last_block):
return False
# Check that the Proof of Work is correct
if not self.valid_proof(last_block['proof'], block['proof']):
return False
last_block = block
current_index += 1
return True
def resolve_conflicts(self):
"""
This is our Consensus Algorithm, it resolves conflicts
by replacing our chain with the longest one in the network.
:return: <bool> True if our chain was replaced, False if not
"""
neighbours = self.nodes
new_chain = None
# We're only looking for chains longer than ours
max_length = len(self.chain)
# Grab and verify the chains from all the nodes in our network
for node in neighbours:
response = requests.get(f'http://{node}/chain')
if response.status_code == 200:
length = response.json()['length']
chain = response.json()['chain']
# Check if the length is longer and the chain is valid
if length > max_length and self.valid_chain(chain):
max_length = length
new_chain = chain
# Replace our chain if we discovered a new, valid chain longer than ours
if new_chain:
self.chain = new_chain
return True
return False
第一个方法 valid_chain()
负责检查一个链是否有效,方法是遍历每个块并验证散列和证明。
resolve_conflicts()
是一个遍历我们所有邻居节点的方法,下载它们的链并使用上面的方法验证它们。 如果找到一个长度大于我们的有效链条,我们就取代我们的链条。
我们将两个端点注册到我们的API中,一个用于添加相邻节点,另一个用于解决冲突:
@app.route('/nodes/register', methods=['POST'])
def register_nodes():
values = request.get_json()
nodes = values.get('nodes')
if nodes is None:
return "Error: Please supply a valid list of nodes", 400
for node in nodes:
blockchain.register_node(node)
response = {
'message': 'New nodes have been added',
'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': 'Our chain was replaced',
'new_chain': blockchain.chain
}
else:
response = {
'message': 'Our chain is authoritative',
'chain': blockchain.chain
}
return jsonify(response), 200
在这一点上,如果你喜欢,你可以使用一台不同的机器,并在你的网络上启动不同的节点。 或者使用同一台机器上的不同端口启动进程。 我在我的机器上,不同的端口上创建了另一个节点,并将其注册到当前节点。 因此,我有两个节点:http://localhost:5000
和 http://localhost:5001
。
注册一个新节点
然后我在节点 2 上挖掘了一些新的块,以确保链条更长。 之后,我在节点1上调用 GET /nodes/resolve,其中区块链数据由一致性算法取代(注:这一步运行结果跟原文有点出入,可以查看原文的结果):
验证节点
这是一个包,去找一些朋友一起,以帮助测试你的区块链。
最终完整Python源代码如下:
import hashlib
import json
from time import time
from urllib.parse import urlparse
from uuid import uuid4
import requests
from flask import Flask, jsonify, request
class Blockchain:
def __init__(self):
self.current_transactions = []
self.chain = []
self.nodes = set()
# Create the genesis block
self.new_block(previous_hash='1', proof=100)
def register_node(self, address):
"""
Add a new node to the list of nodes
:param address: Address of node. Eg. 'http://192.168.0.5:5000'
"""
parsed_url = urlparse(address)
if parsed_url.netloc:
self.nodes.add(parsed_url.netloc)
elif parsed_url.path:
# Accepts an URL without scheme like '192.168.0.5:5000'.
self.nodes.add(parsed_url.path)
else:
raise ValueError('Invalid URL')
def valid_chain(self, chain):
"""
Determine if a given blockchain is valid
:param chain: A blockchain
:return: True if valid, False if not
"""
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")
# Check that the hash of the block is correct
last_block_hash = self.hash(last_block)
if block['previous_hash'] != last_block_hash:
return False
# Check that the Proof of Work is correct
if not self.valid_proof(last_block['proof'], block['proof'], last_block_hash):
return False
last_block = block
current_index += 1
return True
def resolve_conflicts(self):
"""
This is our consensus algorithm, it resolves conflicts
by replacing our chain with the longest one in the network.
:return: True if our chain was replaced, False if not
"""
neighbours = self.nodes
new_chain = None
# We're only looking for chains longer than ours
max_length = len(self.chain)
# Grab and verify the chains from all the nodes in our network
for node in neighbours:
response = requests.get(f'http://{node}/chain')
if response.status_code == 200:
length = response.json()['length']
chain = response.json()['chain']
# Check if the length is longer and the chain is valid
if length > max_length and self.valid_chain(chain):
max_length = length
new_chain = chain
# Replace our chain if we discovered a new, valid chain longer than ours
if new_chain:
self.chain = new_chain
return True
return False
def new_block(self, proof, previous_hash):
"""
Create a new Block in the Blockchain
:param proof: The proof given by the Proof of Work algorithm
:param previous_hash: Hash of previous Block
:return: New Block
"""
block = {
'index': len(self.chain) + 1,
'timestamp': time(),
'transactions': self.current_transactions,
'proof': proof,
'previous_hash': previous_hash or self.hash(self.chain[-1]),
}
# Reset the current list of transactions
self.current_transactions = []
self.chain.append(block)
return block
def new_transaction(self, sender, recipient, amount):
"""
Creates a new transaction to go into the next mined Block
:param sender: Address of the Sender
:param recipient: Address of the Recipient
:param amount: Amount
:return: The index of the Block that will hold this transaction
"""
self.current_transactions.append({
'sender': sender,
'recipient': recipient,
'amount': amount,
})
return self.last_block['index'] + 1
@property
def last_block(self):
return self.chain[-1]
@staticmethod
def hash(block):
"""
Creates a SHA-256 hash of a Block
:param block: Block
"""
# We must make sure that the Dictionary is Ordered, or we'll have inconsistent hashes
block_string = json.dumps(block, sort_keys=True).encode()
return hashlib.sha256(block_string).hexdigest()
def proof_of_work(self, last_block):
"""
Simple Proof of Work Algorithm:
- Find a number p' such that hash(pp') contains leading 4 zeroes
- Where p is the previous proof, and p' is the new proof
:param last_block: <dict> last Block
:return: <int>
"""
last_proof = last_block['proof']
last_hash = self.hash(last_block)
proof = 0
while self.valid_proof(last_proof, proof, last_hash) is False:
proof += 1
return proof
@staticmethod
def valid_proof(last_proof, proof, last_hash):
"""
Validates the Proof
:param last_proof: <int> Previous Proof
:param proof: <int> Current Proof
:param last_hash: <str> The hash of the Previous Block
:return: <bool> True if correct, False if not.
"""
guess = f'{last_proof}{proof}{last_hash}'.encode()
guess_hash = hashlib.sha256(guess).hexdigest()
return guess_hash[:4] == "0000"
# Instantiate the Node
app = Flask(__name__)
# Generate a globally unique address for this node
node_identifier = str(uuid4()).replace('-', '')
# Instantiate the Blockchain
blockchain = Blockchain()
@app.route('/mine', methods=['GET'])
def mine():
# We run the proof of work algorithm to get the next proof...
last_block = blockchain.last_block
proof = blockchain.proof_of_work(last_block)
# We must receive a reward for finding the proof.
# The sender is "0" to signify that this node has mined a new coin.
blockchain.new_transaction(
sender="0",
recipient=node_identifier,
amount=1,
)
# Forge the new Block by adding it to the chain
previous_hash = blockchain.hash(last_block)
block = blockchain.new_block(proof, previous_hash)
response = {
'message': "New Block Forged",
'index': block['index'],
'transactions': block['transactions'],
'proof': block['proof'],
'previous_hash': block['previous_hash'],
}
return jsonify(response), 200
@app.route('/transactions/new', methods=['POST'])
def new_transaction():
values = request.get_json()
# Check that the required fields are in the POST'ed data
required = ['sender', 'recipient', 'amount']
if not all(k in values for k in required):
return 'Missing values', 400
# Create a new Transaction
index = blockchain.new_transaction(values['sender'], values['recipient'], values['amount'])
response = {'message': f'Transaction will be added to Block {index}'}
return jsonify(response), 201
@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_nodes():
values = request.get_json()
nodes = values.get('nodes')
if nodes is None:
return "Error: Please supply a valid list of nodes", 400
for node in nodes:
blockchain.register_node(node)
response = {
'message': 'New nodes have been added',
'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': 'Our chain was replaced',
'new_chain': blockchain.chain
}
else:
response = {
'message': 'Our chain is authoritative',
'chain': blockchain.chain
}
return jsonify(response), 200
if __name__ == '__main__':
from argparse import ArgumentParser
parser = ArgumentParser()
parser.add_argument('-p', '--port', default=5000, type=int, help='port to listen on')
args = parser.parse_args()
port = args.port
app.run(host='0.0.0.0', port=port)