通过 web3.py 用 Python 存取 Ethereum

共 4662字,需浏览 10分钟

 ·

2022-04-19 11:41


想要通过 Python 存取 Ethereum,从 Ethereum 官方的 Github 中可以看到有两种模块可以达成:web3.py 和 pyethereum 。就我目前的理解来说,两者的差别在于 web3.py 主要是作为外部存取 Ethereum 的客户端,也就是说 web3.py 函数库本身不会成为区块链节点,也不会进行区块链同步,而是连接一个区块链上的节点,把区块链当成是外部资料库一样取用而已;而 pyethereum 则比较像是 geth 那样,是用来把自己做成一个区块链节点,会正常进行区块同步,也可以作为矿工开始挖矿。

在本篇当中,因为是想要一个轻量级的客户端来与区块链互动,并不想要准备庞大的储存空间来存放区块链的资料,因此会以 web3.py 为主。

  • 配置 web3.py 执行环境

  • 通过 web3.py 连结 Ethereum 节点

  • 存取区块链上的 ERC20 合约

  • 签署并送出交易

配置 web3.py 执行环境

web3.py 可以直接通过 pip 安装。

  1. pip install web3

需注意的是,在 Windows 上想安装时,会需要事先安装 Visual C++ Builder,否则在安装的最后阶段会因为无法编译而失败。

通过 web3.py 连结 Ethereum 节点

web3.py 因为自身不会作为一个区块链的节点存在,因此它需要有一个节点用来存取区块链上的资料。一般来说最安全的方式应该是自己使用 geth 或者 parity 来自建节点,不过如果在不想要自建节点的状况时,可以考虑看看 infura 提供的 HTTP 节点服务。

以 infura 现在的 API 来说,如果要连结 Ropsten 测试链,连结的网址是 https://ropsten.infura.io/v3/api_key,其中 api_key 要去注册帐号才取得。以下的程序仿照了 web3.py 内建的 auto.infura 的作法,会从环境变数读取 INFURA_API_KEY 这个参数来组出 infura.io 的 HTTP 位址,用来建立跟 Ropsten 测试链的连线。

  1. import os


  2. from web3 import (

  3. HTTPProvider,

  4. Web3,

  5. )


  6. INFURA_ROPSTEN_BASE_URL = 'https://ropsten.infura.io/v3'


  7. def load_infura_url():

  8. key = os.environ.get('INFURA_API_KEY', '')

  9. return "%s/%s" % (INFURA_ROPSTEN_BASE_URL, key)


  10. w3 = Web3(HTTPProvider(load_infura_url()))

存取区块链上的 ERC20 合约

在开始存取合约之前,需要先谈谈什么是 ABI 。在 Ethereum 中,因为合约都是以编译过的 binary code 形式存在,因此其实函数库没办法直接知道合约传输的内容到底是什么,因为合约的回传值全都是 binary。因此在操作合约之前,需要提供一份 ABI 文件,告诉函数库如何使用合约。

  1. # Assume the contract we're going to invoke is a standard ERC20 contract.

  2. with open("erc20.abi.json") as f:

  3. erc20_abi = json.load(f)


  4. # Web3 accept only checksum address. So we should ensure the given address is a

  5. # checksum address before accessing the corresponding contract.

  6. contract_addr = w3.toChecksumAddress('0x4e470dc7321e84ca96fcaedd0c8abcebbaeb68c6');


  7. erc20_contract = w3.eth.contract(address=contract_addr, abi=erc20_abi)


  8. for func in erc20_contract.all_functions():

  9. logger.debug('contract functions: %s', func)


  10. logger.debug("Name of the token: %s", erc20_contract.functions.name().call())

