Python 使用 Web3 与智能合约交互详解

在本章中,您将学习如何以编程方式连接到智能合约。这里,您将使用 Python 编程语言来执行智能合约中的方法。为了实现这一点,您将使用web3.py库。在上一章中,您构建了智能合约并将其部署到以太坊区块链。您还使用 Vyper 编写了智能合约。为了与智能合约进行交互,您启动了 Truffle 控制台并输入了许多命令。这些命令被发送到区块链中的智能合约。根据您键入的命令,这可以读取智能合约的状态,也可以更改智能合约。在本章中,您将超越 Truffle 控制台。

本章将涵盖以下主题:

您将使用 Python 构建一个程序,以编程方式执行智能合约中的方法,我们将此程序称为去中心应用。因此,有一个智能合约,有一个分散的应用。使用 Vyper 或 Solidity 编程语言编写的智能合约存在于以太坊区块链中。这意味着,如果您将智能合约部署到以太坊生产区块链,智能合约的字节码将写入每个以太坊节点。因此,如果我们在这个世界上有 10000 个以太坊节点,那么您的智能合约将复制 10000 次。

然而,分散的应用并不存在于以太坊区块链中。它存在于你的计算机、邻居的计算机、云中,但它不存在于区块链上,也不必像智能合约一样在全世界复制。人们使用各种编程语言构建分散的应用。就以太坊而言,构建去中心应用最流行的编程语言是 Node.js 环境中的 Javascript 和 Python。在本例中,我们将使用 Python 构建一个分散的应用。为此,我们需要一个图书馆。对于 Javascript,我们需要一个web3.js库。在我们的例子中,就是 Python,我们需要一个web3.py库。所有库名称都包含单词 web3。

人们喜欢将 web3 视为互联网的第三个版本:分散的互联网。那么,如果这是第三个版本,你会问第一个和第二个版本是什么?互联网的第一个版本是你用来被动消费内容的互联网(想想静态网站)。互联网的第二个版本是社交网站,你可以在其中生成内容并共同创造体验(想想 Facebook、Twitter 或 Instagram):

在前面的屏幕截图中,我们可以看到 Vyper 或 Solidity 字节码在许多以太坊节点(系统)中存在(复制)。但是使用web3库的程序可以在一台计算机上运行(如笔记本电脑或智能手机)。

不用麻烦了,让我们安装web3库。使用 Python 3.6 创建虚拟环境,如下所示:

$ virtualenv -p python3.6 web3-venv

激活虚拟环境并安装 Vyper,如下所示:

$ source web3-venv/bin/activate
(web3-venv) $ pip install vyper

然后,使用pip安装web3.py库:

(vyper-venv) $ pip install web3

现在,按如下方式验证它是否工作:

(vyper-venv) $ python
>>> import web3
>>> web3.__version__
'4.8.2'

如果您没有遇到任何错误,它就会工作。让我们用web3连接到 Ganache 区块链。为此,首先启动 Ganache,然后返回 Python 命令提示符:

>>> from web3 import Web3, HTTPProvider
>>> w3 = Web3(HTTPProvider('http://localhost:7545'))
>>> w3.eth.blockNumber
0
>>> w3.eth.getBlock('latest')
AttributeDict({'number': 0, 'hash': HexBytes('0x0bbde277e2147d93f12852a370e70e2efe9c66f45db6e80e0cba584508d3ebac'), 'parentHash': HexBytes('0x0000000000000000000000000000000000000000000000000000000000000000'), 'mixHash': 
...
...
HexBytes('0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421'), 'stateRoot': HexBytes('0x31740a2d8b535c624aa481ba7d6d696085438037246b7501b4f24f77f94f3994'), 'receiptsRoot': HexBytes('0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421'), 'miner': '0x0000000000000000000000000000000000000000', 'difficulty': 0, 'totalDifficulty': 0, 'extraData': HexBytes('0x'), 'size': 1000, 'gasLimit': 6721975, 'gasUsed': 0, 'timestamp': 1548300279, 'transactions': [], 'uncles': []})

我们在这里所做的是通过web3库连接到 Ganache 区块链。我们可以检索区块链上的信息,例如在该特定区块链上开采了多少区块。因为我们正在使用开发区块链 Ganache,w3.eth.blockNumber返回0,因为我们没有在 Ganache 上创建任何交易。

Go 以太坊Geth是以太坊协议的一种实现,以 Go 编写。您可以使用 Geth 同步以太坊节点,甚至构建私有以太坊区块链。如果你想成为一名矿工,这是一个你会使用的软件。您的以太坊节点是网关,也是以太坊区块链的一部分。您的web3库程序要求以太坊节点能够与区块链内部的智能合约进行交互。

