Крихітний блокчейн на Python. Частина 2

10 хв. читання

Перша версія крихітного блокчейну була надзвичайно простою, і її було відносно легко створити. Але разом із цією простотою виникло кілька недоліків. SnakeCoin працював тільки на одній машині, тому був далекий від розповсюдження, не кажучи вже про децентралізацію. Блоки додавалися до ланцюга не швидше, ніж хост-комп'ютер міг створити об'єкт у Python і додати його до списку. У випадку з простим блокчейном, це не є проблемою, але ми збираємося зробити SnakeCoin реальною криптовалютою, тому знадобиться контроль кількості блоків (і коінів), які можуть бути створені за раз.

Відтепер дані SnakeCoin будуть транзакціями, тому поле даних кожного блоку буде переліком деяких транзакцій. Кожна транзакція буде JSON об'єктом, із детальним описом відправника коіна, одержувача коіна та кількості SnakeCoin, яка передається.

Примітка: транзакції в форматі JSON з причин, які я коротко опишу нижче.

{
  "from": "71238uqirbfh894-random-public-key-a-alkjdflakjfewn204ij",
  "to": "93j4ivnqiopvh43-random-public-key-b-qjrgvnoeirbnferinfo",
  "amount": 3
}

Тепер, коли ми знаємо, як будуть виглядати наші транзакції, нам потрібен спосіб додавати їх на один із комп'ютерів у нашій блокчейн-мережі, яка називається вузлом. Для цього ми створимо простий HTTP сервер, щоб будь-який користувач міг повідомити нашим вузлам про те, що відбулася нова транзакція. Вузол прийме запит POST із транзакцією у якості тіла запиту. Ось чому транзакції у форматі JSON; нам потрібно, щоб вони були передані в тіло запиту на наш сервер.

pip install flask # Install our web server framework first
from flask import Flask
from flask import request
node = Flask(__name__)

# Збережемо в список транзакції,
# які є в цьому вузлі 
this_nodes_transactions = []

@node.route('/txion', methods=['POST'])
def transaction():
  if request.method == 'POST':
    # У кожному новому запиті POST
    # ми вилучаємо дані транзакції
    new_txion = request.get_json()
    # Далі ми додаємо транзакцію до нашого списку
    this_nodes_transactions.append(new_txion)
    # Оскільки транзакція була успішно надіслана
    # ми заносимо її у нашу консоль
    print "New transaction"
    print "FROM: {}".format(new_txion['from'])
    print "TO: {}".format(new_txion['to'])
    print "AMOUNT: {}\
".format(new_txion['amount'])
    # Тоді ми даємо клієнту зрозуміти, що все спрацювало
    return "Transaction submission successful\
"

node.run()

Отже, у нас тепер є спосіб вести облік користувачів, коли вони відправляють коіни SnakeCoin один одному. Ось чому люди порівнюють блокчейни із публічними, розподіленими гросбухами: усі транзакції зберігаються для всіх учасників процесу, щоб була змога їх бачити, і зберігаються на кожному вузлі мережі.

Але виникає питання: звідки люди отримують коіни SnakeCoin? Поки нізвідки. Ще не існує такої штуки як SnakeCoin, тому що жоден коін ще не був ні створений, ні виданий. Для створення нових коінів люди мають майнити (добувати) блоки SnakeCoin. Коли вони успішно майнять нові блоки, SnakeCoin створюється і дається тій людині, яка майнила цей блок. Як тільки майнер відправляє SnakeCoin іншій людині, коін розповсюджується.

Ми не хочемо, щоб майнити нові блоки SnakeCoin було занадто легко, бо це призведе до надлишку коінів, і вони не будуть мати великої цінності. І навпаки — ми не хочемо, щоб майнити нові блоки було дуже важко, тому що тоді буде недостатньо коінів, і вони будуть занадто дорогими. Для контролю складності майнингу ми реалізуємо алгоритм «доказу виконаної роботи» (Proof-of-Work). По суті, цей алгоритм генерує елемент, який складно створити, але легко перевірити. Елемент називається доказом і сам є доказом того, що комп'ютер виконав певну кількість роботи.

У SnakeCoin ми зробимо просту версію цього алгоритму. Для створення нового блоку комп'ютеру майнера доведеться збільшувати число. Коли це число ділитиметься націло на 9 (кількість літер у слові «SnakeCoin») і буде числом підтвердження останнього блоку, новий блок буде добутий і майнер отримає SnakeCoin.

# ...блокчейн
# ...визначення класу Block

miner_address = "q3nf394hjg-random-miner-address-34nf3i4nflkn3oi"

def proof_of_work(last_proof):
  # Створіть змінну, яку ми будемо використовувати,
  # щоб знайти наступний доказ роботи
  incrementor = last_proof + 1
  # Продовжуйте збільшувати приріст, поки
  # він не буде дорівнювати числу, яке ділиться на 9,
  # і доказу виконаної роботи минулого блоку у ланцюгу
  while not (incrementor % 9 == 0 and incrementor % last_proof == 0):
    incrementor += 1
  # Як тільки це число буде знайдено,
  # ми можемо його повернути як доказ виконаної роботи
  return incrementor