这里假设我们想存取 Ropsten 测试链上位址是 0x4e470dc7321e84ca96fcaedd0c8abcebbaeb68c6 的智能合约。这个合约是透过 etherscan 随便找的某个 ERC20 的合约,因此可以用标准的 ERC20 的 ABI 来存取它。我们在建立这个合约的 instance 时,先跑一个回圈印出合约内所有的 function(这个步骤其实是在列出 ABI 上的信息),接着试着呼叫合约中的 name() 来取得这个合约宣告的代币名称。最后输出的内容如下:

  1. 2018-09-07 15:02:53,815 | __main__ | DEBUG | contract functions: <Function name()>

  2. 2018-09-07 15:02:53,816 | __main__ | DEBUG | contract functions: <Function approve(address,uint256)>

  3. 2018-09-07 15:02:53,824 | __main__ | DEBUG | contract functions: <Function totalSupply()>

  4. 2018-09-07 15:02:53,824 | __main__ | DEBUG | contract functions: <Function transferFrom(address,address,uint256)>

  5. 2018-09-07 15:02:53,824 | __main__ | DEBUG | contract functions: <Function decimals()>

  6. 2018-09-07 15:02:53,824 | __main__ | DEBUG | contract functions: <Function balanceOf(address)>

  7. 2018-09-07 15:02:53,824 | __main__ | DEBUG | contract functions: <Function symbol()>

  8. 2018-09-07 15:02:53,825 | __main__ | DEBUG | contract functions: <Function transfer(address,uint256)>

  9. 2018-09-07 15:02:53,825 | __main__ | DEBUG | contract functions: <Function allowance(address,address)>

  10. 2018-09-07 15:02:54,359 | __main__ | DEBUG | Name of the token: KyberNetwork

签署并送出交易

在上面的例子中,呼叫智能合约时是直接呼叫合约里的 function,但这一般只能用在读取区块链上的资料的状况。如果是想要通过呼叫智能合约来写入资料到区块链,就必须要用另一种方式来呼叫合约,也就是必须先签署交易,然后付 gas 去执行这个交易。

假设我们一样是要呼叫一个 ERC20 的合约,要执行合约上的 transferFrom() 这个函数。transferFrom() 需要三个参数 _from_to_value,表示要从 _from 帐号转帐给 _to 帐号,转帐金额是 _value

  1. # Set the account which makes the transaction.

  2. account = w3.toChecksumAddress(os.environ.get('ETHEREUM_ACCOUNT', ''))

  3. w3.eth.defaultAccount = account


  4. # Web3 accept only checksum address. So we should ensure the given address is a

  5. # checksum address before accessing the corresponding contract.

  6. contract_address = w3.toChecksumAddress('0x4e470dc7321e84ca96fcaedd0c8abcebbaeb68c6')

  7. contract = w3.eth.contract(address=contract_address, abi=contract_abi)


  8. # Prepare the necessary parameters for making a transaction on the blockchain.

  9. estimate_gas = contract.functions.transferFrom(account, account, w3.toWei('1', 'eth')).estimateGas()

  10. nonce = w3.eth.getTransactionCount(account)


  11. # Build the transaction.

  12. txn = contract.functions.transferFrom(account, account, w3.toWei('1', 'eth')).buildTransaction({

  13. 'chainId': 3,

  14. 'gas': estimate_gas,

  15. 'gasPrice': w3.toWei('1', 'gwei'),

  16. 'nonce': nonce

  17. })


  18. logger.debug('Transaction: %s', txn)


  19. # Sign the transaction.

  20. private_key = bytes.fromhex(os.environ.get('ETHEREUM_ACCOUNT_PKEY', ''))

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


  22. tx_hash = w3.eth.sendRawTransaction(signed_txn.rawTransaction)

  23. logger.debug('Txhash: 0x%s', bytes.hex(tx_hash))

在上面的程序中,首先第 2 ~ 3 行先从环境变量中读取我们要使用的帐号,这个帐号将会用来发送交易,当然要付 gas 时也会从这个帐号扣。第 10 ~ 20 行建立一个原始交易(raw transaction),这个交易中因为我们需要自行指定包括 gas、nonce 等参数,因此需要在前面 11 ~ 12 行确认参数要设定多少。然后最重要的第 25 ~ 26 行读取私钥,并且用私钥去签署交易。这里假设私钥的组成会是用 Hex 编码的文字,所以使用 bytes.fromhex 把 Hex 编码转回成 byte 格式。签好以后就送出交易,送出交易时 API 会回传 byte 格式的交易的 transaction hash,可以把它编码后印出来,之后就可以去 etherscan 上查找这笔交易了。

浏览 59
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报