python实现简易区块链

本文主要参考用Python从零开始创建区块链 | 登链社区 | 区块链技术社区 (learnblockchain.cn)
区块链作为一种新型去中心化分布式数据系统,确保了数字加密货币交易等数据的安全存储。其不可篡改、公开透明、匿名的特性降低了互联网信任成本,助力实现从信息互联网向价值互联网的跨越。基于区块链技术,我开发的山寨比特币交易系统提供了一种安全且可验证的交易方式。在系统中,各方共享信息并达成具有预定义规则的协议。本文对山寨比特币交易系统主要工作内容如下:
山寨比特币交易系统以Python语言为基础,构建了一个分布式数字货币交易平台。该系统主要包括区块链、节点、交易、工作量证明和共识算法等核心模块。区块链模块以时间顺序打包交易记录,形成链式结构,确保数据安全和不间断;节点模块维护区块链同步和扩展,通过分布式共识算法确保数据一致性;交易模块支持数字货币发送、接收和交易,保障交易安全性和隐私;工作量证明模块模拟比特币工作量证明算法,确保交易安全性和不可篡改;共识算法模块在多个节点间达成共识,实现交易快速高效。
山寨比特币交易系统为用户提供安全、可靠、可验证的数字货币交易方式,有望在数字经济领域发挥重要作用。
  • 原理阐述

在本项目中,我通过定义一个`Blockchain`类来模拟区块链。在类的构造方法中,我将调用已封装的创建区块方法`new_block()`,生成一个创世区块。同时,我设置该区块的前一个哈希值为1,以表明其为创世区块。此外,我使用列表来保存当前交易和链,并采用集合(set)来存储分布式的节点(node)。

为了模拟分布式,我将注册节点的方法封装到register_node()方法中;为了实现共识算法,我将“检查一条链是否有效”的方法和“找出最长连”的方法分别封装为valid_chain() 和 resolve_conflicts()。

然后创建区块和创建新的交易分别对应new_block() 方法和 new_transaction()方法。

模拟工作量证明时,我将会计算前一个区块的hash值,这部分内容我封装在了 proof_of_work() 方法中。

鉴于我的项目采用Web架构,并需运用Flask框架,在处理请求方法时,调用`Blockchain`类中的方法以模拟交易。

  • 需求分析

    本项目是一个基于Python的山寨比特币交易系统,实现了区块链中的部分基本算法,如共识算法等,并具备创建交易、打包区块、工作量证明等核心功能。通过模仿区块链的分布式特性,实现了高性能和安全性。该项目采用Web架构,并以Python的Flask框架为基础。通过定义一个模拟区块链操作的类,实现了与Flask的交互。在测试过程中,我们使用了Postman这款API测试工具。

  • 系统实现

1 功能设计

1.1 初始化区块链

在Blockchain的构造函数中,初始化当前交易,当前链,以及当前节点,同时创建一个创世区块,设置previous_hash 的值为1表示当前区块为创世区块。

  1. # 定义一个区块链类
  2. class Blockchain:
  3.     def __init__(self): # 初始化当前交易、区块链、节点和创世区块
  4.         self.current_transactions = []  # 当前交易
  5.         self.chain = [] # 一条链
  6.         self.nodes = set()  # 用set来存储node节点
  7.         self.new_block(previous_hash='1', proof=100)    # 创建一个创世区块

1.2 注册节点

为的是模拟比特币的分布式交易系统,系统中可能总是存在多个节点,这些节点都知道对方的存在。在这里我们通过将Web服务启动在不同的端口,来模拟分布式交易系统中的多个不同节点。

让一个节点知道其他节点的存在,这里我们使用的方法是将另一个节点注册到当前节点中来,即将另一个节点的信息存放在当前节点Blockchain类中的 nodes 属性中。

