使用 Python 构建区块链

2025年1月8日 | 阅读 11 分钟

在我们开始使用 Python 编程语言构建区块链之前,让我们回到最初。2008 年,一位笔名为中本聪(Satoshi Nakamoto)的作者(或作者们)发布了一篇白皮书,描述了一种纯粹的点对点电子现金系统。这种电子现金系统独有的特点是,交易无需依赖第三方验证即可确保每笔交易的安全性。取而代之的是,每笔交易都会被加上时间戳,然后根据哈希值进行工作量证明(proof-of-work),连接到一个持续增长的链中。

那么,什么是哈希和工作量证明呢?我们将在接下来的教程中介绍这些概念,并理解它们如何为加密电子现金系统或加密货币奠定基础。中本聪在他的论文中描述的这种特定形式的电子货币成为了比特币,第一个加密货币。但是,当试图使用 Python 构建区块链时,这有什么用呢?

在接下来的教程中,我们将对此进行讲解。

理解区块链

比特币所依赖的系统——一个不断增长的、相互连接的记录列表(即区块)——被称为区块链。比特币是该系统首次成功的应用,在其声名鹊起之后,其他加密货币也基于相同的理念建立起来。然而,该系统并不局限于收集金融信息。相反,存储的数据类型无关紧要,并且独立于区块链网络。

从根本上说,存储在区块链中的数据应该具有以下特征:

  1. 不可变
  2. 分布式
  3. 持久性(数据不丢失)
  4. 不可破解

区块链是一个开源应用程序,在数千台计算机之间共享。这些计算机遵循一套规则来跟踪已从与区块链软件关联的账户发送的金钱。这些特性对于维护区块链的完整性以及交易发生的网络安全性是必需的。

每个区块都包含一组数据,例如,“汤姆周二向哈里支付了 500 美元”。在区块链上,我们可以不使用银行就能转账。为了说明这样一个系统的简单性和优雅性,并描述其细微之处,我们将了解使用 Python 编程语言创建我们自己的区块链的过程。

对于接下来的项目,我们只需要 Python。另外,请记住,我们的区块链将是一个简化的、高层次的介绍。我们不会构建完整的比特币区块链。相反,我们将创建函数来添加区块、交易和加密,以便我们的数据是防篡改的。

让我们开始吧。

使用 Python 构建区块链

为了更好地理解,我们将构建区块链的过程分为几个步骤。这些步骤如下:

第一步:创建区块链类

第二步:编写函数来构建新区块

第三步:编写函数来创建新交易并获取最后一个区块

第四步:编写函数来“哈希”区块

第五步:创建一个新的区块链并发送一些钱

现在,我们将在以下部分讨论这些步骤。

创建区块链类

我们将首先导入所需的库。在这种情况下,我们将需要 **hashlib** 库进行加密,**JSON** 库用于我们的区块格式化,以及 **time** 库用于每个区块的时间戳。然后,我们将创建一个类并初始化以下变量:

  1. chain:这是一个空列表,我们将向其中添加区块。顾名思义,就是“区块链”。
  2. pendingTransactions:当用户互相发送硬币时,他们的交易将保留在此数组中,直到我们批准并将其插入新区块。
  3. newBlock:这是一个我们稍后将定义的函数,我们将使用它将每个区块包含在链中。

让我们考虑以下演示相同内容的代码片段。

示例

说明

在上面的代码片段中,我们导入了所需的库并创建了 **Block_chain** 类,在该类中我们初始化了前面描述的各个变量。

编写函数来构建新区块

现在我们已经初始化了一个空链,让我们开始向其中插入区块。然后,我们将定义具有以下属性的 JSON 对象:

  1. index:取区块链的长度并加 1。我们将用它来引用单个区块,例如,创世区块的索引 = 1。
  2. timestamp:借助 **time()** 模块,我们将在创建区块时为其打上时间戳。用户现在可以检查他们的交易何时在链上得到确认。
  3. transactions:任何已在“待处理”列表中的交易都将显示在新区块中。
  4. proof:此属性来自认为自己找到有效“证明”或“随机数”的矿工。
  5. previous_hash:最近一个已批准区块的哈希版本。

