Python creates a simple blockchain

Blockchain is a distributed ledger, which is an append-only, immutable ordered chain data structure. The data structure It is jointly maintained by a series of nodes in the network, and these nodes do not trust each other. Blockchain is the underlying technology behind the popular digital currencies such as Bitcoin and Ethereum . This article uses Python to create a simple blockchain model from 0, which is very helpful for understanding the basic principles of blockchain.

Prepare

Requirements: python3.6, pip, flask, requests, cURL

flask and requests can be downloaded with the following commands:

pip install falsk, requests

Create the file blockchain.py , all the code is written in this file.

Start

The code is mainly divided into two parts, the first is a Blockchain class, which contains the attributes and characteristic methods of the blockchain; the second is the corresponding routing processing method, which uses the flask server as a node in the blockchain network to process client request pairs. The blockchain completes the corresponding operations and interacts with other nodes in the network.

related data structures
  • block : block, represented by dict , including attribute index (index), timestamp (timestamp), transaction set (transactions), proof of work (proof), hash value of the previous block (previous_hash). Among them, previous_hash is used to link these ordered blocks and ensure that the content in them is not changed.

  • tansaction : transaction, dict form, stored in the block, including attributes: sender (sender), receiver (recipient), amount (amount)

Properties of the Blockchain class
  • chain : a list collection of all blocks in the blockchain , each element is a block

  • current_transactions : The current set of transactions that will be added to the block, list indicates

  • nodes : set collection of other adjacent nodes of the current node

Methods of the Blockchain class
  • new_block() : Generate a new block, receive proof of workload as a parameter, add the block to the chain, clear the current transaction set, and return the generated block. __init__A genesis block is automatically generated in the method, the initial proof is 100, and the previous_hash is '1'

  • new_transaction() : Generate a new transaction, receive three parameters: sender, receiver, amount, add the transaction to the current transaction set, and return the index of the block to which the transaction will be added

  • hash() : Generate the hash value of the block, receive a block as a parameter, first json.dumps()convert the block object into json format, and then hashlib.sha256()calculate its hash value using

  • proof_of_work() : The workload proof algorithm (PoW), finds a number p by looping, so that the first 4 digits of the hash value of the string concatenated with the proof of the previous block are '0000', and the previous block's hash value is received. proof as a parameter, return the found proof that meets the requirements

  • register_node() : Register a node, receive a URL as a parameter, urllib.parse.urlparse()parse the address, get the content ip:port, and add it to the adjacent node set nodes

  • resolve_confict() : The consensus algorithm (consensus) resolves conflicts, i.e. using the longest and valid chain in the network. Traverse the collection of other nodes nodes(the ip and port of these nodes are saved in the collection), and use the chain obtained requests.get()by routing /chainto these nodes chain. Then make a length judgment first , if the chain length of a node is greater than the chain length of the current node; then make an effective judgment on the chain , that is, traverse the chain to judge whether the previous_hash and proof values ​​of each block meet the requirements. If the chain is longer and valid, replace its own chain with this chain and resolve the conflict.

URL handler

The data submitted by the user and the response data of the server are in json format. The server uses Flask.request.get_json()the json data in the form to obtain the json data and Flask.jsonify()converts the response data into json format and returns it.

  • mine() : Mining, the corresponding route is /mine, and the request method is GET. The main tasks to be completed are: calculating the proof of work, rewarding miners (own nodes) with 1 coin through new transactions, adding a new block and adding it to the chain.

  • new_transaction() : Add a new transaction, the corresponding route is /transactions/new, and the request method is POST. By request.get_json()obtaining the json format form submitted by the user, it is judged whether the transaction meets the requirements, and the method of adding transaction in the class is called.

  • full_chain() : View the entire chain, the corresponding route is /chain, and the request method is GET.

  • register_nodes() : Register nodes, the corresponding route is /node/register, and the method is POST. Receive the submitted node set, call register_node()to join these nodes, and return the nodes set

  • consensus() : Consensus, the corresponding route is /node/resolve, and the method is GET. Call resolve_conflict()to resolve conflicts and return the chain after consensus.

all code
import hashlib
import json
from time import time
from uuid import uuid4
from flask import Flask, jsonify, request
from urllib.parse import urlparse
import requests
import sys