我们这里模拟注册节点的方法是register_node() 函数,我们需要传入另一个节点的url地址,然后解析得到服务器路径,再存入nodes集合中。

  1.     def register_node(self, address):   # 注册网络中的节点
  2.         parsed_url = urlparse(address)  # 将一个url的字符串解析并返回一个url的对象
  3.         if parsed_url.netloc:   # 如果地址包含域名
  4.             self.nodes.add(parsed_url.netloc)   # 添加域名
  5.         elif parsed_url.path:   # 如果地址包含路径
  6.             self.nodes.add(parsed_url.path)  # 添加路径
  7.         else:
  8.             raise ValueError('Invalid URL')  # 如果地址无效,抛出异常

1.3 创建交易

有了一个创世区块之后,我们就可以在这个创世区块中进行交易了,我们使用new_transaction() 函数来模拟一次交易,传入的参数:sender表示发送者的地址,recipient表示接受者的地址,amount表示数量,该函数的返回值是本次交易的index索引,这些信息最终都会被打包添加到下一个区块中。

  1.     def new_transaction(self, sender, recipient, amount):  # 创建一笔新的交易
  2.         # 创建一个新的交易到下一个区块中
  3.         self.current_transactions.append({
  4.             'sender': sender,
  5.             'recipient': recipient,
  6.             'amount': amount,
  7.         })
  8.         return self.last_block['index'] + 1  # 返回下一个区块的索引

1.4 工作量证明

我们知道,区块链中的每一个新的区块都来自于工作量证明,工作量证明的目标是计算出一串解决问题的数字,这个结果是十分难计算的,但是却十分容易验证,网络上的任何人都可以验证这个结果。

这里我们也用Python代码实现一个简单的工作量证明算法,我们的规则是:找到一个数字P,使得它与前一个区块的proof拼接而成的字符串的Hash值以4个零开头。

  1.  # 工作量证明
  2.     def proof_of_work(self, last_block):
  3.         # 简单工作量证明算法
  4.         # (1) 找出一个数字 P',使得 hash(PP') 包含前导的4个0
  5.         # (2) 其中 P 是前一个证明, P' 是新的证明
  6.         last_proof = last_block['proof']
  7.         last_hash = self.hash(last_block)
  8.         proof = 0
  9.         while self.valid_proof(last_proof, proof, last_hash) is False:
  10.             proof += 1
  11.         return proof
  12.     # 验证工作量
  13.     @staticmethod
  14.     def valid_proof(last_proof, proof, last_hash):
  15.         #          验证数据
  16.         #          :param last_proof:  <int> 前一个证明
  17.         #          :param proof:   <int>   当前证明
  18.         #          :param last_hash:   前一个区块的哈希值
  19.         #          :return:    <bool>  如果正确,返回 True;否则返回 False
  20.         guess = f'{last_proof}{proof}{last_hash}'.encode()
  21.         guess_hash = hashlib.sha256(guess).hexdigest()
  22.         return guess_hash[:4] == "0000"

1.5 打包生成一个新的区块

这个过程我们需要模拟“挖矿”,通过Flask服务端,我们先运行一个工作量证明算法,得到下一个证明。

找到一个工作量证明之后,我们将给我们的“矿工”一定数量的“仿比特币”作为奖励,然后将当前所有的交易打包并生成一个新的区块,同时将存储当前交易的变量 current_transaction 重置,方便进行一下次交易。

这里我们将分别展示Flask服务端的代码以及Blockchain类中创建区块的代码。

ØFlask服务端的代码

  1.  # 挖掘区块
  2. @app.route('/mine', methods=['GET'])
  3. def mine():
  4.     # 我们运行这个工作证明算法,得到下一个证明
  5.     last_block = blockchain.last_block
  6.     proof = blockchain.proof_of_work(last_block)
  7.     # 找到一个工作量证明之后,我们必须得到一个奖励
  8.     blockchain.new_transaction(
  9.         sender="0",
  10.         recipient=node_identifier,
  11.         amount=1,
  12.     )
  13.     # 添加到链中,生成一个新的区块
  14.     previous_hash = blockchain.hash(last_block)
  15.     block = blockchain.new_block(proof, previous_hash)
  16.     response = {
  17.         'message'"New Block Forged",
  18.         'index': block['index'],
  19.         'transactions': block['transactions'],
  20.         'proof': block['proof'],
  21.         'previous_hash': block['previous_hash'],
  22.     }
  23.     return jsonify(response), 200