一旦我们将上述属性添加到新区块,我们就会将其包含在链中。起初,我们会清空交易的待处理列表,然后将新区块添加到 **self.chain** 并返回它。

让我们通过下面显示的示例代码来理解这一点。

示例

说明

代码首先导入了必要的库:hashlib、json 和 time。这些库是区块链操作的基础。hashlib 提供了 SHA-256 哈希函数,它确保每个区块的安全性和不可变性。json 库用于将 Python 对象序列化为一致的 JSON 格式,使我们能够创建对每个区块进行可靠和标准化的表示。time 函数用于为每个区块打上时间戳,记录其创建时间。

然后定义了 Blockchain 类,它代表了区块链的核心结构。在 __init__ 方法中,初始化了两个列表:self.chain 用于存储区块序列,self.pendingTransactions 用于存放等待添加到下一个区块的交易。

newBlock 方法负责创建一个新区块并将其添加到区块链中。它接受两个参数:the_proof,它是一个验证区块的值;以及 previousHash,它将此区块链接到前一个区块。在此方法内部,创建了一个名为 the_block 的字典,其中包含区块的索引、时间戳、交易、证明以及前一个区块的哈希值。区块创建后,待处理交易列表将被清空,因为它们已被包含在区块中,然后新区块将被添加到区块链中。该方法随后返回新创建的区块。

hash 方法为给定区块生成 SHA-256 哈希。它首先将区块转换为 JSON 字符串,并对键进行排序以确保一致的顺序。然后对该字符串进行编码,并将其通过 SHA-256 函数处理,以生成唯一标识该区块的哈希值。

最后,示例用法部分演示了如何创建区块链实例并使用 newBlock 方法向其中添加新区块。这个简单的示例突出了创建和附加区块链中的区块的基本操作。

让我们考虑以下演示相同内容的代码片段。

示例

说明

在上面的代码片段中,我们定义了 **lastBlock()** 方法,它返回最近添加的区块。然后,我们定义了 **newTransaction()** 函数,在该函数中,我们将 JSON 对象定义为 **the_transaction**,并包含了发件人、收件人和金额的地址。我们将此 JSON 对象添加到 **pendingTransaction** 中,并返回最后一个区块。

编写函数来“哈希”区块

现在,让我们为程序添加密码学。我们知道,比特币和许多其他区块链都使用 SHA-256,这是一种加密哈希函数,它接受一些文本字符串(存储为 Unicode 值)并输出一个 64 个字符长的加密字符串。在区块链中,我们加密的文本被视为一个区块。例如,比特币创世区块的加密字符串或“哈希”如下所示:

fbc13b85c4ade52e2def26eae950f3b55b174df887ad0f0fb5ebfd56881f7fcb

区块链被认为是防篡改的,因为每个区块都包含前一个区块哈希的副本。由于新哈希是从最后一个区块派生的,因此我们无法更改区块的任何方面,而不改变它前面的所有哈希。

假设有人将比特币区块链下载到他们的计算机上,并在创世区块中写下了“Satoshi 发送 Alex 7,236,000 比特币!”这条信息,然后将其广播到比特币网络,并声称自己是秘密富翁。然而,一旦任何有自尊的矿工将他们当前区块链的副本,特别是每个区块中存储的哈希值,与他的链副本进行比较,他们就会发现他是个骗子,拒绝验证,并将他踢出网络。

我们将定义一个接受新区块的方法,并将其键/值对更改为字符串。然后,我们将把该字符串转换为 Unicode,然后将其传递给 **hashlib** 库中的 **SHA256** 方法,并从其返回值创建一个十六进制字符串。然后,我们将返回新的哈希值。

让我们通过以下代码片段来理解这一点。