使用 Ganache 是非常好的。但 Ganache 是一个假的区块链。没有矿工,因此很难模拟我们在真正以太坊区块链上遇到的一些情况。因此,让我们加强我们的游戏。我们现在不需要使用以太坊生产区块链,但我们可以使用介于开发和生产区块链之间的东西Rinkeby 网络。如果以太坊生产区块链类似于生产服务器,则 Rinkeby 网络类似于暂存服务器。在 DevOps 通用语中,暂存服务器是一种尽可能模仿生产服务器的测试服务器。

所以,林克比不是像 Ganache 那样的软件。它生活在互联网上。因此,使用 Rinkeby 网络,我们可以感受到如何处理以太坊生产区块链。在 Rinkeby 网络的以太区块链中,您可能会遇到一种情况,即确认交易需要时间。在 Ganache 中,确认交易只需几秒钟。在 Rinkeby 网络中,确认交易可能需要 20-30 秒,甚至一分钟,因此您需要习惯它。当然,并非以太坊生产区块链中的所有内容都可以在 Rinkeby 网络上复制。以太坊生产区块链中具体发生的另一种情况是,它包括以太坊生产区块链有时会获得的高流量。例如,一个名为 Cryptokitties 的去中心应用减慢了以太坊网络的速度,因为有许多用户与该应用交互,如图所示:https://techcrunch.com/2017/12/03/people-have-spent-over-1m-buying-virtual-cats-on-the-ethereum-blockchain/

还有另一个以太坊测试网络类似于 Rinkeby 网络Ropsten 网络。不同之处在于,Rinkeby 网络使用授权证明PoA)确认交易,而 Ropsten 网络使用工作证明PoW)。现在,没有必要担心这种差异,因为使用 Rinkeby 网络类似于使用 Ropsten 网络。

有两种方法可以通过自己运行以太坊节点或使用其他人的节点连接到这种以太坊区块链。每种方法都有其优缺点。运行以太坊节点会占用大量存储空间。连接到 Rinkeby 网络的节点需要大约 6 GB 的存储空间。至于以太坊生产网络,它需要高达 150 GB 的存储空间。根据您的 internet 连接,要完全运行,您需要一晚或几天时间使节点与所有其他节点完全同步。

另一种方法是使用其他人的节点。有些人构建一个 web 服务来连接到他们的以太坊节点,因此您可以使用 API 连接到他们的以太坊节点。Infura 是最受欢迎的服务之一。您只需在他们的网站上注册即可获得 API。

要为 Rinkeby 网络运行我们自己的以太坊节点,请转至https://geth.ethereum.org/downloads/ 下载适用于您的操作系统的软件。对于 UbuntuLinux,这是tar.gz格式的,所以您必须提取它。然后,将二进制文件放在方便的地方(例如/opt/bin/home/yourusername/Program/user/local/bin

执行此操作后,按如下方式同步节点:

$ ./geth --rinkeby

您可以使用不同的数据目录。默认情况下,Geth 将数据存储在~/.ethereum目录中:

$ ./geth --rinkeby --datadir /opt/data/ethereumdata

就我而言,这需要一个晚上。您的体验可能会有所不同,具体取决于您的互联网连接速度。

如果完全同步(您知道输出不再经常更改时就是这种情况),那么您可以在web3-venv虚拟环境中运行 Python,如下所示:

(web3-venv) $ python
>>> from web3 import Web3, IPCProvider
>>> w3 = Web3(IPCProvider("/home/yourusername/.ethereum/rinkeby/geth.ipc"))

这里,我们使用的提供者与上一个示例中使用的提供者不同。在 Ganache 示例中,我们使用 HTTP 提供程序。记住,Ganache 使用http://localhost:7545,您在块菌配置中使用此信息。然而,在我们的例子中,当我们连接到以太坊节点时,我们使用进程间通信提供程序IPC。您还可以看到一个参数IPCProvider,它是一个文件路径。因此,Python 程序通过该文件与以太坊节点通信。在计算机科学中,该文件称为pipe。您只需在本地计算机上搜索geth.ipc文件所在的位置。记住geth.ipc只有在运行geth软件时才会显示。如果停止它,geth.ipc文件将消失。

然后,在正常运行业务之前,需要向 web3 中间件中注入一些东西。这是因为以太坊生产区块链中的区块大小不同于 Rinkeby 区块链中的区块大小:

>>> from web3.middleware import geth_poa_middleware
>>> w3.middleware_stack.inject(geth_poa_middleware, layer=0)

然后,您可以对其进行测试,如以下代码块所示:

>>> w3.eth.getBlock('latest')
AttributeDict({'difficulty': 2, 'proofOfAuthorityData': HexBytes('0xd883010813846765746888676f312e31312e32856c696e7578000000000000001c62ac5af9b2ea6bf897a99fff40af6474cd5680fc8239853f03db116b2154594d2ab77a6f18c41132ee819143d2d41819237468924d29cb4b1252d2385a862400'), 'gasLimit': 7000000, 'gasUsed': 1373640, 'hash': HexBytes('0xa14b569f874eefc75fe734bc28b7457755eff1da26794d6615f15e1739204067'), 'logsBloom': 
...
...
HexBytes('0x66e75c91271b45f5271d2fe2fd0efc66f48f641632e83a086fc57646a0c0bc3f'), 'uncles': []})