ØBlockchain类中的代码

  1.     def new_block(self, proof, previous_hash):  # 创建一个新的区块
  2.         # 在区块链中创建一个新的区块
  3.         block = {
  4.             'index': len(self.chain) + 1,  # 区块索引
  5.             'timestamp': time(),  # 时间戳
  6.             'transactions'self.current_transactions,  # 交易列表
  7.             'proof': proof,  # 工作量证明
  8.             # 建立创世区块的时候,前一个区块的哈希值没有,所以这里我们需要从外部传入一个指定的 hash 值
  9.             'previous_hash': previous_hash or self.hash(self.chain[-1]),  # 前一个区块的哈希
  10.         }
  11.         self.current_transactions = []  # 重置当前交易列表
  12.         self.chain.append(block)    # 将区块添加到链中
  13.         return block

1.6 共识算法

由于我们的仿比特币交易系统是分布式的,所以我们必须保证所有的节点都运行在同一条链上,当一个节点与另一个节点有不同时就会存在冲突,为了解决这个问题,我们遵循最长链原则,使用共识算法,让网络中的节点达成共识。

共识算法的第一部分,我们需要检查一条链是否是有效链,

  1.     def valid_chain(self, chain):   # 共识算法,检查一条链是否有效
  2.         last_block = chain[0]   # 选取链中的第一个区块
  3.         current_index = 1   # 当前索引初始化为1
  4.         while current_index < len(chain):   # 遍历区块链直到最后一个区块
  5.             block = chain[current_index]    # 获取当前索引的区块
  6.             print(f'{last_block}')  # 打印上一个区块
  7.             print(f'{block}')   # 打印当前区块
  8.             print("\n-----------\n")
  9.             # 检查哈希值和工作量证明
  10.             last_block_hash = self.hash(last_block) # 获取上一个区块的哈希值
  11.             if block['previous_hash'] != last_block_hash:   # 检查当前区块的前一个哈希是否匹配
  12.                 return False
  13.             # 检查工作量证明是否正确
  14.             if not self.valid_proof(last_block['proof'], block['proof'], last_block_hash):
  15.                 return False
  16.             last_block = block  # 更新上一个区块为当前区块
  17.             current_index += 1  # 索引加1,移动到下一个区块
  18.         return True
  19.     # 共识算法,找出一个最长链
  20.     def resolve_conflicts(self):
  21.         # 共识算法,负责循环读取所有相邻节点,获取它们的链并使用上面的方法验证它们的有效性。如果找到了一个更长的有效链,则取代我们当前的链。
  22.         neighbours = self.nodes  # 获取网络中的所有节点
  23.         new_chain = None  # 初始化新区块链变量
  24.         max_length = len(self.chain)  # 初始化最长链长度为当前链长度
  25.         for node in neighbours:  # 遍历所有节点
  26.             response = requests.get(f'http://{node}/chain')  # 向节点请求链信息
  27.             if response.status_code == 200:  # 如果请求成功
  28.                 length = response.json()['length']  # 获取链的长度
  29.                 chain = response.json()['chain']  # 获取链的数据
  30.                 if length > max_length and self.valid_chain(chain):  # 如果找到更长的有效链
  31.                     max_length = length  # 更新最长链长度
  32.                     new_chain = chain  # 更新新区块链
  33.         if new_chain:  # 如果找到了新的区块链
  34.             self.chain = new_chain  # 替换当前链
  35.             return True  # 返回True表示链被替换了
  36.         return False  # 如果没有找到新的链,返回False

2 流程设计

2.1 环境准备

确保已经安装 Python3.9pipFlaskrequests等相关环境。同时还需要一个 HTTP 客户端Postman

2.2 初始化区块链

我们需要构造一个创世块(没有前区块的第一个区块),之后需要给它加上一个工作量证明。每个区块都需要经过工作量证明,俗称挖矿

  1. class Blockchain(object):