示例

说明

在上面的代码片段中,我们定义了 **hash()** 函数,它接受一个区块并将其转换为字符串,然后转换为 Unicode 进行哈希。然后,我们使用 **SHA256()** 函数进行加密,然后将 Unicode 转换为十六进制字符串。

创建一个新的区块链并发送一些钱

既然我们已经创建了区块链类,并包含了各种方法来构建新区块和新交易,以及一个用于使用 SHA256 加密哈希任何区块的自定义方法,让我们开始构建链。

我们将实例化 **Block_chain** 类并执行一些虚拟交易。请确保将它们列在包含在链中的一些区块中。

让我们考虑以下演示相同内容的代码片段。

示例

说明

在上面的代码片段中,我们实例化了 **Block_chain()** 类。然后,我们执行了一些交易并打印了它们供用户查看。

现在,让我们看一下使用 Python 构建区块链项目的完整代码。

完整项目代码

文件:buildingBlockchain.py

输出

Genesis block:  [
    {'index': 1,
    'timestamp': 1640067926.584454,
    'transactions': [],
    'proof': 100,
    'previous_hash': 'The Times 03/Jan/2009 Chancellor on brink of second bailout for banks.'},
    
    {'index': 2,
    'timestamp': 1640067926.584454,
    'transactions': [
        {'sender': 'Satoshi', 'recipient': 'Alex', 'amount': '10 BTC'},
        {'sender': 'Alex', 'recipient': 'Satoshi', 'amount': '2 BTC'},
        {'sender': 'Satoshi', 'recipient': 'James', 'amount': '10 BTC'}
        ],
    'proof': 10123,
    'previous_hash': 'a1b0cf063d43989421eb4b28d9be8f82c2e2e9e40bc9814321e3cbb70b00530a'},
    
    {'index': 3,
    'timestamp': 1640067926.584454,
    'transactions': [
        {'sender': 'Alex', 'recipient': 'Lucy', 'amount': '2 BTC'},
        {'sender': 'Lucy', 'recipient': 'Justin', 'amount': '1 BTC'},
        {'sender': 'Justin', 'recipient': 'Alex', 'amount': '1 BTC'}
        ],
    'proof': 10384,
    'previous_hash': '23699917fdcc013a85bbb5872251768e976bfcc2cd8403565c04970bca24a871'}
    ]

说明

在上面的输出中,我们可以看到我们的区块链目前包含三个区块:创世区块(索引为 1,没有交易),以及我们自己添加的 2 个区块。我们还可以注意到,加密哈希(源自每个前面的区块)和时间戳不匹配。诚然,计算机几乎同时构建了每个区块,因为我们执行了程序并几乎同时生成了区块;然而,比特币区块大约每十分钟创建一个。

我们中有人注意到任何账户余额吗?区块链不是银行,这是一个区分两者的好例子。加密货币钱包将通过扫描整个链并汇总我们收到的硬币和花费的硬币量来估算余额。我们不必依赖银行来告诉我们账户中的金额。我们只是信任网络而不是一家大型企业。这不是很迷人吗?

总结

在接下来的教程中,我们成功构建了一个可以填充包含加密货币交易的区块的区块链;然而,这不是一个安全的网络。首先,每当有人调用 **newBlock()** 时,我们就会创建一个区块,而且没有任何条件。**newBlock()** 方法需要一个名为 proof 的参数;然而,在我们的例子中,它可以是任何东西。它可以是一个数字,也可以是一个字符串,说“你好世界”,或者基本上任何东西。

在比特币网络中,存在一个称为 **工作量证明** 的共识机制,它说明了实现安全性的规则。证明是一个随机数,除非我们有一些专用高性能机器全天候工作,否则很难找到。

我们还遗漏了许多其他细节,例如矿工需要收取的费用、交易计数、公钥/私钥、默克尔树结构等等。然而,上面的演练为我们提供了一个有用的区块链运行组件的基本示例。