构建自己的区块链
如何构建自己的区块链:Python 教程
本教程将引导您从零开始构建区块链的基础知识。通过一个具体示例的详细讲解,您将更深入地了解区块链的优势和局限性。如需更全面的概述,我推荐您阅读 BitsOnBlocks 上的这篇优秀文章。
1. 交易、验证和系统状态更新
区块链本质上是一个分布式数据库,它有一套规则来验证数据库中新增的数据。我们将首先追踪两个虚拟人物——爱丽丝和鲍勃——的账户,他们将彼此进行虚拟货币交易。
我们需要创建一个用于存储传入交易的交易池,验证这些交易,并将它们打包成一个区块。
我们将使用哈希函数为每笔交易创建一个“指纹”——这个哈希函数将每个区块彼此关联起来。为了方便使用,我们将定义一个辅助函数来封装我们使用的 Python 哈希函数。
import hashlib, json, sys
def hashMe(msg=""):
# For convenience, this is a helper function that wraps our hashing algorithm
if type(msg)!=str:
msg = json.dumps(msg,sort_keys=True) # If we don't sort keys, we can't guarantee repeatability!
if sys.version_info.major == 2:
return unicode(hashlib.sha256(msg).hexdigest(),'utf-8')
else:
return hashlib.sha256(str(msg).encode('utf-8')).hexdigest()
接下来,我们要创建一个函数来生成 Alice 和 Bob 之间的交易。我们将用负数表示取款,用正数表示存款。我们将确保交易始终发生在我们系统的两个用户之间,并且存款金额与取款金额大小相等——也就是说,我们既不创造货币,也不销毁货币。
import random
random.seed(0)
def makeTransaction(maxValue=3):
# This will create valid transactions in the range of (1,maxValue)
sign = int(random.getrandbits(1))*2 - 1 # This will randomly choose -1 or 1
amount = random.randint(1,maxValue)
alicePays = sign * amount
bobPays = -1 * alicePays
# By construction, this will always return transactions that respect the conservation of tokens.
# However, note that we have not done anything to check whether these overdraft an account
return {u'Alice':alicePays,u'Bob':bobPays}
现在让我们创建大量交易,然后将它们分成区块。
txnBuffer = [makeTransaction() for i in range(30)]
下一步:创建我们自己的区块!我们将从交易缓冲区中取出前 k 笔交易,并将它们打包成一个区块。在此之前,我们需要定义一个方法来检查我们提取到区块中的交易的有效性。对于比特币,验证函数会检查输入值是否为有效的未花费交易输出(UTXO),交易输出是否不大于输入值,以及用于签名的密钥是否有效。在以太坊中,验证函数会检查智能合约是否被忠实执行并遵守 gas 限制。
不过别担心,我们不必构建如此复杂的系统。我们将定义一套非常简单的规则,这些规则适用于基本的代币系统:
- 存款和取款的总和必须为 0(代币既不被创造也不被销毁)
- 用户账户必须有足够的资金来支付任何提款。
如果违反了其中任何一个条件,我们将拒绝交易。
同时提供了一些交易示例,其中一些是欺诈性的——但我们现在可以检查它们的有效性!
def updateState(txn, state):
# Inputs: txn, state: dictionaries keyed with account names, holding numeric values for transfer amount (txn) or account balance (state)
# Returns: Updated state, with additional users added to state if necessary
# NOTE: This does not not validate the transaction- just updates the state!
# If the transaction is valid, then update the state
state = state.copy() # As dictionaries are mutable, let's avoid any confusion by creating a working copy of the data.
for key in txn:
if key in state.keys():
state[key] += txn[key]
else:
state[key] = txn[key]
return state
def isValidTxn(txn,state):
# Assume that the transaction is a dictionary keyed by account names
# Check that the sum of the deposits and withdrawals is 0
if sum(txn.values()) is not 0:
return False
# Check that the transaction does not cause an overdraft
for key in txn.keys():
if key in state.keys():
acctBalance = state[key]
else:
acctBalance = 0
if (acctBalance + txn[key]) < 0:
return False
return True
state = {u'Alice':5,u'Bob':5}
print(isValidTxn({u'Alice': -3, u'Bob': 3},state)) # Basic transaction- this works great! True
print(isValidTxn({u'Alice': -4, u'Bob': 3},state)) # But we can't create or destroy tokens! False
print(isValidTxn({u'Alice': -6, u'Bob': 6},state)) # We also can't overdraft our account. False
print(isValidTxn({u'Alice': -4, u'Bob': 2,'Lisa':2},state)) # Creating new users is valid. True
print(isValidTxn({u'Alice': -4, u'Bob': 3,'Lisa':2},state)) # But the same rules still apply! False
每个区块包含一批交易、指向前一个区块哈希值的引用(如果区块编号大于 1)以及其内容和区块头的哈希值。
2. 构建区块链:从交易到区块
我们准备开始构建区块链!目前区块链上什么都没有,但我们可以通过定义“创世区块”(系统中的第一个区块)来启动它。由于创世区块不与任何先前的区块关联,它的处理方式略有不同,我们可以任意设置系统状态。在本例中,我们将为两位用户(Alice 和 Bob)创建账户,并分别给予他们 50 个代币。
state = {u'Alice':50, u'Bob':50} # Define the initial state
genesisBlockTxns = [state]
genesisBlockContents = {u'blockNumber':0,u'parentHash':None,u'txnCount':1,u'txns':genesisBlockTxns}
genesisHash = hashMe( genesisBlockContents )
genesisBlock = {u'hash':genesisHash,u'contents':genesisBlockContents}
genesisBlockStr = json.dumps(genesisBlock, sort_keys=True)
这将成为所有其他要素联系在一起的第一个要素。
chain = [genesisBlock]
对于每个区块,我们希望收集一组交易信息,创建区块头,对其进行哈希处理,然后将其添加到链中。
def makeBlock(txns,chain):
parentBlock = chain[-1]
parentHash = parentBlock[u'hash']
blockNumber = parentBlock[u'contents'][u'blockNumber'] + 1
txnCount = len(txns)
blockContents = {u'blockNumber':blockNumber,u'parentHash':parentHash,
u'txnCount':len(txns),'txns':txns}
blockHash = hashMe( blockContents )
block = {u'hash':blockHash,u'contents':blockContents}
return block
让我们用这个方法将交易缓冲区处理成一组数据块:
blockSizeLimit = 5 # Arbitrary number of transactions per block-
# this is chosen by the block miner, and can vary between blocks!
while len(txnBuffer) > 0:
bufferStartSize = len(txnBuffer)
## Gather a set of valid transactions for inclusion
txnList = []
while (len(txnBuffer) > 0) & (len(txnList) < blockSizeLimit):
newTxn = txnBuffer.pop()
validTxn = isValidTxn(newTxn,state) # This will return False if txn is invalid
if validTxn: # If we got a valid state, not 'False'
txnList.append(newTxn)
state = updateState(newTxn,state)
else:
print("ignored transaction")
sys.stdout.flush()
continue # This was an invalid transaction; ignore it and move on
## Make a block
myBlock = makeBlock(txnList,chain)
chain.append(myBlock)
链信息输出如下:
chain[0]
{
'contents': {'blockNumber': 0,
'parentHash': None,
'txnCount': 1,
'txns': [{'Alice': 50, 'Bob': 50}]},
'hash': '7c88a4312054f89a2b73b04989cd9b9e1ae437e1048f89fbb4e18a08479de507'
}
chain[1]
{
'contents':
{
'blockNumber': 1,
'parentHash': '7c88a4312054f89a2b73b04989cd9b9e1ae437e1048f89fbb4e18a08479de507',
'txnCount': 5,
'txns': [{'Alice': 3, 'Bob': -3},
{'Alice': -1, 'Bob': 1},
{'Alice': 3, 'Bob': -3},
{'Alice': -2, 'Bob': 2},
{'Alice': 3, 'Bob': -3}]
},
'hash': '7a91fc8206c5351293fd11200b33b7192e87fad6545504068a51aba868bc6f72'
}
正如预期的那样,创世区块包含一笔无效交易,该交易会初始化账户余额(凭空创建代币)。子区块引用了父区块的哈希值,其中包含一组影响系统状态的新交易。现在我们可以看到系统状态已更新,包含了这些交易。
3 检查链有效性
既然我们已经知道如何创建新区块并将它们连接成链,那么让我们定义一些函数来检查新区块是否有效,以及整个链是否有效。 在区块链网络中,这一点在两个方面变得尤为重要:
- 在初始设置节点时,我们会下载完整的区块链历史记录。下载完成后,我们需要遍历整个区块链来计算系统状态。为了防止有人在初始链中插入无效交易,我们需要在初始下载时检查整条链的有效性。
- 一旦我们的节点与网络同步(拥有最新的区块链副本和系统状态的表示),它就需要检查广播到网络的新区块的有效性。
我们需要三个功能来实现这一点:
- checkBlockHash: 一个简单的辅助函数,用于确保区块内容与哈希值匹配。
- checkBlockValidity: 检查给定父节点和当前系统状态的区块的有效性。如果区块有效,我们希望它返回更新后的状态;否则,抛出错误。
- checkChain: 检查整个区块链的有效性,并计算从创世区块开始的系统状态。如果区块链有效,则返回系统状态;否则,抛出错误。
def checkBlockHash(block):
# Raise an exception if the hash does not match the block contents
expectedHash = hashMe( block['contents'] )
if block['hash']!=expectedHash:
raise Exception('Hash does not match contents of block %s'%
block['contents']['blockNumber'])
return
def checkBlockValidity(block,parent,state):
# We want to check the following conditions:
# - Each of the transactions are valid updates to the system state
# - Block hash is valid for the block contents
# - Block number increments the parent block number by 1
# - Accurately references the parent block's hash
parentNumber = parent['contents']['blockNumber']
parentHash = parent['hash']
blockNumber = block['contents']['blockNumber']
# Check transaction validity; throw an error if an invalid transaction was found.
for txn in block['contents']['txns']:
if isValidTxn(txn,state):
state = updateState(txn,state)
else:
raise Exception('Invalid transaction in block %s: %s'%(blockNumber,txn))
checkBlockHash(block) # Check hash integrity; raises error if inaccurate
if blockNumber!=(parentNumber+1):
raise Exception('Hash does not match contents of block %s'%blockNumber)
if block['contents']['parentHash'] != parentHash:
raise Exception('Parent hash not accurate at block %s'%blockNumber)
return state
def checkChain(chain):
# Work through the chain from the genesis block (which gets special treatment),
# checking that all transactions are internally valid,
# that the transactions do not cause an overdraft,
# and that the blocks are linked by their hashes.
# This returns the state as a dictionary of accounts and balances,
# or returns False if an error was detected
## Data input processing: Make sure that our chain is a list of dicts
if type(chain)==str:
try:
chain = json.loads(chain)
assert( type(chain)==list)
except: # This is a catch-all, admittedly crude
return False
elif type(chain)!=list:
return False
state = {}
## Prime the pump by checking the genesis block
# We want to check the following conditions:
# - Each of the transactions are valid updates to the system state
# - Block hash is valid for the block contents
for txn in chain[0]['contents']['txns']:
state = updateState(txn,state)
checkBlockHash(chain[0])
parent = chain[0]
## Checking subsequent blocks: These additionally need to check
# - the reference to the parent block's hash
# - the validity of the block number
for block in chain[1:]:
state = checkBlockValidity(block,parent,state)
parent = block
return state
4 整合:最终的区块链架构
在实际的区块链网络中,新节点会下载区块链副本并进行验证(就像我们刚才做的那样),然后在点对点网络上宣告自身的存在,并开始监听交易。它们将交易打包成一个区块,然后将自己提议的区块传递给其他节点。
我们已经了解了如何验证区块链副本,以及如何将交易打包成一个区块。如果我们从其他地方收到一个区块,验证它并将其添加到我们自己的区块链中也很容易。
假设以下代码在节点 A 上运行,该节点负责挖矿:
import copy
nodeBchain = copy.copy(chain)
nodeBtxns = [makeTransaction() for i in range(5)]
newBlock = makeBlock(nodeBtxns,nodeBchain)
现在假设 newBlock 被发送到我们的节点,我们想要检查它是否为有效区块,如果是则更新我们的状态:
print("Blockchain on Node A is currently %s blocks long"%len(chain))
try:
print("New Block Received; checking validity...")
state = checkBlockValidity(newBlock,chain[-1],state) # Update the state- this will throw an error if the block is invalid!
chain.append(newBlock)
except:
print("Invalid block; ignoring and waiting for the next block...")
print("Blockchain on Node A is now %s blocks long"%len(chain))
Blockchain on Node A is currently 7 blocks long
New Block Received; checking validity...
Blockchain on Node A is now 8 blocks long
5 结论与拓展
我们已经构建了区块链的所有基本架构,从状态转换规则集到区块创建方法,再到用于验证交易、区块和整条链有效性的机制。我们可以从下载的区块链副本中获取系统状态,验证从网络接收的新区块,并创建我们自己的区块。
我们创建的系统状态实际上是一个分布式账本或数据库——这是许多区块链的核心。我们可以扩展它,使其包含特殊交易类型或完整的智能合约。
我们尚未探讨网络架构、工作量证明或状态证明验证步骤,以及为区块链提供抗攻击安全性的共识机制。我们也尚未讨论公钥密码学、隐私和验证步骤。未来我们将对此进行更深入的探讨!