2.3 注册节点

使用register_node() 函数创建一个节点,作为我们连入区块链的服务器

  1. def register_node(self, address):

2.4 创建交易,并用区块分批保存交易

向列表中添加一个交易记录,并返回该记录将被添加到的区块(下一个待挖掘的区块)的索引,等下在用户提交交易时会有用。

2.5 将区块加入区块链

通过逐个添加方式,将区块一个个链接起来,返回整条链

2.6 实现工作量证明算法

通过新增一个交易授予矿工(自己)一个币

构造新区块并将其添加到链中

2.7 设立共识机制

当一个节点与另一个节点有不同的链时,就会产生冲突。 为了解决这个问题,我们将制定最长的有效链条是最权威的规则。换句话说就是:在这个网络里最长的链就是最权威的。 我们将使用这个算法,在网络中的节点之间达成共识。

2.8 通过端口调用服务

2.9 运行多个节点,进行测试

3 编程代码

关于Blockchain类的一些实现具体功能的函数,在4.1小结已经基本展示完毕,这里展示一些Flask服务端的代码。

Ø实例化Flask结点,Blockchain对象

  1. app = Flask(__name__)
  2. blockchain = Blockchain()

Ø创建一个新的交易

  1. # 创建一个新的交易
  2. @app.route('/transactions/new', methods=['POST'])
  3. def new_transaction():
  4.     values = request.get_json()
  5.     # 检查post数据
  6.     required = ['sender''recipient''amount']
  7.     if not all(k in values for k in required):
  8.         return '缺少必要字段'400
  9.     # 创建一个新的交易
  10.     index = blockchain.new_transaction(values['sender'], values['recipient'], values['amount'])
  11.     response = {'message': f'Transaction will be added to Block {index}'}
  12.     return jsonify(response), 201

Ø返回整条链

  1. # 返回整条链
  2. @app.route('/chain', methods=['GET'])
  3. def full_chain():
  4.     response = {
  5.         'chain': blockchain.chain,
  6.         'length': len(blockchain.chain),
  7.     }
  8.     return jsonify(response), 200

Ø添加相邻节点

  1. # 添加相邻节点
  2. @app.route('/nodes/register', methods=['POST'])
  3. def register_nodes():
  4.     values = request.get_json()
  5.     nodes = values.get('nodes')
  6.     if nodes is None:
  7.         return "错误!请提供一个有效的节点列表"400
  8.     for node in nodes:
  9.         blockchain.register_node(node)
  10.     response = {
  11.         'message''新的节点已经被创建',
  12.         'total_nodes': list(blockchain.nodes),
  13.     }
  14.     return jsonify(response), 201

Ø共识机制

  1. # 共识机制
  2. @app.route('/nodes/resolve', methods=['GET'])
  3. def consensus():
  4.     replaced = blockchain.resolve_conflicts()
  5.     # app.run(host='10.210.5.208', port=port)
  6.     if replaced:
  7.         response = {
  8.             'message''我们的链被替换了',
  9.             'new_chain': blockchain.chain
  10.         }
  11.     else:
  12.         response = {
  13.             'message''我们的链是权威的',
  14.             'chain': blockchain.chain
  15.         }
  16.     return jsonify(response), 200

Ø启动服务,指定端口

  1. if __name__ == '__main__':
  2.     from argparse import ArgumentParser
  3.     parser = ArgumentParser()
  4.     parser.add_argument('-p''--port'default=5000type=int)
  5.     args = parser.parse_args()
  6.     port = args.port
  7.     app.run(host='0.0.0.0',port=port)