@node.route('/mine', methods = ['GET'])
def mine():
  # Візьміть останній доказ виконаної роботи
  last_block = blockchain[len(blockchain) - 1]
  last_proof = last_block.data['proof-of-work']
  # Знайдіть доказ виконаної роботи
  # для поточного намайненого блоку
  # Примітка: програма залишиться на цьому місці,
  # поки не буде знайдений новий доказ роботи
  proof = proof_of_work(last_proof)
  # Як тільки ми знаходимо дійсний доказ роботи,
  # ми знаємо, що можемо намайнити блок,
  # щоб винагородити майнера, додавши транзакцію
  this_nodes_transactions.append(
    { "from": "network", "to": miner_address, "amount": 1 }
  )
  # Тепер ми можемо зібрати необхідні дані
  # для створення нового блоку
  new_block_data = {
    "proof-of-work": proof,
    "transactions": list(this_nodes_transactions)
  }
  new_block_index = last_block.index + 1
  new_block_timestamp = this_timestamp = date.datetime.now()
  last_block_hash = last_block.hash
  # Пустий список транзакцій
  this_nodes_transactions[:] = []
  # Тепер створимо новий блок
  mined_block = Block(
    new_block_index,
    new_block_timestamp,
    new_block_data,
    last_block_hash
  )
  blockchain.append(mined_block)
  # Повідомимо клієнта про те, що ми намайнили блок
  return json.dumps({
      "index": new_block_index,
      "timestamp": str(new_block_timestamp),
      "data": new_block_data,
      "hash": last_block_hash
  }) + "\
"

Тепер ми контролюємо кількість видобутих блоків у визначений період часу, і можемо видавати нові коіни людям у мережі. Але, як вже було сказано, ми робимо це тільки на одному комп'ютері. Якщо блокчейни децентралізовані, як ми переконаємося в тому, що у кожному вузлі знаходиться один і той самий ланцюг? Для цього ми змусимо кожен вузол передавати свою версію ланцюга іншим вузлам і дозволимо їм отримувати ланцюги з інших вузлів. Після цього кожен вузол повинен буде підтвердити ланцюги інших вузлів так, щоб кожен вузол у мережі досяг консенсусу відносно того, як виглядатиме результівний блокчейн. Це називається алгоритмом консенсусу.

Наш алгоритм консенсусу буде досить простим: якщо один ланцюг вузла відрізняється від іншого, тоді найдовший ланцюг у мережі залишається, а усі коротші – видаляються. Якщо між ланцюгами у нашій мережі не буде конфлікту, тоді продовжуємо.

@node.route('/blocks', methods=['GET'])
def get_blocks():
  chain_to_send = blockchain
  # Конвертуйте наші блоки у словники,
  # щоб ми могли надіслати їх пізніше як об'єкти json
  for block in chain_to_send:
    block_index = str(block.index)
    block_timestamp = str(block.timestamp)
    block_data = str(block.data)
    block_hash = block.hash
    block = {
      "index": block_index,
      "timestamp": block_timestamp,
      "data": block_data,
      "hash": block_hash
    }
  # Відправте наш ланцюг будь-кому, хто про це попросить
  chain_to_send = json.dumps(chain_to_send)
  return chain_to_send

def find_new_chains():
  # Отримайте блокчейни з всіх інших вузлів
  other_chains = []
  for node_url in peer_nodes:
    # Отримайте їх ланцюги, використовуючи запит GET
    block = requests.get(node_url + "/blocks").content
    # Конвертуйте об'єкт JSON у словник Python
    block = json.loads(block)
    # Додайте його до нашого списку
    other_chains.append(block)
  return other_chains

def consensus():
  # Отримайте блоки із інших вузлів
  other_chains = find_new_chains()
  # Якщо наш ланцюг не найдовший,
  # тоді ми збережемо найдовший ланцюг
  longest_chain = blockchain
  for chain in other_chains:
    if len(longest_chain) < len(chain):
      longest_chain = chain
  # Якщо найдовший ланцюг був не нашим,
  # тоді ми встановимо наш ланцюг як найдовший
  blockchain = longest_chain

Ми майже закінчили. Після запуску повного коду сервера SnakeCoin, запустіть наступні команди у вашому терміналі. Звичайно, це при умові, що у вас встановлений cURL.

  • Створіть транзакцію.
curl "localhost:5000/txion" \\
     -H "Content-Type: application/json" \\
     -d '{"from": "akjflw", "to":"fjlakdj", "amount": 3}'
  • Намайніть новий блок.
curl localhost:5000/mine
  • Перевірте результати. З вікна клієнта ми бачимо це:

Крихітний блокчейн на Python. Частина 2

За допомогою структурного друку (Pretty Printing) видно, що після майнінгу ми отримуємо деяку цікаву інформацію про наш новий блок.

{
  "index": 2,
  "data": {
    "transactions": [
      {
        "to": "fjlakdj",
        "amount": 3,
        "from": "akjflw"
      },
      {
        "to": "q3nf394hjg-random-miner-address-34nf3i4nflkn3oi",
        "amount": 1,
        "from": "network"
      }
    ],
    "proof-of-work": 36
  },
  "hash": "151edd3ef6af2e7eb8272245cb8ea91b4ecfc3e60af22d8518ef0bba8b4a6b18",
  "timestamp": "2017-07-23 11:23:10.140996"
}

Ось і все! На цій стадії ми створили досить великий блокчейн. Тепер SnakeCoin може бути запущений на декількох машинах для створення мережі, і реальні коіни SnakeCoin можна майнити. У наступній частині ми обговоримо створення гаманця SnakeCoin, щоб користувачі могли надсилати, отримувати та зберігати свої коіни SnakeCoin.

Помітили помилку? Повідомте автору, для цього достатньо виділити текст з помилкою та натиснути Ctrl+Enter
Codeguida 5.6K
Приєднався: 8 місяців тому
Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.

Вхід / Реєстрація