本文最初发布于 Medium 网站,经原作者授权由 InfoQ 中文站翻译并分享。
前言
在本文中,我们将使用 Python 语言实现一个简单朴素的“智能区块链”,并和一个区块链做对比。我们希望通过这种方式,帮助开发人员和区块链社区更清楚地了解并接纳这种技术的主要概念和优势。常见的“智能区块链”网络中有一个区块生产者智能合约(BPSC)。为了在网络中成功交易,发送者必须先将加密货币转移到(BPSC)钱包,然后(BPSC)立即将相同的加密货币自动发送到最终接收者的钱包。这个看似简单的任务可以准确地保存(BPSC)智能合约中所有成功交易的数据,并确保数据不受篡改。本文由 Somayeh Gholami 和 Mehran Kazeminia 于 2020 年 4 月撰写。
开始之前的准备工作
如果你不熟悉“智能区块链”,并且还没有阅读过我们以前写的关于这种技术的文章,那么可以在下面这篇文章中大致了解一下。本文的实现过程中也会给出必要的解释。
https://hackernoon.com/what-is-smart-blockchain-4b134275e90f
我们假设读者熟悉 Python 语言并理解区块链、哈希等技术概念。因此为了简化篇幅,我们将重点放在了“智能区块链”的特性上。当然,如果你忘记了一些区块链的概念,则可以参考以下文章。在这篇文章中,作者使用 Python 实现了一个完整的区块链。我们还使用他的想法和代码来实现了“智能区块链”,这样就能方便地对比“智能区块链”和区块链了。
https://hackernoon.com/learn-blockchains-by-building-one-117428612f46
我们的 Python 版本是 Python 3.7,使用 Spyder-Anaconda3 作为 IDE。你可以使用自己喜欢的 IDE,例如 PyCharm 等。当然,Jupyter Notebook 之类并不适合这一用例。我们还将使用 Postman,但你也可以使用 cURL
通过编程了解“智能区块链”的功能
首先,我们来创建一个名为 Smart_Blockchain 的类。这个类的构造方法现在会创建两个空列表,一个用于存储交易(transaction),另一个用于存储区块(block)的链。在这个类的构造方法中,我们还编写了用于创建初始的创世块(genesis block)的代码。现在我们需要完成以下四种方法:
new_block、new_transaction、last_block、hash
但是,“智能区块链”中没有共识问题,也没有工作量证明(POW)机制或权益证明(POS)机制之类的东西。换句话说,没必要像比特币和以太坊网络那样生成单一区块的几个替代方案,让矿工花费数十亿美元和惊人的功耗来选择有效且正确的区块。以下代码就是根据这些特性来编写的。
import hashlib
import json
from time import time
class Smart_Blockchain:
def __init__(self):
self.current_transactions = []
self.chain = []
# Create the genesis block
self.new_block(previous_hash='1')
def new_block(self, previous_hash):
"""
Create a new Block in the Smart Blockchain
:param previous_hash: Hash of previous Block
:return: New Block
"""
block = {
'index': len(self.chain) + 1,
'timestamp': time(),
'transactions': self.current_transactions,
'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, amount, recipient):
"""
Creates a new transaction to go into the next mined Block
:param sender: Address of the Sender
:param amount_send: The amount sent by the sender
:param bpsc: Address of the Smart contract (bpsc)
:param amount_bpsc: The amount received by bpsc (Transaction fees)
:param recipient: Address of the Recipient
:param amount_receive: The amount received by the recipient
:return: The index of the Block that will hold this transaction
"""
self.current_transactions.append({
'sender': sender,
'amount_send': amount,
'bpsc': 'bpsc_wallet_address', # Block Producer Smart Contract (bpsc)
'amount_bpsc': amount * 0.00005, # Transaction fees
'recipient': recipient,
'amount_receive': amount * 0.99995,
})
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: Block
"""
# We must make sure that the Dictionary is Ordered, or we'll have inconsistent hashes
复制代码
在以上代码中,没有证明变量或随机数,也不需要区块奖励。因为如前所述,“智能区块链”没有共识可言。甚至交易费也可能为零。但是,为了避免垃圾邮件之类的目的,我们可以假定每笔交易的总费用为 0.00005。这笔小额款项存储在(BPSC)智能合约中,交易的总成本可用于开发等工作,甚至可以烧掉以产生通缩。通缩是维持和增强货币以及加密货币价值的最有效方法之一。我们现在正在使用 Flask 框架,这样就可以通过基于 HTTP 协议平台的请求来与“智能区块链”通信。为此我们创建以下三种方法:
/transactions/new
/mine
/chain
import hashlib
import json
from time import time
from urllib.parse import urlparse
from flask import Flask, jsonify, request
class Smart_Blockchain:
# .....
# .....
# .....
# Instantiate the Node
app = Flask(__name__)
# Instantiate the Smart_Blockchain
blockchain = Smart_Blockchain()
@app.route('/mine', methods=['GET'])
def mine():
last_block = blockchain.last_block
# Forge the new Block by adding it to the chain
previous_hash = blockchain.hash(last_block)
block = blockchain.new_block(previous_hash)
response = {
'message': "New Block Forged",
'index': block['index'],
'transactions': block['transactions'],
'previous_hash': block['previous_hash'],
}
return jsonify(response), 200
@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', 'amount', 'recipient']
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['amount'], values['recipient'])
response = {'message': f'Transaction will be added to Block {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
if __name__ == '__main__':
from argparse import ArgumentParser
parser = ArgumentParser()
parser.add_argument('-p', '--port', default=5000, type=int, help='port to listen on')
args = parser.parse_args()
port = args.port
app.run(host='0.0.0.0', port=port)
复制代码
根据以上代码,服务器将在端口 5000 上启动。当然,另一种方法是导入 sys 并对最后一部分代码进行以下更改。这样,你就可以在每次运行时指定所需的端口。
import sys
.....
if __name__ == '__main__':
app.run(host='0.0.0.0', port=sys.argv[1])
复制代码
现在,我们将代码的两个主要部分串联起来。你可以在这里下载本文的所有代码。这时,我们的文件名为bpsc101.py。在下一步中我们将运行服务器。我们还使用 Postman 连接到网络上的新 API。将 POST 交易请求发送到下列服务器地址后,我们将开始与新 API 通信:
http://localhost:5000/transactions/new
这个交易的信息必须在“Body”部分中输入,并且显然,我们选择“raw”,然后是“JSON”。
在上面的示例中,我们想将一百万个代币(例如 Aka 代币)从一个钱包发送给(带特定地址的)另一个人。为此,我们将代币和对这个交易的请求发送到服务器(其充当区块生产者智能合约)。如你所见,服务器的响应将这个交易添加到第二个块中,因为创世块已经在代码中定义过了。
现在,我们发送一个 GET 挖掘请求到服务器地址:
http://localhost:5000/mine
这将创建下一个块。每个块的内容包括所有已执行的交易和未完成的交易,例如在这里,创建的第二个块仅包含一个交易。
根据上图,作为交易费的五十个代币保留在(BPSC)钱包中,其余代币由(BPSC)发送给最终接收者。当然,如前所述,交易费用将被烧掉或花费在网络开发等任务上。但是,我们尚未对这些金额的支出类型建模。现在,我们知道链服务器中必须有两个块。要查看当前链,我们发送一个 GET 链请求到服务器地址:
http://localhost:5000/chain
为了进一步的测试,你可以发送新的交易请求,并且每次发送少量交易请求后,你都可以发送一个挖掘请求。即使不做交易,你也可以挖一个空块。同时,链请求可随时为你确定链的状态。
“智能区块链”的去中心化网络
像区块链这样的“智能区块链”网络是去中心化的。因此我们需要为每个节点实现一种方法,告知它们旁边有哪些节点,并保留那些节点的列表。为此我们使用 set(),这样每个节点仅记录一次。我们还创建以下方法来接受 URL 形式的新节点列表:
/nodes/register
import hashlib
import json
from time import time
from urllib.parse import urlparse
from flask import Flask, jsonify, request
class Smart_Blockchain:
def __init__(self):
self.current_transactions = []
self.chain = []
self.nodes = set()
# Create the genesis block
self.new_block(previous_hash='1')
def register_node(self, address):
"""
Add a new node to the list of nodes
:param address: Address of node. Eg. 'http://192.168.0.5:5000'
"""
parsed_url = urlparse(address)
if parsed_url.netloc:
self.nodes.add(parsed_url.netloc)
elif parsed_url.path:
# Accepts an URL without scheme like '192.168.0.5:5000'.
self.nodes.add(parsed_url.path)
else:
raise ValueError('Invalid URL')
.......
@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
if __name__ == '__main__':
from argparse import ArgumentParser
parser = ArgumentParser()
parser.add_argument('-p', '--port', default=5000, type=int, help='port to listen on')
args = parser.parse_args()
port = args.port
app.run(host='0.0.0.0', port=port)
复制代码
请记住,在“智能区块链”中,只有(BPSC)的区块生产者智能合约可以创建或挖掘新区块。但网络的其余节点只能将请求发送到(BPSC),并通过该请求接收最新的链。因此,我们需要实现来自(BPSC)的节点请求以返回最新链。为此我们创建以下方法:/smart/chain
import hashlib
import json
from time import time
from urllib.parse import urlparse
import requests
from flask import Flask, jsonify, request
class Smart_Blockchain:
def __init__(self):
self.current_transactions = []
self.chain = []
self.nodes = set()
# Create the genesis block
self.new_block(previous_hash='1')
def smart_chain(self):
"""
All nodes can receive the smart_chain
"""
schain = None
response = requests.get(f'http://127.0.0.1:5000/chain')
if response.status_code == 200:
chain = response.json()['chain']
schain = chain
# Replace our chain
if schain:
self.chain = schain
return True
return False
.......
@app.route('/smart/chain', methods=['GET'])
def smart_chain():
replaced = blockchain.smart_chain()
if replaced:
response = {
'message': 'Smart chain update by bpsc',
'smart chain': blockchain.chain,
'length': len(blockchain.chain)
}
else:
response = {
'message': 'Unsuccessful: Please try again',
'old chain': blockchain.chain,
'length': len(blockchain.chain)
}
return jsonify(response), 200
if __name__ == '__main__':
from argparse import ArgumentParser
parser = ArgumentParser()
parser.add_argument('-p', '--port', default=5000, type=int, help='port to listen on')
args = parser.parse_args()
port = args.port
app.run(host='0.0.0.0', port=port)
复制代码
如前所述,在这个示例中,我们要在端口 5000 上启动服务器(充当区块生产者智能合约)。根据上述代码,网络中的其他任何节点都可以向(BPSC)发送请求,并收到最新链。第 26 行上的命令“requests”执行相同的操作。换句话说,根据一个请求,由(BPSC)创建的最新链将替换该节点的链。下面我们还需要把目前为止的各段代码组合起来。但如果我们要测试/nodes/register 方法和/smart/chain 方法,则需要在网络中有其他节点。这意味着我们要么使用多台计算机,要么在同一台计算机上定义不同的端口。我们选择第二种方式,在同一台计算机上创建端口号为 5001 和 5002 的另外两个节点。但主要问题是,除了负责交易、挖掘区块并为特定代币(例如 Aka 代币)创建链的(BPSC)之外,其余节点应该无法执行这些活动。当然这很简单,我们只需要从其他节点中删除一些代码即可,例如挖掘能力等。现在我们的文件名是bpsc102.py。
为此我们创建了以下文件:nodes_v1_5001.py和nodes_v1_5002.py。请单击它们查看详细更改。这些文件与原始的 bpsc102.py 相比仅删除了部分代码,并且未添加任何内容。当然,端口号也已更改。
如果你出于任何原因使用了 VPN,则必须在后续测试中将其关闭。请在单独的控制台上运行所有三个文件,并创建三个节点。这意味着我们将同时拥有以下节点:
http://localhost:5000
http://localhost:5001
http://localhost:5002
测试时,我们首先发送两个连续的 POST 请求,以将端口号为 5001 的节点通知给网络上的邻居节点,并存储这些节点的列表:
http://localhost:5001/nodes/register
这意味着,第一个“节点”:[“http://127.0.0.1:5000”](BPSC)和第二个“节点”:[“http://127.0.0.1:5002“](另一个节点)存储在了上述列表中。
现在我们要测试/smart/chain 方法。但在这个测试之前,我们最好至少发送一个交易请求,然后再向(BPSC)发送一个挖掘请求。这将在(BPSC)中保存一个带有两个块的链。
现在,我们将后续请求(GET 类型)发送到端口号为 5001 的节点地址:
http://localhost:5001/smart/chain
这样,具有端口 5001 的节点将接收来自(BPSC)的最新链,节点的链替换为(BPSC)创建的最新链。
这样,在“智能区块链”中,尽管所有节点都无法生成区块链,但所有节点都可以通过(BPSC)交易并从(BPSC)接收最新链。另外,作为去中心化网络,节点之间可以相互通信,网络节点甚至可以通过数十个(BPSC)的服务获益,因为假定每个(BPSC)都专用于一种代币。
去中心化网络上的数十个“智能区块链”
为了解释清楚这个主题,我们最好为网络节点实现第二条链。通过这种模式,你能在每个节点上存储数十个并行的链。
import hashlib
import json
from time import time
from urllib.parse import urlparse
import requests
from flask import Flask, jsonify, request
class Smart_Blockchain:
def __init__(self):
self.current_information = []
self.chain = []
self.chain2 = []
self.nodes = set()
# Create the genesis block
self.new_block(previous_hash='1')
def register_node(self, address):
"""
Add a new node to the list of nodes
:param address: Address of node. Eg. 'http://192.168.0.5:5000'
"""
parsed_url = urlparse(address)
if parsed_url.netloc:
self.nodes.add(parsed_url.netloc)
elif parsed_url.path:
# Accepts an URL without scheme like '192.168.0.5:5000'.
self.nodes.add(parsed_url.path)
else:
raise ValueError('Invalid URL')
def smart_chain(self):
"""
All nodes can receive the smart_chain
"""
schain = None
response = requests.get(f'http://127.0.0.1:5000/chain')
if response.status_code == 200:
chain = response.json()['chain']
schain = chain
# Replace our chain
if schain:
self.chain = schain
return True
return False
def new_block(self, previous_hash):
"""
Create a new Block in the Smart Blockchain
:param previous_hash: Hash of previous Block
:return: New Block
"""
block = {
'index2': len(self.chain2) + 1,
'timestamp': time(),
'information': self.current_information,
'previous_hash': previous_hash or self.hash(self.chain2[-1]),
}
# Reset the current list of transactions
self.current_information = []
self.chain2.append(block)
return block
def new_information(self, information):
"""
Creates a new information
:param information: Your information
:return: The index of the Block that will hold this information
"""
self.current_information.append({'information': information })
return self.last_block['index2'] + 1
@property
def last_block(self):
return self.chain2[-1]
@staticmethod
def hash(block):
"""
Creates a SHA-256 hash of a Block
:param block: Block
"""
# 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()
# Instantiate the Node
app = Flask(__name__)
# Instantiate the Smart_Blockchain
blockchain = Smart_Blockchain()
@app.route('/mine', methods=['GET'])
def mine():
last_block = blockchain.last_block
# Forge the new Block by adding it to the chain
previous_hash = blockchain.hash(last_block)
block = blockchain.new_block(previous_hash)
response = {
'message': "New Block Forged",
'index2': block['index2'],
'information': block['information'],
'previous_hash': block['previous_hash'],
}
return jsonify(response), 200
@app.route('/information/new', methods=['POST'])
def new_information():
values = request.get_json()
# Check that the required fields are in the POST'ed data
required = ['information']
if not all(k in values for k in required):
return 'Missing values', 400
# Create a new information
index = blockchain.new_information(values['information'])
response = {'message': f'information will be added to Block {index}'}
return jsonify(response), 201
@app.route('/chain2', methods=['GET'])
def full_chain2():
response = {
'chain2': blockchain.chain2,
'length': len(blockchain.chain2),
}
return jsonify(response), 200
@app.route('/chain', methods=['GET'])
def full_chain():
response = {
'chain': blockchain.chain,
'length': len(blockchain.chain),
}
return jsonify(response), 200
@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('/smart/chain', methods=['GET'])
def smart_chain():
replaced = blockchain.smart_chain()
if replaced:
response = {
'message': 'Smart chain update by bpsc',
'smart chain': blockchain.chain,
'length': len(blockchain.chain)
}
else:
response = {
'message': 'Unsuccessful: Please try again',
'old chain': blockchain.chain,
'length': len(blockchain.chain)
}
return jsonify(response), 200
if __name__ == '__main__':
from argparse import ArgumentParser
parser = ArgumentParser()
parser.add_argument('-p', '--port', default=5001, type=int, help='port to listen on')
args = parser.parse_args()
port = args.port
app.run(host='0.0.0.0', port=port)
复制代码
仔细查看以上代码,可以发现两个并行且独立的区块链存储在一个节点上。当然,其中一个是先前的“智能区块链”,另一个是已添加到先前代码中以存储个人数据的简单区块链。当然,第二个区块链可以是“智能区块链”,并且节点能够以相同的方式使用数十个(BPSC)服务(这些服务可以是存储在各自独立的区块链中针对不同代币的交易服务,等等。)为了创建第二个链,我们将两个列表添加到先前的列表中。第 12 行用于存储数据,第 14 行用于存储区块链。你可以自行检查其余的更改。我们根据上述代码创建了nodes_v2_5001.py和nodes_v2_5002.py文件。请同时运行这两个文件和bpsc102.py文件以执行以下测试。
现在我们要测试/chain2 方法。nodes_v2_5001.py 文件表示一个端口号为 5001 的节点。我们向该节点发送了一个存储个人数据的请求。当然,我们知道最早创建的是初始块或称创世块,所以服务器会将数据添加到第二个块。然后,我们发送对同一节点的挖掘请求。这将创建下一个块(第二个链上的第二个块)。
http://localhost:5001/information/new
http://localhost:5001/mine
我们将后续类型为 GET 的请求发送到同一节点:
http://localhost:5001/chain2
通过这个测试,我们可以确保两个块都位于服务器的第二个链中,并且第二个链与第一个链没有关系。
为了确保第二条链不会干扰“智能区块链”的功能,我们将重复之前对“智能区块链”的测试。也就是说,我们首先测试所有三个节点的/nodes/register 方法。然后,从扮演(BPSC)角色的节点上,我们请求几个交易和多个挖掘:
http://localhost:5000/transactions/new
http://localhost:5000/mine
最后,我们将后续类型为 GET 的请求发送到端口号为 5001 的节点地址:
http://localhost:5001/smart/chain
如你在图中所见,由(BPSC)创建的最新链代替了该节点的主链(第一条链)。最后我们得出结论,“智能区块链”和该节点中存储的第二条链各自完全独立。
在“智能区块链”中计划和调度挖矿,及生产区块的操作
当一个智能合约在区块链网络上注册后,它就会永远存在下去。也就是说,只要这个区块链网络还在正常工作,任何人(甚至是智能合约的编写者和拥有者)都无法对合约的初始内容做任何改动。区块生产者智能合约(BPSC)的情况也是一样的。因此从一开始,挖矿作业(生产区块)的时间就应在代码中明确实现。否则,(BPSC)就必须等待挖掘请求才能挖掘新块。
有三种方法可以为挖矿作业(生产区块)规划一个(BPSC)智能合约:
我们这里只实现一种方法的一个示例。例如,假设在每个交易之后,(BPSC)都会创建一个新块。因此每个块仅包含一个交易。
..........
@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', 'amount', 'recipient']
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['amount'], values['recipient'])
response = {'message': f'Transaction will be added to Block {index}'}
last_block = blockchain.last_block
# Forge the new Block by adding it to the chain
previous_hash = blockchain.hash(last_block)
block = blockchain.new_block(previous_hash)
response = {
'message': "New Block Forged",
'index': block['index'],
'transactions': block['transactions'],
'previous_hash': block['previous_hash'],
}
return jsonify(response), 201
..........
复制代码
如你所见,这里只更改了/transactions/new 方法,在方法末尾添加了/mine 的代码。我们从中创建了bpsc103.py文件。你可以运行该文件并使用 Postman 执行所需的测试。在这种情况下,如果你将之后的请求发送到服务器:
http://localhost:5000/transactions/new
(BPSC)会立即进行交易,同时挖掘一个块并将其添加到链中。
现在,如果你决定将挖掘请求发送到服务器:
http://localhost:5000/mine
就只会挖一个空块,因为没有任何交易。
总结和结论
为了模拟(BPSC)区块生产者智能合约,我们设定了端口号为 5000 的节点,并实现了以下三个文件:
bpsc101.py:这个版本不是最终版本,不能与其他节点交互。
bpsc102.py:这是最终版本,并且所有场景都针对该版本做了测试。我们测试了交易请求、挖掘请求、发送其他节点的最新链的请求,等等。
bpsc103.py:这个版本也是最终版本,但我们在之前的版本基础上添加了新功能。具体来说,(BPSC)需要在每次交易后创建一个新块并将该块添加到链中。
端口 5001 和 5002 表示其他网络节点。为了模拟这些节点,我们实现了以下两个版本:
nodes_v1_5001.py 和 nodes_v1_5002.py
nodes_v2_5001.py 和 nodes_v2_5002.py
至此代码就写完了。你还可以在单机或多台机器上做更多测试。但请记住,无论是针对区块链还是“智能区块链”的 Python 模拟,通常都不会包含所有必要的控件和操作。在本文中就没有涉及某些控件。例如,对钱包的必要控件就没提。如果(BPSC)处于任何原因未能将代币发送给最终接收者,我们会希望将所有代币返回给发送者,但这里也没有实现。在实际工作中不应忽略这些控件。大多数智能合约均以特定的高级语言编写。例如,以太坊区块链中的智能合约通常以 Solidity 语言编写。还好,使用 Solidity 实现这种类型的控件比 Python 更容易。
最后提一下
在本文和之前的一些文章中,我们已经证明矿工、区块生产者及其活动不是区块链技术的固有组成部分。当然,用智能合约代替矿工和区块生产者是一个新的,有争议的想法,并且存在很多反对声音和阻碍。但是从长远来看,维持现状是不可能的。实际上,比特币的工作量证明机制(POW)陷入了僵局。所有矿工都在提高他们的处理能力以相互竞争,结果消耗相当于一个一千万人口国家的年消费量的电力。这不公平。
剑桥比特币电力消耗指数
如果你对这个主题感兴趣,可以阅读我们之前关于“智能区块链”以及比特币问题的文章。
另外,如果你有任何疑问或在本文中看到任何歧义或错误,请告知我们。
Somayyeh Gholami
Mehran Kazeminia
原文链接:
Implementing a “Smart Blockchain” with Pyth
评论