4.界面实现

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>区块链演示</title>
    <style>
        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            background-color: #f4f4f9;
            color: #333;
            margin: 0;
            padding: 0;
        }
        header {
            background-color: #8A2BE2;
            color: #fff;
            padding: 20px 0;
            text-align: center;
            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
        }
        .container {
            width: 80%;
            margin: 20px auto;
            overflow: hidden;
        }
        .content {
            display: flex;
            flex-wrap: wrap;
            justify-content: space-between;
        }
        .card {
            background: #fff;
            margin: 10px;
            padding: 20px;
            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
            border-radius: 8px;
            flex: 1 1 calc(48% - 40px);
            box-sizing: border-box;
            transition: transform 0.3s ease;
        }
        .card:hover {
            transform: translateY(-5px);
        }
        .card h3 {
            margin-top: 0;
            color: #8A2BE2;
        }
        .card form {
            display: flex;
            flex-direction: column;
        }
        .card form input, .card form button {
            padding: 10px;
            margin: 5px 0;
            font-size: 16px;
            border: 1px solid #ddd;
            border-radius: 4px;
        }
        .card form button {
            background-color: #8A2BE2;
            color: #fff;
            border: none;
            cursor: pointer;
            transition: background-color 0.3s ease;
        }
        .card form button:hover {
            background-color: #7a22c4;
        }
        .chain, .transactions, .node-chain {
            margin: 20px 0;
        }
        .block, .transaction {
            background: #fff;
            margin: 10px 0;
            padding: 10px;
            border-left: 5px solid #8A2BE2;
            border-radius: 4px;
            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
        }
        .block p, .transaction p {
            margin: 5px 0;
        }
        h2 {
            color: #8A2BE2;
        }
        input[type="text"], input[type="number"] {
            width: calc(100% - 22px);
        }
        button {
            width: calc(100% - 22px);
        }
    </style>
</head>
<body>
    <header>
        <h1>区块链演示</h1>
    </header>
    <div class="container">
        <div class="content">
            <div class="card">
                <h3>在5000端口挖掘新的区块</h3>
                <button onclick="mineBlock('5000')">挖掘</button>
            </div>
            <div class="card">
                <h3>在5000端口创建新的交易</h3>
                <form id="transactionForm5000">
                    <input type="text" id="sender5000" placeholder="发送方">
                    <input type="text" id="recipient5000" placeholder="接收方">
                    <input type="number" id="amount5000" placeholder="金额">
                    <button type="button" onclick="createTransaction('5000')">创建交易</button>
                </form>
            </div>
            <div class="card">
                <h3>在其他端口挖掘新的区块</h3>
                <input type="text" id="portMine" placeholder="端口号">
                <button onclick="mineBlock(document.getElementById('portMine').value)">挖掘</button>
            </div>
            <div class="card">
                <h3>在其他端口创建新的交易</h3>
                <form id="transactionFormOther">
                    <input type="text" id="portTransaction" placeholder="端口号">
                    <input type="text" id="senderOther" placeholder="发送方">
                    <input type="text" id="recipientOther" placeholder="接收方">
                    <input type="number" id="amountOther" placeholder="金额">
                    <button type="button" onclick="createTransaction(document.getElementById('portTransaction').value)">创建交易</button>
                </form>
            </div>
            <div class="card">
                <h3>查看5000端口交易记录</h3>
                <button onclick="fetchTransactions('5000')">查看</button>
            </div>
            <div class="card">
                <h3>查看其他端口交易记录</h3>
                <input type="text" id="portTransactions" placeholder="端口号">
                <button onclick="fetchTransactions(document.getElementById('portTransactions').value)">查看</button>
            </div>
            <div class="card">
                <h3>查看节点区块链</h3>
                <form id="nodeChainForm">
                    <input type="text" id="nodeChain" placeholder="节点URL">
                    <button type="button" onclick="fetchNodeChain()">查看</button>
                </form>
            </div>
        </div>
        <div class="chain" id="chain"></div>
        <div class="transactions" id="transactions"></div>
        <div class="node-chain" id="node-chain"></div>
    </div>