您得到的输出是关于 Rinkeby 网络中最新区块链块的信息。你可以从区块链区块中学到一些东西。您可以在该区块中找到所有已确认的交易;使用的气体、气体限制等。在 Rinkeby 网络中,miner 始终是零地址(0x0000000000000000000000000000000000000000,因为 Rinkeby 网络中的区块链使用权限证明。但在 mainnet(生产网络)中,您可以找到谁获得了奖励来确认区块链。您可以从中的 mainnet(以太坊生产网络)的最新区块中找到信息 https://etherscan.io/blocks 。当然,如果您愿意同步以太坊生产节点,您可以从该节点找到相同的信息。

在我们继续使用web3库之前,让我们先来看看 Geth 软件。Geth 软件可以像块菌控制台一样工作:

$ ./geth --rinkeby --verbosity 0 console

该语句中的关键字是console,但为了让它更愉快,您应该添加另一个--verbosity标志,其值为0。这将防止您因geth软件的大量输出而陷入困境:

Welcome to the Geth JavaScript console!
instance: Geth/v1.8.16-stable-477eb093/darwin-amd64/go1.11
modules: admin:1.0 clique:1.0 debug:1.0 eth:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0
>

在 Geth 控制台中,您可以执行在 Truffle 控制台中可以执行的任何操作。但是,我们现在想要创建一个以太坊帐户。当您启动 Ganache 时,您将拥有 10 个可供使用的帐户。然而,Rinkeby 区块链并非如此。您需要在 Rinkeby 中手动创建帐户:

> personal.newAccount("password123")
"0x28f5b56b035da966afa609f65fd8f7d71ff68327"

这是创建新以太坊帐户的命令。您需要提供密码才能在 Geth 控制台中创建帐户。不要忘记此帐户的密码,因为没有恢复密码的选项。此命令的输出是您帐户的公共地址。

私钥在以下目录中的文件中加密:/home/yourusername/.geth/rinkeby/keystore

文件名如下:UTC—2018-10-12T09-30-20.687898000Z—28f5b56b035da966afa609f65fd8f7d71ff68327。这是时间戳和公钥的组合。您可以打开它,但无法在其中找到私钥:

{"address":"28f5b56b035da966afa609f65fd8f7d71ff68327","crypto":{"cipher":"aes-128-ctr","ciphertext":"38b091f59f879369a6afdd91f21c1a82deb59374677144c94dd529d3c9069d39","cipherparams":{"iv":"b168482d467df6e1fe4bdb5201a64a6a"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"bd94440d3f2bb9313a0020331bac9410ff3cdc9f32756f41f72dde1ef7bf32e1"},"mac":"3313b72603e85e73f84a47ef7ed0e931db85441e1702e0d96f2f001c54170cb6"},"id":"7a033367-92fe-42d3-bec5-970076f35d8a","version":3}

要解密它,您可以使用web3库。将脚本命名为extract_private_key.py

from web3 import Web3
w3 = Web3()

# Change the filepath to your keystore's filepath
with open('/opt/data/ethereumdata/keystore/UTC--2018-10-12T09-30-20.687898000Z--28f5b56b035da966afa609f65fd8f7d71ff68327') as keyfile:
    encrypted_key = keyfile.read()
    private_key = w3.eth.account.decrypt(encrypted_key, 'password123')
    print(private_key)

如果执行脚本,您将看到您的私钥,该私钥可用于其他情况:

(web3-venv) $ python extract_private_key.py
b'\xa0\xe2\xa2\xf0$j\xe9L\xb3\xc0\x14Q\xb0D\xec\xa16\xa1\xca\xdd\x07.\x0f\x0f=5\xbd\xc5mb(r'

Please do not use this account in a production setting any more because the private key is already exposed. Use it for development purposes only.

此私钥为bytes格式。如果要将其转换为十六进制字符串,可以如下操作:

(web3-venv) $ python
>>>  b'\xa0\xe2\xa2\xf0$j\xe9L\xb3\xc0\x14Q\xb0D\xec\xa16\xa1\xca\xdd\x07.\x0f\x0f=5\xbd\xc5mb(r'.hex()
'a0e2a2f0246ae94cb3c01451b044eca136a1cadd072e0f0f3d35bdc56d622872'

现在,让我们用这个库编写一个分散的应用。最简单的去中心应用脚本是从一个帐户向另一个帐户汇款。将脚本命名为send_money_ganache.py

from web3 import Web3, HTTPProvider

w3 = Web3(HTTPProvider('http://localhost:7545'))

private_key = '59e31694256f71b8d181f47fc67914798c4b96990e835fc1407bf4673ead30e2'

transaction = {
  'to': Web3.toChecksumAddress('0x9049386D4d5808e0Cd9e294F2aA3d70F01Fbf0C5'),
  'value': w3.toWei('1', 'ether'),
  'gas': 100000,
  'gasPrice': w3.toWei('1', 'gwei'),
  'nonce': 0
}

signed = w3.eth.account.signTransaction(transaction, private_key)
tx = w3.eth.sendRawTransaction(signed.rawTransaction)

在执行此脚本之前,首先启动 Ganache。完成此操作后,将您喜欢的任何公共地址放入事务字典的to字段中。此帐户将成为接收者。然后找到另一个帐户,查看其私钥,并在private_key变量中输入值:

value字段中放置一个乙醚。这意味着您要将 1 以太发送到另一个帐户。这是一个简单的脚本,用于说明如何发送事务。它没有显示最佳实践,因为您不应该像这样在代码中嵌入私钥。例如,您可以使用受限权限从文件中读取私钥,也可以从标准输入请求私钥。

如果执行此脚本,您会注意到接收方的余额将增加 1 ETH,而发送方的余额将减少 1 ETH:

以下是以下输出:

如你所知,以太坊区块链不是免费的;必须有人来维护它。我说的不是编写以太坊软件的开发人员,而是运行以太坊节点以确认事务的矿工。他们从以太坊软件本身获得金钱(ETH)作为回报。除此之外,他们还获得交易费用作为回报。这个费用是汽油和汽油的价格。

在以太坊软件中,我们为什么需要在奖励之外再加一笔费用?这是为了防止来自用户的垃圾邮件。如果一笔交易是免费的,一个骗子可以建立两个账户,在他们之间来回汇款。此外,这为那些想支付更多费用的用户提供了高优先级。如果有两个类似的交易,但第一个交易有更多的天然气作为后盾,那么在矿业公司的待办事项列表中,它将具有更高的优先级。最终确认燃气量较少的交易;只需要再等一会儿。

所以有天然气,也有天然气价格。Gas 是您愿意在此交易中分配的气体量。在上一个脚本中,您分配 20000 gas 来创建一个事务以发送资金。对于更复杂的事务,例如在智能合约中执行复杂的方法,可能需要更多的 gas。如果你没有分配足够的汽油,你的交易将被拒绝,你也可能失去汽油。但是如果你投入的汽油足够多,如果你的交易成功,剩余的汽油会退还给你。所以,你会想:为什么不尽可能多地把汽油放在一边呢?有一个陷阱。如果您在智能合约中调用某个方法时,由于某种原因导致断言失败(例如:assert 1==2),则您将丢失在断言行之前所用的所有气体(但剩余的气体将被退还)。所以你需要采取中间立场。

煤气价格是煤气的价格,所以煤气不是免费的。它与以太坊本身是分离的。你们用现有的汽油购买汽油。您可以查看历史交易中的天然气价格。在以太坊生产区块链中,您可以查看中的天然气价格 https://www.ethgasstation.info/

您如何估计交易所需的汽油?您需要学习 Solidity 或 Vyper 编程语言的所有复杂性。如果我分配一个 256 位的整数变量并将其存储在存储器中,需要多少钱?循环呢?构造一个结构怎么样?这听起来很复杂,但幸运的是,web3图书馆有一种估算天然气使用量的方法。首先,创建一个名为estimate_gas.py的脚本:

from web3 import Web3, HTTPProvider

w3 = Web3(HTTPProvider('http://localhost:7545'))

transaction = {
  'to': Web3.toChecksumAddress('0x9049386D4d5808e0Cd9e294F2aA3d70F01Fbf0C5'),
  'value': w3.toWei('1', 'ether'),
  'gas': 100000,
  'gasPrice': w3.toWei('1', 'gwei'),
  'nonce': 0
}

print("Estimating gas usage: " + str(w3.eth.estimateGas(transaction)))
print("Gas price: " + str(w3.eth.gasPrice))

您将获得以下输出:

Estimating gas usage: 21000
Gas price: 2000000000

如果发现与 nonce 相关的错误,请将 nonce 更改为 1 或更高,直到其正常工作。我们将在下一节讨论 nonce。

您可能已经注意到,如果多次尝试执行“发送资金”脚本,就会出现与 nonce 相关的错误。如果你没有,试试看。您必须增加 nonce 以使其再次工作。Nonce 就像一个指示器,指示您与一个帐户进行了多少交易。对于第一笔交易(创建新帐户之后),您在 nonce 中输入零值。然后,对于第二个事务,在 nonce 中输入值 1。然后,对于第三个事务,在 nonce 中输入值 2。

但是跟踪 nonce 值变得很愚蠢,特别是如果你想使用一个旧帐户,而你不知道 nonce 值有多高。幸运的是,有一种方法可以从以太坊区块链获取最新的 nonce 值。创建一个名为get_latest_nonce.py的脚本:

from web3 import Web3, HTTPProvider
w3 = Web3(HTTPProvider('http://localhost:7545'))
transaction_count = w3.eth.getTransactionCount("0xcc6d61988CdcF6eB510BffAeD4FC0d904f8d3e7D")
print(transaction_count)

尝试使用更高的 nonce 再次发送货币,然后执行此脚本。再做一次。这将向您显示数值。

有了这些新知识,您必须小心在以太坊区块链中创建交易。在现实世界中,确认交易可能需要时间。假设您使用相同的 nonce 并行创建两个不同的事务。如果两项交易均有效,则以太坊区块链上只记录其中一项交易。另一个将因暂时问题而被拒绝。这两项交易中的哪一项将被确认?这将是相当随机的。

相反,您必须决定要首先确认的交易。给它一个较低的时间。对于第二个事务,您给予更高的 nonce。但是,如果第一个事务失败,第二个事务将被拒绝,因为 nonce 太高(nonce 跳过一个数字)。这是你需要记住的事情。

那么,为什么会有这些临时官僚机构呢?这是为了防止相同的事务再次发生。假设您广播了一个事务,其中您向您的邻居发送了一个 ETH。我可以复制此事务并再次广播它。因为这笔交易是用你的签名验证的,所以我可能会耗尽你的帐户。

既然您对 Ganache 很感兴趣,那么让我们尝试在 Rinkeby 网络上创建一个事务。你可能会注意到这里有一个问题,不像 Ganache,你有 10 个账户,每个账户有 100 个 ETH 余额,但在这里你什么也得不到。默认情况下,您必须创建一个帐户。创建 10 个帐户很容易。但是余额呢?您使用 Geth 软件在 Rinkeby 网络上创建的每个帐户的余额均为 0 ETH。但是汇款需要钱来支付交易费。

在以太坊生产区块链中,您可以通过加密货币兑换或采矿获得以太坊。但在 Rinkeby 网络中,你通过乞讨获得金钱。下面是如何做到这一点。

转到https://faucet.rinkeby.io/ ,然后,使用其中一个社交媒体平台,如 Twitter、Google+或 Facebook,您可以在 Rinkeby 网络中创建包含您的公共地址的帖子。然后,在 Rinkeby 水龙头网站上以表格的形式发布您的社交媒体帖子。您有三种选择:8 小时内 3 个以太,1 天内 7.5 个以太,或 3 天内 18.5 个以太:

为了确保获得平衡,您可以从 Geth 软件检查平衡。先同步它。正如我之前所说,这一过程可能需要很长时间,可能需要几个小时,在我的情况下可能需要一个晚上:

$ ./geth --rinkeby

在您的本地区块链节点与 Rinkeby 网络完全同步后,首先终止geth进程,然后再次启动geth,但使用不同的标志:

$ ./geth --rinkeby --verbosity 0 console

geth控制台内执行此命令:

> web3.eth.getBalance('0x28f5b56b035da966afa609f65fd8f7d71ff68327')
3000000000000000000

把这个地址改成你的地址。你应该从水龙头里拿些水。

假设您已经拥有 ETH,您可以在 Rinkeby 网络中创建事务。下面是在林克比网络中发送以太的脚本。完整代码可参考以下 GitLab 链接上的代码文件:https://gitlab.com/arjunaskykok/hands-on-blockchain-for-python-developers/blob/master/chapter_04/send_money_rinkeby.py

from web3 import Web3, IPCProvider
from web3.middleware import geth_poa_middleware

# Change the path of geth.ipc according to your situation.
w3 = Web3(IPCProvider('/opt/data/ethereumdata/geth.ipc'))

w3.middleware_stack.inject(geth_poa_middleware, layer=0)

...
...

nonce = w3.eth.getTransactionCount(Web3.toChecksumAddress(from_account))

transaction = {
  'to': Web3.toChecksumAddress(to_account),
  'value': w3.toWei('1', 'ether'),
  'gas': 21000,
  'gasPrice': w3.toWei('2', 'gwei'),
  'nonce': nonce
}

signed = w3.eth.account.signTransaction(transaction, private_key)
w3.eth.sendRawTransaction(signed.rawTransaction)

根据您的情况更改帐户接收人的地址、您的私钥加密文件位置、您的密码和geth.ipc文件位置。

请记住,我们的私钥是在文件中加密的。因此,我们读取该文件,然后用密码将其解锁。请记住,不应将密码直接嵌入代码中。等待几分钟后,您可以在geth控制台中查看您的目的地账户余额:

> web3.eth.getBalance('0x99fb2eee85acbf878d4154de73d5fb1b7e88c328')
100000000000000000

通过使用私钥对事务进行签名来发送事务。这是在以太坊中创建事务的最通用的方法。但还有另一种方法,只需要使用密码。

您可以像这样使用私钥:

signed = w3.eth.account.signTransaction(transaction, private_key)
w3.eth.sendRawTransaction(signed.rawTransaction)

或者,您可以在签署交易时使用密码,如下所示:

w3.personal.sendTransaction(transaction, password)

只有在控制节点时才能使用密码,因为它需要加密的私钥文件。我在以太坊节点中创建了几个帐户。我只能在为这些帐户签署交易时使用密码。但是有了私钥,我可以使用任何帐户。

您已经使用 Python 脚本和 Ganache 中的web3库以及 Rinkeby 网络发送了以太。现在,让我们创建一个与智能合约交互的脚本。但在此之前,您需要学习如何使用geth启动智能合约,以及如何使用web3库启动 Python 脚本。之前,在第 3 章使用 Vyper实现智能合约,您使用块菌启动了智能合约。

在下一节中,我们将与web3连接到智能合约。以下是如何将智能合约部署到 Rinkeby 区块链:

$ ./geth --rinkeby --verbosity 0 console

geth控制台中,列出您使用 Geth 软件的所有帐户:

> eth.accounts
["0x8b55f0a88a1c53a8976953cde4f141752e847a00", "0x1db565576054af728b46ada9814b1452dd2b7e66", "0x28f5b56b035da966afa609f65fd8f7d71ff68327", "0x5b0d65b07a61c7b760bf372bbec1b3894d4b0225", "0x99fb2eee85acbf878d4154de73d5fb1b7e88c328"]

所有这些帐户都来自您使用以下命令创建的密钥库文件:personal.newAccount("password")。假设您要解锁第一个账户,则可以使用personal.unlockAccount方法:

> personal.unlockAccount(eth.accounts[0], "password123")
true

现在,获取bytecode并将其放入变量中。请记住,当您使用 Vyper 编译器编译源代码时,您会得到bytecode

> bytecode = "smart contract bytecode"
> tx = eth.sendTransaction({from: eth.accounts[0], data: bytecode, gas: 500e3}

然后,检查您的智能合约是否已在区块链上确认:

> web3.eth.getTransactionReceipt(tx)

如果已确认,则应获得以下输出:

{
 blockHash: "0xfed7dcbd5e8c68e17bff9f42cd30d95588674497ae719a04fd6a2ff219bb001d",
 blockNumber: 2534930,
 contractAddress: "0xbd3ffb07250634ba413e782002e8f880155007c8",
 cumulativeGasUsed: 1071323,
 from: "0x1db565576054af728b46ada9814b1452dd2b7e66",
 gasUsed: 458542,
 logs: [],
 logsBloom: "0x00000...",
 status: "0x1",
 to: null,
 transactionHash: "0x1a341c613c2f03a9bba32be3c8652b2d5a1e93f612308978bbff77ce05ab02c7",
 transactionIndex: 4
}

您还可以使用带有web3库的 Python 脚本启动智能合约。将此脚本命名为deploy_smart_contract_to_ganache.py。您可以参考以下 GitLab 链接上的代码文件以获取完整代码,https://gitlab.com/arjunaskykok/hands-on-blockchain-for-python-developers/blob/master/chapter_04/deploy_smart_contract_to_ganache.py

from web3 import Web3, HTTPProvider
from vyper import compile_codes

contract_source_code = '''
name: public(bytes[24])

@public
def __init__():
    self.name = "Satoshi Nakamoto"

...
...

# Change the account to your situation.
tx_hash = HelloSmartContract.constructor().transact({'from': '0xb105F01Ce341Ef9282dc2201BDfdA2c26903da77'})

tx_receipt = w3.eth.waitForTransactionReceipt(tx_hash)
print(tx_receipt)

运行脚本。但要确保你有 Ganache 在运行。您应该获得以下输出:

AttributeDict({'transactionHash': HexBytes('0xcfce0a28d0f8232735f99bcf871762f9780f19ab916e92c03d32fdabfd6b9e9a'), 'transactionIndex': 0, 'blockHash': HexBytes('0x84139a5c9ad050cf7be0678feb4aefc9e8b2806636245f16c790048e50347dfe'), 'blockNumber': 1, 'from': '0xb105f01ce341ef9282dc2201bdfda2c26903da77', 'to': None, 'gasUsed': 339198, 'cumulativeGasUsed': 339198, 'contractAddress': '0x9Dc44aa8d05c86388E647F954D00CaA858837804', 'logs': [], 'status': 1, 'logsBloom': HexBytes('0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'), 'v': '0x1c', 'r': '0x74c63921055bd2fed65a731356b30220c6de3a28ec5fd26e296bf609d76d25ce', 's': '0x655395f422fa7b419caf87f99e2da09296b123eceb99aed4d19195e542b01bcd'})

首先,使用以下语句创建智能合约对象:

HelloSmartContract = w3.eth.contract(abi=abi, bytecode=bytecode)

然后,为了部署智能合约,您只需使用constructor方法:

tx_hash = HelloSmartContract.constructor().transact({'from': '0xb105F01Ce341Ef9282dc2201BDfdA2c26903da77'})

最后一步非常重要,那就是等待交易得到确认。这很重要,因为您需要在交易确认后获取智能合约的地址。

如果要将智能合约部署到 Rinkeby 网络,则需要修改此脚本。创建一个名为deploy_smart_contract_to_rinkeby.py的新文件。完整代码可参考以下 GitLab 链接上的代码文件:https://gitlab.com/arjunaskykok/hands-on-blockchain-for-python-developers/blob/master/chapter_04/deploy_smart_contract_to_rinkeby.py

from web3 import Web3, IPCProvider
from vyper import compile_codes

contract_source_code = '''
name: public(bytes[24])

...
...

signed = w3.eth.account.signTransaction(transaction, private_key)
tx_hash = w3.eth.sendRawTransaction(signed.rawTransaction)

tx_receipt = w3.eth.waitForTransactionReceipt(tx_hash)
print(tx_receipt)

将智能合约部署到 Rinkeby 网络时的区别(除了使用IPCProvider而不是HTTPProvider之外)是您还必须设置gasgasPricenonce。除此之外,您还可以使用buildTransaction方法,获取transaction对象,并使用私钥对其进行签名。由于此脚本在 Rinkeby 网络中运行,因此需要确保已分配足够的天然气。在天然气不足的情况下,将智能合约部署到 Rinkeby 网络是一个常见的错误。然后,开发人员对为什么智能合约不能在之后访问感到困惑。将此智能合约部署到 Rinkeby 网络时,需要等待一段时间

在上一章中,我们使用名为hello.vy的 Vyper 开发了一个简单的智能合约。让我们使用web3创建一个脚本来与这个智能合约交互。如果您忘记了hello.vy的内容,以下是该文件的内容:

name: public(bytes[24])

@public
def __init__():
    self.name = "Satoshi Nakamoto"

@public
def change_name(new_name: bytes[24]):
    self.name = new_name

@public
def say_hello() -> bytes[32]:
    return concat("Hello, ", self.name)

编译并部署到 Ganache 或 Rinkeby 网络。现在,根据您是想连接到 Ganache 还是 Rinkeby 中的智能合约,选择以下选项之一。

第一个脚本用于与 Rinkeby 网络中的智能合约交互。将脚本命名为play_with_smart_contract_in_rinkeby.py,完整代码可参考以下 GitLab 链接上的代码文件:https://gitlab.com/arjunaskykok/hands-on-blockchain-for-python-developers/blob/master/chapter_04/play_with_smart_contract_in_rinkeby.py

from web3 import Web3, IPCProvider
from vyper import compile_codes

contract_source_code = '''
name: public(bytes[24])

...
...

signed_txn_hash = w3.eth.sendRawTransaction(signed_txn.rawTransaction)

w3.eth.waitForTransactionReceipt(signed_txn_hash)

print(Hello.functions.say_hello().call())

第二个脚本是与 Ganache 中的智能合约交互。将脚本命名为play_with_smart_contract_in_ganache.py,完整代码可参考以下 GitLab 链接上的代码文件:https://gitlab.com/arjunaskykok/hands-on-blockchain-for-python-developers/blob/master/chapter_04/play_with_smart_contract_in_ganache.py

from web3 import Web3, HTTPProvider
from vyper import compile_codes

contract_source_code = '''
name: public(bytes[24])

...
...

signed_txn_hash = w3.eth.sendRawTransaction(signed_txn.rawTransaction)

w3.eth.waitForTransactionReceipt(signed_txn_hash)

print(Hello.functions.say_hello().call())

为了更好地理解这个概念,我们将逐行讨论代码:

from web3 import Web3, IPCProvider
from vyper import compile_codes

contract_source_code = '''
name: public(bytes[24])

...
...

smart_contract = {}
smart_contract['hello'] = contract_source_code

format = ['abi', 'bytecode']
compiled_code = compile_codes(smart_contract, format, 'dict')

abi = compiled_code['hello']['abi']

这部分脚本旨在获取智能合约的abibytecode

# Change the path of geth.ipc according to your situation.
w3 = Web3(IPCProvider('/opt/data/ethereumdata/geth.ipc'))

from web3.middleware import geth_poa_middleware
w3.middleware_stack.inject(geth_poa_middleware, layer=0)

# Change the address of the smart contract, the account, the password, and the path to the keystore according to your situation,
address = "0x58705EBBc791DB917c7771FdA6175b2D9F59D51A"
password = 'password123'
w3.eth.defaultAccount = '0x28f5b56b035da966afa609f65fd8f7d71ff68327'
with open('/opt/data/ethereumdata/keystore/UTC--2018-10-12T09-30-20.687898000Z--28f5b56b035da966afa609f65fd8f7d71ff68327') as keyfile:
    encrypted_key = keyfile.read()
    private_key = w3.eth.account.decrypt(encrypted_key, password)

Hello = w3.eth.contract(address=address, abi=abi)

此脚本用于 Rinkeby 网络选项。您将获得web3连接对象和私钥。然后,根据abi和部署智能合约脚本中的地址初始化智能合约对象:

w3 = Web3(HTTPProvider('http://localhost:7545'))

# Change the address of the smart contract, the private key, and the account according to your situation
address = "0x9Dc44aa8d05c86388E647F954D00CaA858837804"
private_key = '0x1a369cedacf0bf2f5fd16b5215527e8c8767cbd761ebefa28d9df0d389c60b6e'
w3.eth.defaultAccount = '0xb105F01Ce341Ef9282dc2201BDfdA2c26903da77'

Hello = w3.eth.contract(address=address, abi=abi)

此脚本用于 Ganache 选项。获取web3连接对象并设置私钥。然后,根据部署智能合约脚本中的abiaddress初始化智能合约对象。

在这两个(Ganache 和 Rinkeby)脚本中,您都将值设置为w3.eth.defaultAccount。如果您将地址设置为w3.eth.defaultAccount,这意味着该地址是要广播事务的帐户。它还将执行智能合约中的方法。如果您还记得您在 Truffle console 中所做的操作,那么您使用from参数指定了将在智能合约中执行方法的帐户,如下所示:

Donation.at("0x3e9417399786347b6ab38f59d3f00829d6bba7b8").change_useless_variable("sky is blue", {from: "0xb105F01Ce341Ef9282dc2201BDfdA2c26903da77" });

如果不使用默认帐户,则在生成事务时也需要指定from字段:

print(Hello.functions.name().call())

print(Hello.functions.say_hello().call())

要从不改变智能合约状态的公共变量或公共方法获取值,请使用合约对象的functions方法,然后是公共变量和公共方法(两者都必须使用()执行),然后执行call方法:

nonce = w3.eth.getTransactionCount(w3.eth.defaultAccount)

txn = Hello.functions.change_name(b"Vitalik Buterin").buildTransaction({
  'gas': 70000,
  'gasPrice': w3.toWei('1', 'gwei'),
  'nonce': nonce
})

如果您还记得如何使用 nonce,则有必要获取更新的 nonce。对于将要更改智能合约状态的交易,您使用buildTransaction而不是call,它提供您已经识别的参数:gasgasPricenonce。如果不使用w3.eth.defaultAccount,则需要在此处添加另一个参数:from。如果要将一些以太发送到智能合约(例如,捐赠智能合约中的donate方法),还可以添加另一个参数:value

如果您注意到,Rinkeby 网络脚本中的gasgasPrice参数要高得多:

txn = Hello.functions.change_name(b"Lionel Messi").buildTransaction({
        'gas': 500000,
        'gasPrice': w3.toWei('30', 'gwei'),
        'nonce': nonce
      })

在 Ganache,您可以获得 70000 汽油,而汽油价格设置为1 gwei。然而,在林克比网络中,你必须小心。为了安全起见,我在与 Rinkeby 网络中的智能合约进行交互时,提高了天然气和天然气价格。如果您未能更改 Rinkeby 网络中智能合约的状态,有时意味着您没有分配足够的天然气,且天然气价格不够高:

signed_txn = w3.eth.account.signTransaction(txn, private_key=private_key)

您使用私钥签署此交易。但是,在 Ganache 中,您不必这样做。相反,您可以不使用私钥直接进行交易:

Hello.functions.change_name(b"Vitalik Buterin").transact()

相比之下,对于 Rinkeby 网络或以太坊生产区块链,您必须签署您的交易:

signed_txn_hash = w3.eth.sendRawTransaction(signed_txn.rawTransaction)

然后,广播您的事务:

w3.eth.waitForTransactionReceipt(signed_txn_hash)

在 Ganache 中,该方法的执行速度非常快,但在 Rinkeby 中,可能需要几分钟。在适当的去中心应用中,您可以使用异步编程或线程来处理此问题:

print(Hello.functions.say_hello().call())

最后一行用于确保已更改智能合约中的name变量。

在本章中,您学习了如何安装web3库。这是设计用于连接智能合约的库。除此之外,您还学习了如何在 Rinkeby 网络上运行以太坊节点。您已将web3配置为连接到 Rinkeby 网络上的以太坊区块链。您还学习了如何告诉web3连接到以太坊测试网络,如 Ganache。此外,您还创建了一个脚本,用于将以太从一个帐户发送到另一个帐户。最后,您创建了一个脚本来执行智能合约上的方法,以读取公共变量的值或更改智能合约的状态。

在下一章中,您将使用一个名为Populus的智能合约开发框架,该框架负责您所从事的与智能合约相关的手动工作,例如编译代码和部署代码。此外,Populus 框架提供了一种测试智能合约的综合方法。

教程来源于Github,感谢apachecn大佬的无私奉献,致敬!

技术教程推荐

赵成的运维体系管理课 -〔赵成〕

机器学习40讲 -〔王天一〕

10x程序员工作法 -〔郑晔〕

消息队列高手课 -〔李玥〕

安全攻防技能30讲 -〔何为舟〕

如何讲好一堂课 -〔薛雨〕

React Native 新架构实战课 -〔蒋宏伟〕

大厂设计进阶实战课 -〔小乔〕

深入拆解消息队列47讲 -〔许文强〕