中间件
默认情况下,Web3 管理多层中间件。它们位于公共 Web3 方法和 供应商 之间,后者处理与以太坊客户端的本地通信。每一层都可以修改请求和/或响应。有些中间件是默认启用的,有些是可选的。
每个中间件层在请求到达供应商之前被调用,然后在供应商返回之后以相反的顺序处理结果。然而,中间件有可能在请求没有到达供应商(甚至没有到达更深层的中间件)的情况下就从调用中提前返回。
更多信息可在“内部: 中间件 ”部分获得。
## 默认中间件
如果您不提供任何中间件,默认情况下会添加一些中间件。默认值可能会定期更改,因此此列表可能不包括最新版本的默认值。您可以在web3/manager.py
的构造函数中找到最新的默认值
属性字典
web3.middleware.attrdict_middleware()
这个中间件将函数的输出从字典转换成支持点语法访问的AttributeDict
,就像除了eth.get_block('latest')['number']
之外的eth.get_block('latest').number
。
。eth 名称解析
web3.middleware.name_to_address_middleware()
这个中间件将以太坊名称服务(ENS)名称转换成该名称指向的地址。例如 w3.eth.send_transaction
会接受。“从”和“到”字段中的 eth 名称。
注意
该中间件仅在通过 mainnet(部署了 ENS 合约的地方)调用时转换 ENS 名称,对于所有其他情况,将导致一个InvalidAddress
错误
预言的
web3.middleware.pythonic_middleware()
这将在适当的时候将参数和返回值转换为 python 原语。例如,它将 RPC 调用eth_blockNumber
返回的原始十六进制字符串转换为int
。
燃气价格策略
web3.middleware.gas_price_strategy_middleware()
警告
燃气价格策略仅支持传统交易。伦敦分支引入了maxFeePerGas
和maxPriorityFeePerGas
交易参数,这些参数应该尽可能在gasPrice
上使用。
如果适用,并且已经设定了燃气价格策略,这会将燃气价格添加到交易中。参见 气价 API 了解气价是如何得出的信息。
缓冲气体估计
web3.middleware.buffered_gas_estimate_middleware()
如果gas
不在交易参数中,这将为交易增加一个气体估算。将 gas 设置为:min(w3.eth.estimate_gas + gas_buffer, gas_limit)
,其中 gas_buffer 默认值为 100,000 微
HTTPRequestRetry
web3.middleware.http_retry_request_middleware()
这个中间件是专门为 HTTPProvider 默认的,它重试返回以下错误的失败请求:ConnectionError
、HTTPError
、Timeout
、TooManyRedirects
。此外,有一个白名单,只允许重试某些方法,以便不重新发送事务,排除的方法有:eth_sendTransaction
、personal_signAndSendTransaction
、personal_sendTransaction
。 ## 配置中间件
中间件可以在运行时添加、删除、替换和清除。为了使这更容易,您可以命名中间件以供以后参考。或者,您可以使用对中间件本身的引用。
中间件订单
可以将中间件想象成一个洋葱,在洋葱的最外层发起 web3.py 请求,以太坊节点(如 geth 或奇偶校验)在洋葱的最内层接收并响应请求。这是一个(简化的)图表:
New request from web3.py
|
|
v
`````Layer 2```py```
``````py` ``````py`
````` | ```py`
```` v ```py`
``` ```py
`. ``````py``Layer 1``````py` `.`
`` ```` ```py`` .`
`. ``` | ```py `.`
.` ``` v ```py `.
`. `.` ``` .`
`` .` `Layer 0` `` .`
`` `. ```py`` ``````py . .`
`. `` ``` | ```py .` .
. `` `.` | `` . .
. `. `` JSON-RPC call .` . .`
. . `` | . `` .
`` . . v . . .
. .` . . . ``
. . . Ethereum node .` . .
. . . . . .
. `` `. | . . .
. . .` | .` . .
`. .` .` Response .` .` .
. . `.` | `.` `. .
`. . ``` | ```py` `. .
. `. ````` v ```py` `. ``
. .` ```Layer 0`` `` `.
. `. `.` `.
. `. | `.` `.
.` ```py | ``` .`
`. ```py v ```` `.`
`` ```py``` ````` .`
`` ```py``Layer 1````` `.`
```py ```
```py` | ```
```py`` v ````
```py``` `````
```py``````Layer 2```py```````py
|
v
Returned value in Web3.py
中间件在Web3.middleware_onion
维护。请参见下面的 API。
在列表中指定中间件,或者检索中间件列表时,按照最外层在前,最内层在后的顺序返回。在上面的例子中,这意味着w3.middleware_onion.middlewares
将按照[2, 1, 0]
的顺序返回中间件。
参见“内部构件: 中间件 ”深入了解中间件如何工作。
中间件堆栈 API
要在不同的层中添加或删除项目,请使用以下 API:
Web3.middleware_onion.add(*middleware*, *name=None*)
中间件将被添加到最外层。这意味着新的中间件将首先修改请求,最后修改响应。你可以选择用任何可散列的对象来命名它,通常是一个字符串。
>>> w3 = Web3(...)
>>> w3.middleware_onion.add(web3.middleware.pythonic_middleware)
# or
>>> w3.middleware_onion.add(web3.middleware.pythonic_middleware, 'pythonic')
Web3.middleware_onion.inject(*middleware*, *name=None*, *layer=None*)
将命名的中间件注入到任意层。
当前的实现只支持最内层或最外层的注入。注意,注入到最外层相当于调用 Web3.middleware_onion.add()
。
# Either of these will put the pythonic middleware at the innermost layer
>>> w3 = Web3(...)
>>> w3.middleware_onion.inject(web3.middleware.pythonic_middleware, layer=0)
# or
>>> w3.middleware_onion.inject(web3.middleware.pythonic_middleware, 'pythonic', layer=0)
Web3.middleware_onion.remove(*middleware*)
中间件将从它所在的层中移除。如果您添加了带有名称的中间件,请使用该名称将其删除。如果您将中间件作为对象添加,请稍后再次使用该对象将其删除:
>>> w3 = Web3(...)
>>> w3.middleware_onion.remove(web3.middleware.pythonic_middleware)
# or
>>> w3.middleware_onion.remove('pythonic')
Web3.middleware_onion.replace(*old_middleware*, *new_middleware*)
中间件将从它所在的任何一层被取代。如果中间件被命名,它将继续使用相同的名称。如果它是未命名的,那么您现在可以用新的中间件对象来引用它。
>>> from web3.middleware import pythonic_middleware, attrdict_middleware
>>> w3 = Web3(...)
>>> w3.middleware_onion.replace(pythonic_middleware, attrdict_middleware)
# this is now referenced by the new middleware object, so to remove it:
>>> w3.middleware_onion.remove(attrdict_middleware)
# or, if it was named
>>> w3.middleware_onion.replace('pythonic', attrdict_middleware)
# this is still referenced by the original name, so to remove it:
>>> w3.middleware_onion.remove('pythonic')
Web3.middleware_onion.clear()
清空所有的中间件,包括默认的。
>>> w3 = Web3(...)
>>> w3.middleware_onion.clear()
>>> assert len(w3.middleware_onion) == 0
Web3.middleware_onion.middlewares
以适当的顺序返回Web3
实例的所有当前中间件,以便导入到新的Web3
实例中。
>>> w3_1 = Web3(...)
# add uniquely named middleware:
>>> w3_1.middleware_onion.add(web3.middleware.pythonic_middleware, 'test_middleware')
# export middlewares from first w3 instance
>>> middlewares = w3_1.middleware_onion.middlewares
# import into second instance
>>> w3_2 = Web3(..., middlewares=middlewares)
>>> assert w3_1.middleware_onion.middlewares == w3_2.middleware_onion.middlewares
>>> assert w3_2.middleware_onion.get('test_middleware')
可选中间件
Web3 附带了非默认的中间件,供您自定义使用。除了 配置中间件 的其他方式,您可以在初始化 Web3 时指定一个中间件列表,用:
Web3(middlewares=[my_middleware1, my_middleware2])
警告
这将取代默认的中间件。要保留默认功能,要么使用上面的middleware_onion.add()
,要么将默认中间件添加到您的新中间件列表中。
下面是内置中间件列表,默认不启用。
样式检查
web3.middleware.make_stalecheck_middleware(*allowable_delay*)
这个中间件检查区块链有多旧,如果区块链太旧,就以失败中断调用。
allowable_delay
是允许区块链落后于time.time()
的长度,以秒为单位
因为这个中间件需要一个参数,所以您必须用一个方法调用来创建中间件。
two_day_stalecheck = make_stalecheck_middleware(60 * 60 * 24 * 2)
web3.middleware_onion.add(two_day_stalecheck)
在这个例子中,如果区块链中的最新块超过 2 天,那么中间件将在除了web3.eth.get_block()
之外的每个调用中引发StaleBlockchain
异常。
隐藏物
所有的缓存中间件都接受这些常见的参数。
cache_class
必须是一个 callable,它返回一个实现字典 API 的对象。rpc_whitelist
必须是可迭代的,最好是一组可以缓存的 RPC 方法。should_cache_fn
必须是带有签名fn(method, params, response)
的可调用函数,返回响应是否应该被缓存。
web3.middleware.construct_simple_cache_middleware(*cache_class*, *rpc_whitelist*, *should_cache_fn*)
构建一个中间件,它将缓存rpc_whitelist
中任何 RPC 方法的返回值。
可以在web3.middlewares.simple_cache_middleware
找到这个中间件的一个现成版本。
web3.middleware.construct_time_based_cache_middleware(*cache_class*, *cache_expire_seconds*, *rpc_whitelist*, *should_cache_fn*)
构建一个中间件,它将在cache_expire_seconds
定义的时间内缓存rpc_whitelist
中任何 RPC 方法的返回值。
cache_expire_seconds
应该是一个值在被逐出之前可以保留在缓存中的秒数。
可以在web3.middlewares.time_based_cache_middleware
找到这个中间件的一个现成版本。
web3.middleware.construct_latest_block_based_cache_middleware(*cache_class*, *average_block_time_sample_size*, *default_average_block_time*, *rpc_whitelist*, *should_cache_fn*)
构建一个中间件,它将缓存最新块的rpc_whitelist
中任何 RPC 方法的返回值。它通过跟踪当前平均块时间并仅在最后看到的最新块比平均块时间旧时请求新块,来避免为每个请求重新获取当前最新块。
average_block_time_sample_size
为确定平均阻塞时间而应取样的块的数量。default_average_block_time
初始平均阻塞时间值,用于没有足够的链历史来确定平均阻塞时间的情况。
可以在web3.middlewares.latest_block_based_cache_middleware
找到这个中间件的一个现成版本。
### 权威证明
注意
在中间件洋葱的第 0 层注入中间件很重要:w3 . middleware _ onion . inject(geth _ poa _ middleware,layer=0)
geth_poa_middleware
需要连接到geth --dev
或 Rinkeby 公共网络。它也可能需要其他 EVM 兼容的区块链,如多边形或 BNB 链(币安智能链)。
如果中间件不是在中间件洋葱的第 0 层注入的,那么在与您的 EVM 节点交互时,您可能会得到如下所示的错误。
web3.exceptions.ExtraDataLengthError: The field extraData is 97 bytes, but should be 32. It is quite likely that you are connected to a POA chain. Refer to http://web3py.readthedocs.io/en/stable/middleware.html#proof-of-authority for more details. The full extraData is: HexBytes('...')
连接到加载中间件的默认geth --dev
实例的最简单方法是:
>>> from web3.auto.gethdev import w3
# confirm that the connection succeeded
>>> w3.clientVersion
'Geth/v1.7.3-stable-4bb3c89d/linux-amd64/go1.9'
这个例子使用一个惟一的 IPC 位置连接到 Linux 上的一个本地geth --dev
实例,并加载中间件:
>>> from web3 import Web3, IPCProvider
# connect to the IPC location started with 'geth --dev --datadir ~/mynode'
>>> w3 = Web3(IPCProvider('~/mynode/geth.ipc'))
>>> from web3.middleware import geth_poa_middleware
# inject the poa compatibility middleware to the innermost layer (0th layer)
>>> w3.middleware_onion.inject(geth_poa_middleware, layer=0)
# confirm that the connection succeeded
>>> w3.clientVersion
'Geth/v1.7.3-stable-4bb3c89d/linux-amd64/go1.9'
为什么geth_poa_middleware
是必要的?
对于单一的权威证明(PoA)标准,目前还没有达成一致的意见。不过,有些节点已经成功地进行了实验。一个是 go-ethereum (geth),它使用原型 PoA 作为其开发模式和 Rinkeby 测试网络。
不幸的是,它偏离了黄皮书规范,黄皮书规范将每个块中的extraData
字段限制为最大 32 字节。Geth 的 PoA 使用超过 32 个字节,所以这个中间件在返回块数据之前对它进行了一点修改。 ### 本地管理的日志和块过滤器
这个中间件提供了以太坊节点管理过滤器的替代方案。使用时,日志和块过滤器逻辑在本地处理,同时使用相同的 web3 过滤器 api。使用不依赖于服务器状态的 JSON-RPC 端点来检索过滤器结果。
>>> from web3 import Web3, EthereumTesterProvider
>>> w3 = Web3(EthereumTesterProvider())
>>> from web3.middleware import local_filter_middleware
>>> w3.middleware_onion.add(local_filter_middleware)
# Normal block and log filter apis behave as before.
>>> block_filter = w3.eth.filter("latest")
>>> log_filter = myContract.events.myEvent.build_filter().deploy()
签署
web3.middleware.construct_sign_and_send_raw_middleware(*private_key_or_account*)
这个中间件自动捕获事务,对它们进行签名,并作为原始事务发送它们。事务上的from
字段或w3.eth.default_account
必须设置为这个中间件的私有密钥的地址才能生效。
T0】单个私钥或私钥元组、列表或集合.
密钥可以是以下任何格式:
- 一个物体
- 一个物体
- A raw private key is hexadecimal string or byte string.
>>> from web3 import Web3, EthereumTesterProvider
>>> w3 = Web3(EthereumTesterProvider)
>>> from web3.middleware import construct_sign_and_send_raw_middleware
>>> from eth_account import Account
>>> acct = Account.create('KEYSMASH FJAFJKLDSKF7JKFDJ 1530')
>>> w3.middleware_onion.add(construct_sign_and_send_raw_middleware(acct))
>>> w3.eth.default_account = acct.address
现在,您可以从 acct.address 发送事务,而不必构建和签署每个原始事务。
使用该签名中间件时,发送动态费用交易时(建议优先于遗留交易),需要2
(或'0x2'
)的交易type
。这是因为交易签名是基于交易type
参数进行验证的。当maxFeePerGas
和/或maxPriorityFeePerGas
作为参数出现在交易中时,该值默认为'0x2'
,因为这些参数意味着动态费用交易。由于这些值实际上取代了传统的gasPrice
值,因此不要为动态费用交易设置gasPrice
。这样做会导致验证问题。
# dynamic fee transaction, introduced by EIP-1559:
>>> dynamic_fee_transaction = {
... 'type': '0x2', # optional - defaults to '0x2' when dynamic fee transaction params are present
... 'from': acct.address, # optional if w3.eth.default_account was set with acct.address
... 'to': receiving_account_address,
... 'value': 22,
... 'maxFeePerGas': 2000000000, # required for dynamic fee transactions
... 'maxPriorityFeePerGas': 1000000000, # required for dynamic fee transactions
... }
>>> w3.eth.send_transaction(dynamic_fee_transaction)
遗留事务仍然以 EIP-1559 引入之前的方式工作:
>>> legacy_transaction = {
... 'to': receiving_account_address,
... 'value': 22,
... 'gasPrice': 123456, # optional - if not provided, gas_price_strategy (if exists) or eth_gasPrice is used
... }
>>> w3.eth.send_transaction(legacy_transaction)