<script>
    const apiBase = 'http://localhost';
    async function fetchChain() {
        const response = await fetch(`${apiBase}:5000/chain`);
        const data = await response.json();
        const chainDiv = document.getElementById('chain');
        chainDiv.innerHTML = '<h2>当前区块链</h2>';
        data.chain.forEach(block => {
            const blockDiv = document.createElement('div');
            blockDiv.className = 'block';
            blockDiv.innerHTML = `
                <p><strong>区块:</strong> ${block.index}</p>
                <p><strong>时间戳:</strong> ${block.timestamp}</p>
                <p><strong>交易:</strong> ${JSON.stringify(block.transactions)}</p>
                <p><strong>工作量证明:</strong> ${block.proof}</p>
                <p><strong>上一个区块哈希:</strong> ${block.previous_hash}</p>
            `;
            chainDiv.appendChild(blockDiv);
        });
    }
    async function mineBlock(port) {
        try {
            const response = await fetch(`${apiBase}:${port}/mine`);
            const data = await response.json();
            alert(data.message);
            if (port === '5000') {
                fetchChain();
            }
        } catch (error) {
            alert(`无法连接到端口 ${port}: ${error.message}`);
        }
    }
    async function createTransaction(port) {
        const sender = document.getElementById(`sender${port === '5000' ? '5000' : 'Other'}`).value;
        const recipient = document.getElementById(`recipient${port === '5000' ? '5000' : 'Other'}`).value;
        const amount = document.getElementById(`amount${port === '5000' ? '5000' : 'Other'}`).value;
        try {
            const response = await fetch(`${apiBase}:${port}/transactions/new`, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({ sender, recipient, amount })
            });
            const data = await response.json();
            alert(data.message);
        } catch (error) {
            alert(`无法连接到端口 ${port}: ${error.message}`);
        }
    }
    async function fetchTransactions(port) {
        try {
            const response = await fetch(`${apiBase}:${port}/chain`);
            const data = await response.json();
            const transactionsDiv = document.getElementById('transactions');
            transactionsDiv.innerHTML = `<h2>${port}端口交易记录</h2>`;
            data.chain.forEach(block => {
                block.transactions.forEach(transaction => {
                    const transactionDiv = document.createElement('div');
                    transactionDiv.className = 'transaction';
                    transactionDiv.innerHTML = `
                        <p><strong>发送方:</strong> ${transaction.sender}</p>
                        <p><strong>接收方:</strong> ${transaction.recipient}</p>
                        <p><strong>金额:</strong> ${transaction.amount}</p>
                    `;
                    transactionsDiv.appendChild(transactionDiv);
                });
            });
        } catch (error) {
            alert(`无法连接到端口 ${port}: ${error.message}`);
        }
    }
    async function fetchNodeChain() {
        const node = document.getElementById('nodeChain').value;
        try {
            const response = await fetch(`http://${node}/chain`);
            const data = await response.json();
            const nodeChainDiv = document.getElementById('node-chain');
            nodeChainDiv.innerHTML = '<h2>节点区块链</h2>';
            if (data.error) {
                alert(data.error);
            } else {
                data.chain.forEach(block => {
                    const blockDiv = document.createElement('div');
                    blockDiv.className = 'block';
                    blockDiv.innerHTML = `
                        <p><strong>索引:</strong> ${block.index}</p>
                        <p><strong>时间戳:</strong> ${block.timestamp}</p>
                        <p><strong>交易:</strong> ${JSON.stringify(block.transactions)}</p>
                        <p><strong>工作量证明:</strong> ${block.proof}</p>
                        <p><strong>上一个区块哈希:</strong> ${block.previous_hash}</p>
                    `;
                    nodeChainDiv.appendChild(blockDiv);
                });
            }
        } catch (error) {
            alert(`无法连接到节点: ${error.message}`);
        }
    }
    document.addEventListener('DOMContentLoaded', fetchChain);
</script>
</body>
</html>
  • 运维工作说明

1 基本功能测试

1.1 启动服务

运行代码,启动我们的服务

1.2 链的具体内容

分别查看链的具体内容,可以看到,初始化一个 BlockChain 对象之后,将会创建一个 “创世区块”,此时,区块链中只有一个区块,这个区块中还没有任何一笔交易。

1.3 创建交易

调用接口,创建一笔交易,添加到下一个区块中。(这里我们只使用 5000 端口的节点测试)。

这里我们发送 Post 请求,将发送者地址,接收者地址,交易金额作为参数传入。由着3个参数构成一笔交易的具体内容。