class Blockchain(object):
    def __init__(self):
        # 当前即将加入区块的交易集合
        self.current_transactions = []
        self.chain = []
        self.nodes = set()
        # create the genesis block
        self.new_block(proof=100, previous_hash='1')

    def new_block(self, proof, previous_hash=None):
        """
        生成新块
        :param proof: <int> The proof given by the PoW 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,        # a list
            '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> 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 the last block in the chain
        return self.chain[-1]

    @staticmethod
    def hash(block):
        """
        生成区块的 SHA-256 hash值
        :param block: <dict> Block
        :return: <str> the hash value
        """
        # we must make sure that the dict 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_proof):
        """
        工作量证明:
         - 查找一个 p' 使得 hash(pp')以4个0开头
         - 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)
        :param proof: <int> previous proof
        :param last_proof: <int> current proof
        :return: <bool> True if correct, Flase if not.
        """
        guess_hash = hashlib.sha256((str(last_proof) + str(proof)).encode()).hexdigest()
        return guess_hash[:4] == '0000'

    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
        """
        node = urlparse(address).netloc
        self.nodes.add(node)

    def valid_chain(self, chain):
        """
        Determine if a blockchain is valid
        :param chain: <list> a blockchain
        :return: <bool> True if valid, False if not
        """
        for i in range(1, len(chain)):
            block = chain[i]
            previous_block = chain[i-1]
            if self.hash(previous_block) != block['previous_hash']:
                return False
            if not self.valid_proof(previous_block['proof'], block['proof']):
                return False
        return True

    def resolve_conflict(self):
        """
        共识算法解决冲突,使用网络中最长且有效的链
        :param chain: <list> other blockchain
        :return: <bool> True 链被取代,False 链未被取代
        """
        flag = False
        for node in self.nodes:
            r = requests.get('http://{}/chain'.format(node))
            if r.status_code == 200:
                chain = r.json()['chain']
                length = r.json()['length']
                if length > len(self.chain) and self.valid_chain(chain):
                    self.chain = chain
                    flag = True
        return flag


# 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():
    # We run the PoW algorithm to get the next proof...
    last_proof = blockchain.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': '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():
    # json.loads(request.get_data())
    values = request.get_json()
    # Check that the required fields are in 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': 'Transaction will be added to Block {}'.format(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('/node/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('/node/resolve', methods=['GET'])
def consensus():
    is_replaced = blockchain.resolve_conflict()
    if is_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__':
    myport = 5000
    if len(sys.argv) > 1:
        myport = int(sys.argv[1])
    app.run(host='0.0.0.0', port=myport)

test

  • Open multiple terminals on one machine to run the source code separately, and simulate a multi-node network by listening to multiple different ports. This simulates a blockchain network with two nodes.

    python3 blockchain.py 5000         #在终端1运行
    python3 blockchain.py 5001         #在终端2运行
  • Mining: Create another terminal 3 to operate through the curl command. A mining operation is performed on node 1, and there are two blocks in the chain at this time.

    curl http://127.0.0.1:5000/mine

    write picture description here

  • Send Transaction: Send a transaction to Node 1.

    curl -X POST -H "Content-Type: application/json" -d '{"sender": "5000", "recipient": "5001", "amount": 100}' "http://127.0.0.1:5000/transactions/new"

    write picture description here

  • View the blockchain: first perform a mine operation to make the transaction just sent into the third block, and then view the data information of the entire blockchain. At this time, the chain has 3 blocks, of which the third block contains Two transactions.

    curl http://127.0.0.1:5000/mine
    curl http://127.0.0.1:5000/chain

    write picture description here

  • Registering a node: Send the address of node 1 (port 5000, 3 blocks) to node 2 (port 5001, 1 block).

    curl -X POST -H "Content-Type: application/json" -d '{"nodes": ["http://127.0.0.1:5000"]}' "http://127.0.0.1:5001/node/register"

    write picture description here

  • Consensus: Make node 2 complete consensus with neighboring nodes, and replace node 2's chain (length 1) with node 1's chain (length 3).

    curl http://127.0.0.1:5001/node/resolve

    write picture description here

Reference article: https://learnblockchain.cn/2017/10/27/build_blockchain_by_python/

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325935555&siteId=291194637