构建自己的区块链

来自程序员技术小站
跳转到导航 跳转到搜索

如何构建自己的区块链: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 结论与拓展

我们已经构建了区块链的所有基本架构,从状态转换规则集到区块创建方法,再到用于验证交易、区块和整条链有效性的机制。我们可以从下载的区块链副本中获取系统状态,验证从网络接收的新区块,并创建我们自己的区块。

我们创建的系统状态实际上是一个分布式账本或数据库——这是许多区块链的核心。我们可以扩展它,使其包含特殊交易类型或完整的智能合约。

我们尚未探讨网络架构、工作量证明或状态证明验证步骤,以及为区块链提供抗攻击安全性的共识机制。我们也尚未讨论公钥密码学、隐私和验证步骤。未来我们将对此进行更深入的探讨!