这里为了方便辨认,我们发送者和接收者采用易于辨识的名字。

然后我们这里创建2笔交易,交易发送者的名字分别为 “孔孟群” 和 “智慧小孔”。

1.4 查看5000完整链

我们再次查看5000端口节点的完整链

但是我们发现,链的长度并没有发生改变;这是因为,我们只是进行了交易,但是这些交易并没有被打包成为一个新的区块,我们还要调用 BlockChain 的 new_block() 方法,将交易打包成为一个新的区块。

1.5 打包新的区块

将交易打包成一个新的区块,这里我们调用 new_block ,打包生成一个区块,这里我们依旧使用5000 端口的节点进行测试。简单介绍一个这个方法:

在打包形成一个新的区块之前,所有的交易都存储在 BlockChain 类的 current_transactions 属性里面,我们只需要将这个属性作为 block 属性的一个子属性即可,然后再重置一下 current_transactions 的值即可,方便进行一下次交易。

可以看到,在第2个区块中,除了我们手动添加的2笔交易,发现还有一笔交易。实际上,这笔交易是我们在代码中手动添加的,这个 sender 为 0 表示这个结点已经挖掘出新的星币,然后这笔交易就是为了奖励那个找到工作量证明的那个用户。

1.6 查看整条链的内容

打包生成一个区块后,我们的链理论上应该是有2个区块了。我们调用接口来查看一下我们的链的具体内容。

可以发现,我们的链的长度为2,的确是有2个区块,验证了我的猜测,代码没有问题。

2 分布式功能测试

经过上面的测试过程,我们单一节点的区块链需要具备的功能基本完成了,但是区块链是一个分布式的系统,所以我们还需要进行分布式测试。

2.1 启动2个服务

我们在本机上使用不同的端口模拟不同的节点,第1个结点我们部署在 5000 端口,第2个结点我们部署在 5001 端口。

l5000端口的节点

l5001端口的节点

2.2 两条链的具体内容

分别查看两条链的具体内容。

l5000端口节点

l5001端口节点

可以看到,2条链都初始化成功,各自具有一个创世区块。

2.3 将5001端口节点注册到5000端口节点中去

比特币是分布式的,这里我们为了模仿比特币,启动多个不同端口的节点来模拟分布式。

同时,我们还需要让一个节点知道其相邻结点的存在,即分布式系统中的每一个节点,都需要存储该系统中的其他节点的记录。

这里我们通过 BlockChain 类的 register_node 方法来实现识别相邻节点的功能要求。

register_node 方法需要我们传入一个节点的地址,然后将这个地址添加到 BlockChain 类的 nodes 属性中,这个 nodes 是一个 set 集合。

可以看到,这里我们调用 5000 端口节点的 register_node() 方法,将 5001 端口的地址传入,返回结果显示新的结点已经被创建,说明我们的 5000 端口的节点已经知道 5001 端口节点的存在了。

2.4 测试共识机制

因为之前我们已经将 5001 端口的节点注册到了 5000 端口的节点中,所以 5000 端口的结点时已经知道了 5001 端口节点的存在的,现在只要在 5001 端口的节点上挖掘一些新的区块,使得 5001 端口节点的链的长度要比 5000 端口节点链的长度更长,然后在调用 5000 端口结点的 resolve_conflicts() 函数,如果发现 5000 端口的链被替换成立 5001 端口的链,这说明我们的共识算法是有效的。

好,下面开始我们的测试。

l我们首先在 5001 端口多打包生成一些区块

挖掘区块,使得5001端口有8个区块,即链的长度是8。

l接下来,调用5000端口节点的resolve_conflicts()方法,解决节点间的冲突

调用之前,我们的 5000 端口链的具体内容是这样的,只有1个区块:

l调用 resolve_conflicts() 方法

调用之后显示我们的链已经被替换了

l再次查看 5000 端口链的详细内容

发现链的长度变为了8,验证了我们刚才的猜想,说明我的共识算法是有效的。

2.5界面实现

  • 37
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值