بلاکچین، زنجیرهای تغییرناپذیر و متوالی از دادههای دستهبندیشده به نام بلاک است. بلاکها میتوانند شامل تراکنشها، فایلها یا هر گونه اطلاعات مورد نظر شما باشند. اما نکته مهم این است که با استفاده از هشها به یکدیگر متصل شدهاند.یکی از راههای سریع برای فهم کارکرد بلاکچین، ساخت بلاکچین با پایتون است. کمی تعجببرانگیز است، نه؟ چطور وقتی نمیدانیم یک چیزی چطور کار میکند، آن را بسازیم؟ در واقع میتوان آموختن را با اجرای مرحلهبهمرحله تلفیق کرد. با خواندن ادامه این مطلب، نحوه کار بلاکچین را میآموزید و همچنین میتوانید یک بلاکچین بسازید. با میهن بلاکچین همراه باشید.
آموزش ساخت بلاکچین با پایتون
برای ساخت بلاکچین باید به خواندن و نوشتن اصول زبان برنامهنویسی Python آشنا باشید. همچنین باید نحوه کار درخواستهای HTTP را بدانید؛ زیرا با HTTP با بلاکچین صحبت خواهیم کرد.
مواد لازم برای ساخت بلاکچین:
- نصب پایتون ۳.۶ به بالا
- نسخه ۰.۱۲.۲ فلسک (Flask)
- نسخه ۲.۱۸.۴ کتابخانه درخواست (Requests library)
- راهاندازی یکی از کلاینتهای HTTP نظیر Postman یا cURL نیز نیاز دارید.
مرحله اول: تعریف بلاک و تراکنش
ویرایشگر متن (IDE) مورد نظر خود را اجرا کنید. ویرایشگر متن PyCharm گزینه خوبی است. فایل جدیدی به اسم blockchain.py ایجاد کنید. اول یک کلاس بلاکچینی ایجاد میکنیم و یک فهرست خالی برای ذخیره بلاکچین و فهرست دیگری برای ذخیره تراکنشها ایجاد میکنیم. کدهای زیر را ببینید:
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
کلاس بلاکچین ما مسئول مدیریت زنجیره است و تراکنشها را ذخیره میکند و چند روش کمکی برای افزودن بلاکهای جدید به زنجیره دارد. در ادامه به چند مورد از این متدها (Methods) میپردازیم.
اما یک بلاک چه شکلی است و حاوی چه اطلاعاتی است؟
- شاخص (Index)
- زمان ساخت تقریبی (Timestamp)
- فهرست تراکنشها
- یک گواه (Proof)
- هش تراکنش قبلی (Previous Hash)
مثالی از کد یک بلاک را میتوانید مشاهده کنید:
block = {
'index': 1,
'timestamp': 1506057125.900785,
'transactions': [
{
'sender': "8527147fe1f5426f9dd545de4b27ee00",
'recipient': "a77f5cdfa2934df3954a5c7c7da5df1f",
'amount': 5,
}
],
'proof': 324984774000,
'previous_hash': "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
}
هر بلاک جدید، حاوی هش بلاک قبلی خواهد بود. این موضوع بسیار حیاتی است؛ زیرا این نکته باعث تغییرناپذیری بلاک چین میشود. اگر هکر، بلاک قبلی زنجیره را تغییر دهد یا آلوده کند، تمام بلاکهای بعدی نیز دارای هشهای نادرست خواهند شد.
آیا این موضوع را متوجه شدید؟ اگر متوجه نشدید کمی زمان بگذارید تا کامل آن را متوجه شوید؛ زیرا هسته اصلی ایده بلاک چین است.
مرحله دوم: افزودن تراکنشها به بلاک
ما به روشی برای افزودن تراکنشها به بلاک نیاز داریم. روش مورد نظر ما ()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 تراکنش جدیدی به فهرست افزود، شاخص بلاکی که تراکنش به آن افزوده خواهد شد را بر میگرداند (به خط آخر کد توجه کنید). این نکته بعدا و هنگامی که کاربر تراکنش را ثبت میکند، مفید خواهد بود.
مرحله سوم: ساخت بلاکهای جدید
ساخت بلاکچین را با بلاک جنسیس (Genesis block) یا همان بلاک پیدایش (اولین بلاک ساختهشده در هر شبکه) شروع میکنیم. همچنین باید گواه به بلاک جنسیس اضافه کنیم که حاصل کار استخراج یا ماینینگ است. در این خصوص بیشتر توضیح خواهیم داد.
علاوه بر ایجاد بلاک جنسیس، باید متدهایی هم برای ()new_block و ()new_transaction و ()hash به آن بیفزاییم:
import hashlib
import json
from time import time
class Blockchain(object):
def __init__(self):
self.current_transactions = []
self.chain = []
# Create the genesis block
self.new_block(previous_hash=1, proof=100)
def new_block(self, proof, previous_hash=None):
"""
Create a new Block in the Blockchain
:param proof: <int> The proof given by the Proof of Work algorithm
:param previous_hash: (Optional) <str> Hash of previous Block
:return: <dict> 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: <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
@property
def last_block(self):
return self.chain[-1]
@staticmethod
def hash(block):
"""
Creates a SHA-256 hash of a Block
:param block: <dict> Block
:return: <str>
"""
# 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()
خب تا اینجا که خیلی ساده بود و تقریبا کار معرفی بلاکچین تمام شد. اما برویم سراغ نحوه ایجاد، ساخت یا استخراج بلاکهای جدید در بلاک چین. با مفهوم گواه اثبات کار (PoW) شروع میکنیم.
آشنایی با گواه اثبات کار (PoW)
ایجاد بلاکهای جدید یا اصطلاحا استخراج آنها توسط الگوریتم گواه اثبات کار صورت میگیرد. هدف از این الگوریتم، کشف یک عدد برای حل یک معادله ریاضی است. از منظر محاسباتی، یافتن این عدد سخت اما تایید آن آسان است. اجازه بدهید یک مثال بزنم:
معادله زیر را در نظر بگیرید. عدد یکان هش حاصلضرب x در y باید صفر شود.
Hash(x*y)=ac23dc…0
ما x را معادل ۵ در نظر میگیریم و آن را در پایتون اجرا میکنیم:
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 نیز ۲۱ میشود؛ زیرا هش تولید شده باید به عدد صفر ختم شود:
hash(5 * 21) = 1253e9373e...5e3600155e860
در بیت کوین به الگوریتم گواه اثبات کار هش کش (Hashcash) میگویند و فرق آنچنانی با مثال فوق ندارد؛ هشکش الگوریتمی است که ماینرها برای حل آن و به منظور ایجاد بلاک جدید رقابت میکنند. به طور کلی، سختی با تعداد کاراکترها (اعداد، حروف و نشانهها) جستجو شده در یک رشته از زنجیره تعیین میشود. سپس ماینرها برای جوابی که ارائه میدهند با دریافت کوین، پاداش دریافت میکنند. شبکه بهراحتی میتواند راهکار و جواب آنها را تایید کند.
مرحله چهارم: پیادهسازی گواه اثبات کار اولیه
بیایید الگوریتم مشابهی را برای بلاکچین خود پیادهسازی کنیم. قوانین نیز مشابه با مثال فوق خواهد بود:
عدد 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"
برای تنظیم سختی الگوریتم، میتوانیم تعداد صفرهای آخر را تغییر دهیم. اما ۴ رقم آخر مناسب است. متوجه خواهید شد که افزودن یک صفر به این ۴ رقم، تفاوت چشمگیری در زمان مورد نیاز برای یافتن جواب ایجاد خواهد کرد.
کلاس بلاک چین ما تقریبا تکمیل شده است و آماده تعامل با آن با استفاده از درخواست های HTTP هستیم.
مرحله پنجم: ایجاد API
ما از چارچوب فلسک پایتون استفاده خواهیم کرد. این چارچوب یک میکروفریمورک است و تعیین پایانهها (Endpoints) برای توابع پایتون را آسان میکند. این امر به ما کمک میکند تا با بلاک چین با استفاده از درخواستهای HTTP صحبت کنیم.
سه متد را ایجاد خواهیم کرد:
- transaction/new/: برای ایجاد تراکنش جدید در بلاک
- mine/: برای دستور استخراج بلاک جدید به سرور
- chain/: برای بازگردانی کامل بلاک چین
ایجاد و تنظیم فلسک (Flask)
سرور، یک نود در شبکه بلاکچین ایجاد خواهد کرد. در ابتدا چند کد بویلرپلیت (Boilerplate) ایجاد میکنیم:
import hashlib
import json
from textwrap import dedent
from time import time
from uuid import uuid4
from flask import Flask
class Blockchain(object):
...
# Instantiate our 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():
return "We'll mine a new Block"
@app.route('/transactions/new', methods=['POST'])
def new_transaction():
return "We'll add a new transaction"
@app.route('/chain', methods=['GET'])
def full_chain():
response = {
'chain': blockchain.chain,
'length': len(blockchain.chain),
}
return jsonify(response), 200
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
توضیح مختصری از چیزهایی که اضافه کردهایم:
خط ۱۵: شروع به کار نود یا گره.
خط ۱۸: ایجاد اسم تصادفی برای نود
خط ۲۱: نمونهسازی کلاس بلاکچین
خط ۲۴ الی ۲۶: ایجاد پایانه mine/ برای دریافت درخواست
خط ۲۸ الی ۳۰: ایجاد پایانه transaction/new/ که درخواست را Post میکند؛ زیرا دادهها را به آن ارسال خواهیم کرد.
خط ۳۲ الی ۳۸: ایجاد پایانه chain/ که بلاک چین کامل را بر میگرداند.
خط ۴۰ الی ۴۱: سرور را در پورت ۵۰۰۰ اجرا میکند.
مرحله ششم: ساخت پایانه تراکنش
درخواست تراکنش به این شکل است. نحوه ارسالهای کاربر به سرور این گونه است:
{
"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
مرحله هفتم: ساخت پایانه استخراج
پایانه ماینینگ بسیار ساده است و باید شامل سه مورد باشد:
- محاسبه گواه اثبات کار
- جایزه دادن به ماینر با افزودن تراکنشی که یک کوین اهدا میکند
- ایجاد بلاک جدید با افزودن آن به زنجیره
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
به خاطر داشته باشید که دریافتکننده بلاک استخراج شده، آدرس نود ما بوده و اکثر اقداماتی که انجام میدهیم، فقط تعامل با متدهای موجود در بلاکچین است. در این مرحله کار ما تمام شده است و میتوانیم تعامل با بلاکچین را آغاز کنیم.
مرحله هشتم: تعامل با بلاکچین
میتوانید از طریق cURL یا Postman قدیمی و ساده با API تعامل ایجاد کنید. سرور را راهاندازی کنید:
$ python blockchain.py
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
با ارائه درخواست http://localhost:5000/mine، ماینینگ بلاک را امتحان میکنیم:
با ارائه درخواست POST به http://localhost:5000/transaction/new با بدنه حاوی ساختار تراکنش، تراکنش جدیدی ایجاد میکنیم:
اگر از Postman استفاده نمیکنید، میتوانید معادل این درخواستها را با استفاده از cURL انجام دهید:
$ curl -X POST -H "Content-Type: application/json" -d '{
"sender": "d4ee26eee15148ee92c6cd394edd974e",
"recipient": "someone-other-address",
"amount": 5
}' "http://localhost:5000/transactions/new"
ما سرور خود را ریستارت و دو بلاک استخراج کردیم. برای بررسی زنجیره کامل درخواست http://localhost:5000/chain را اجرا میکنیم.
{
"chain": [
{
"index": 1,
"previous_hash": 1,
"proof": 100,
"timestamp": 1506280650.770839,
"transactions": []
},
{
"index": 2,
"previous_hash": "c099bc...bfb7",
"proof": 35293,
"timestamp": 1506280664.717925,
"transactions": [
{
"amount": 1,
"recipient": "8bbcb347e0634905b0cac7955bae152b",
"sender": "0"
}
]
},
{
"index": 3,
"previous_hash": "eff91a...10f2",
"proof": 35089,
"timestamp": 1506280666.1086972,
"transactions": [
{
"amount": 1,
"recipient": "8bbcb347e0634905b0cac7955bae152b",
"sender": "0"
}
]
}
],
"length": 3
}
مرحله نهم: اجماع در بلاکچین
این مرحله بسیار جالب است. ما بلاکچین اولیهای داریم که تراکنشها را میپذیرد و به ما امکان میدهد تا بلاکهای جدید را استخراج کنیم. اما نکته کلی بلاک چینها این است که باید غیرمتمرکز باشند. اگر بلاکچینها متمرکز باشند، چگونه مطمئن شویم که بیانگر زنجیره یکسانی هستند؟ به این موضوع مشکل اجماع میگوییم و اگر در شبکه خود بیش از یک نود میخواهیم، باید الگوریتم اجماع اجرا کنیم.
ثبت نودهای جدید در فرآیند ساخت بلاکچین
قبل از آن که بتوانیم الگوریتم اجماع پیاده سازی کنیم، به روشی نیاز داریم تا نودها از نودهای مجاور خود در شبکه باخبر شوند. هر نود داخل شبکه ما باید رجیستری یا ثبتی از سایر نودهای شبکه داشته باشد. بنابراین به پایانههای بیشتری نیاز داریم:
- nodes/register/: این پایانه برای پذیرش فهرست نودهای جدید در قالب URLها است.
- nodes/resolve/: این پایانه برای پیادهسازی الگوریتم اجماع است که هر تناقضی را برطرف میکند تا اطمینان حاصل شود که نود مورد نظر دارای زنجیره صحیح است.
ما باید سازنده بلاکچین خود را تغییر داده و اصلاح کنیم و روشی را برای ثبت نودها ارائه دهیم:
...
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)
توجه داشته باشید که برای نگهداری فهرست نودها از ()set استفاده کردهایم. این روش ابتدایی برای اطمینان حاصل کردن از تغییرناپذیری افزودن نودهای جدید است؛ یعنی فرقی ندارد چند بار نود خاصی را اضافه کنیم، مهم این است که تمام این دفعات دقیقا یکبار نشان داده شوند.
پیادهسازی الگوریتم اجماع برای ساخت بلاکچین
همانطور که اشاره شد، وقتی یک نود نسبت به یک نود دیگر زنجیرهای متفاوت داشته باشد تعارض یا اختلاف ایجاد میشود.
برای برطرف کردن این تعارض یا اختلاف، قانونی وضع خواهیم کرد که بلندترین زنجیره معتبر، زنجیره اصلی باشد. با استفاده از این الگوریتم، بین نودهای شبکه به اجماع دست مییابیم.
...
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 مسئول بررسی معتبر بودن زنجیره است و این امر را با بررسی هر بلاک، تایید هش و گواه انجام میدهد. همچنین ()reaolve_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
سپس چند بلاک جدید با نود ۲ استخراج کردیم تا اطمینان حاصل کنیم که زنجیره بزرگتر بوده است. سپس درخواست GET /nodes/resolve را روی نود ۱ اجرا میکنیم:
پرسش و پاسخ (FAQ)
- آیا میتوانم بلاکچین خودم را بسازم؟
بله. میتوانید در زمان کوتاهی با کمک کدهای پایتون بلاکچین بسازید. برای این کار کافی است بلاک و بلاکچین، الگوریتم اجماع و فرآیند ماینینگ را تعریف کنید. - نحوه ساخت بلاکچین با پایتون چگونه است؟
برای ساخت بلاکچین در پایتون باید این گامها را بردارید: ۱- ساخت کلاس بلاک ۲- تعریف بلاکچین ۳-رمزنگاری هر بلاک با استفاده از تابع هش رمزنگاری ۴-افزودن متد گواه اثبات کار ۵- ساخت یک API به منظور تعامل سایر نودها و کاربران با بلاکچین
جمعبندی
در مطلب آموزش ساخت بلاکچین با پایتون، یک زنجیره مبتنی بر گواه اثبات کار را با یکدیگر ساختیم. ابتدا به تبیین مفهوم بلاک و بلاکچین پرداختیم و هر کدام را در برنامه تعریف کردیم. سپس یک سیستم گواه اثبات کار و راهی برای افزودن بلاکهای جدید از طریق استخراج طراحی کردیم. در نهایت برنامهای برای ارتباط سایر نودها با بلاکچین ساختیم. آیا شما تا به حال بلاکچین ساختید؟ دوست دارید در آینده یک شبکه بلاکچینی طراحی کنید؟