簡単なブロックチェーンをつくる(その7 マイニング)

 ジェネシスブロック以外のすべてのブロックもマイニングによって生成する.どのブロックにもトランザクションリストの先頭にコインベーストランザクションをつくる.コインの送り先はとりあえずジェネシスブロックと同様にSatoとするが,あとで複数ノードにしたときにノード所有者に置き換える.

 block 関数を mine 関数に置き換え,/mine によりマイニングを行いブロックを生成する.

import hashlib, json

from time import time
from flask import Flask, jsonify, request

app = Flask(__name__)

class Blockchain():

    # ハッシュ値の上4文字が0000となるノンスを見つける
    difficulty = 4
    target = '0000'

    def __init__(self):
        # ブロックチェーン本体
        self.blockchain = []
        # 1ブロックのトランザクションリスト
        self.tx_list = []
        # ジェネシスブロックの"前ブロック"のハッシュ値
        self.prev_block_hash = hashlib.sha256("genesis".encode()).hexdigest()
        # ジェネシスブロック
        block = {
            'index': 0,
            'timestamp': time(),
            # コインベーストランザクション: 'Sato'に1000コインを送付
            'tx_list': [{'sender': '0', 'recipent': 'Sato', 'amount': 1000}],
            'nonce': 0,
            'prev_block_hash': self.prev_block_hash
        }
        # ジェネシスブロックのノンスを計算
        block['nonce'] = self.proof_of_work(block)
        # ジェネシスブロックをブロックチェーンに追加
        self.blockchain.append(block)

    # プルーフオブワーク
    # ターゲットの条件を満たすハッシュ値を与えるノンスを見つける
    def proof_of_work(self, block):
        while True:
            hash = hashlib.sha256(json.dumps(block, sort_keys=True).encode()).hexdigest()
            if hash[:self.difficulty] == self.target:
                # 見つけたブロックのハッシュ値(次のブロックに必要)
                self.prev_block_hash = hash
                break
            block['nonce'] += 1

        return block['nonce']


# ブロックチェーンインスタンス
bc = Blockchain()

# マイニングによるブロック生成
@app.route("/mine", methods=["GET"])
def mine():
    # マイナー報酬のコインベーストランザクションをトランザクションリスト
    # の先頭に追加
    # 受領者はとりあえず'Sato'にする
    tx = {
        'sender': '0',
        'recipient': 'Sato',
        'amount': 1000
    }
    bc.tx_list.insert(0, tx)

    # マイニング
    block = {
        'index': len(bc.blockchain),
        'tiemstamp': time(),
        'tx_list': bc.tx_list,
        'nonce': 0,
        'prev_block_hash': bc.prev_block_hash
    }
    block['nonce'] = bc.proof_of_work(block)

    # 次のブロック用に現在のブロックのハッシュを計算しておく
    bc.prev_block_hash = hashlib.sha256(json.dumps(block, sort_keys=True).encode()).hexdigest()

    # トランザクションリストをブロックに入れたのでクリア
    bc.tx_list = []

    # ブロックチェーンにブロックを追加
    bc.blockchain.append(block)

    return jsonify(bc.blockchain), 200

# トランザクション生成
@app.route("/tx", methods=['POST'])
def tx():
    tx = request.get_json()

    fields = ['sender', 'recipient', 'amount']
    if (not all(k in tx for k in fields)) or (tx['amount'] <= 0):
        message = 'Input Error'
        status_code = 400
    else:
        bc.tx_list.append(tx)
        message ='Tx added'
        status_code = 201
        print(bc.tx_list)

    responce = {'message': message}
    return jsonify(responce), status_code

if __name__ == "__main__":
    app.run()

 例として,まずトランザクションを1回行い,その後マイニングを行う.

> curl -X POST -H "Content-Type: application/json" -d "{\"sender\": \"Sato\", \"recipient\": \"Yamada\", \"amount\": 123}" "http://localhost:5000/tx" | jq

> curl http://localhost:5000/mine | jq

結果

[
  {
    "index": 0,
    "nonce": 55681,
    "prev_block_hash": "aeebad4a796fcc2e15dc4c6061b45ed9b373f26adfc798ca7d2d8cc58182718e",
    "timestamp": 1624885111.6655188,
    "tx_list": [
      {
        "amount": 1000,
        "recipent": "Sato",
        "sender": "0"
      }
    ]
  },
  {
    "index": 1,
    "nonce": 2540,
    "prev_block_hash": "00004d7d1c7bf13b448695f131acbacb8d171204ece78da93846b9c70f4d5fd1",
    "tiemstamp": 1624885122.4209077,
    "tx_list": [
      {
        "amount": 1000,
        "recipient": "Sato",
        "sender": "0"
      },
      {
        "amount": 123,
        "recipient": "Yamada",
        "sender": "Sato"
      }
    ]
  }
]