众所周知,开发EOS交易机器人与WAX链游脚本,我们都需要调用eosio chain api:
https://developers.eos.io/manuals/eos/latest/nodeos/plugins/chain_api_plugin/api-reference/index
可以看到它全部基于HTTP协议,理论上我们可以直接使用HTTP客户端与其交互,比如python里面的【requests】包,但对于查询数据,这样直接发送HTTP请求尚可。但提交交易时,则涉及到解析chain info,打包交易和签名,自己从头发明轮子则过于麻烦,所以我们一般会使用eosio sdk来做这些事情。
可惜,eosio官方并未提供基于python语言实现的sdk,官方支持的最主流的sdk是基于javascript实现的
【eosjs】:https://github.com/EOSIO/eosjs
而基于python实现的sdk,之前我们主要使用第三方个人开发者开发的
【eospy】:https://github.com/eosnewyork/eospy
【pyeoskit】:https://github.com/learnforpractice/pyeoskit
另外还有一些小众的:
【ueosio】https://github.com/EOSArgentina/ueosio (太过于底层,使用起来麻烦)
【eosjs_python】https://github.com/EvaCoop/eosjs_python (用python和nodejs交互最终调用eosjs…)
不过在我们为WAX链游开发自动化脚本工具的过程中,经过深度使用,发现一些问题,上述工具还是不能很好的满足我们的需求。
【eospy】存在的问题:1.屏蔽了内部HTTP库的细节,不方便修改HTTP请求的各项参数。
【eospy】内部使用【requests】来发起HTTP请求,但并未暴露requests对象接口,如果我们需要修改HTTP请求的各项参数,则比较麻烦,比如设置http/socks5代理,修改http超时值,修改http请求头(比如"User-Agent")。
另外,【requests】默认会使用系统HTTP代理设置,如果你的电脑开了梯子之类的工具,【eospy】在发起交易时HTTP请求会走系统代理,而产生一些意外效果。当然我们可以修改【requests】的 session.trust_env = False 来让其不走系统代理。
上诉问题,我们都可以直接修改【eospy】的源码来满足我们的需求,但这样修修补补改出来的效果总感觉不是很安逸。
2.没有完善的会话隔离机制
会话隔离是什么意思呢,我们知道requests包可以用requests.Session()来创建一个对象,多个session对象之间互不干扰,可以设置不同的代理,设置不同的http请求头,设置不同的超时值等参数。同样,我们理想中的eosio sdk,也可以实例化一个session对象,每个session对象可以设置不同的代理,绑定不同的账号和私钥,使用不同的rpc节点。
这点在开发自动交易机器人和链游脚本时非常重要,因为一个程序往往要同时跑几十个几百个账号,每个账号都要设置不同的代理IP,甚至使用不同的rpc节点。
ce = eospy.cleos.Cleos(url="https://jungle3.greymass.com")
【eospy】虽然可以实例化session对象,但接下来两个需要发送HTTP请求的步骤:ce.abi_json_to_bin 和 ce.push_transaction ,多个session对象均使用同样的http配置,ce.push_transaction 还好,提供了proxies参数和 timeout参数,但ce.abi_json_to_bin并未提供proxies参数。当然这些小问题都可以通过简单修改【eospy】来解决。
3.错误处理
一个eosio自动交易机器人或链游脚本在运行的时候,往往会有三种错误。
① 一种是网络错误,比如网络超时,网络中断,代理问题
② 一种是节点错误,eos/wax节点拒绝服务,比如节点本身出故障,或者访问太频繁导致返回http 429 或 http 403拒绝服务
③ 一种是交易错误,即交易已提交,但因为CPU不足,余额不足,签名错误,权限问题,合约报错等原因,返回http 500错误
这三种错误在链游脚本中应该明确区分和处理,属于网络错误的,可以就地重试,属于节点错误的,调整访问频率或切换代理,属于交易错误的,不应该盲目重试。
然而,在【eospy】中,以上三种错误,【eospy】内部均抛出底层【requests】库的http异常,并未明确区分错误类别,处理起来稍麻烦,且不是很优雅。
4.交易没有序列化
下面使用【eospy】发起一个简单的交易:
import datetime
import eospy.cleos
import eospy.keys
import pytz
consumer_name = "consumer1111"
consumer_private_key = eospy.keys.EOSKey("5KWxgG4rPEXzHnRBaiVRCCE6WAfnqkRpTu1uHzJoQRzixqBB1k3")
ce = eospy.cleos.Cleos(url="https://jungle3.greymass.com")
def main():
action = {
"account": 'eosio.token',
"name": 'transfer',
"authorization": [
{
"actor": consumer_name,
"permission": "active",
},
],
"data": {
"from": consumer_name,
"to": "consumer2222",
"quantity": "0.0001 EOS",
"memo": "by eospy",
},
}
data = ce.abi_json_to_bin(action['account'], action['name'], action["data"])
action["data"] = data["binargs"]
tx = {
"actions": [action],
"expiration": str((datetime.datetime.utcnow() + datetime.timedelta(seconds=90)).replace(tzinfo=pytz.UTC))
}
resp = ce.push_transaction(tx, consumer_private_key)
print(resp)
if __name__ == '__main__':
main()
通过调试或抓包,我们可以看到最终【eospy】发出的HTTP请求正文是:
{
"compression":"none",
"transaction":{
"expiration":"2022-05-25T16:09:46.186449+00:00",
"ref_block_num":4469,
"ref_block_prefix":4235931364,
"net_usage_words":0,
"max_cpu_usage_ms":0,
"delay_sec":0,
"context_free_actions":[],
"actions":[
{
"account":"eosio.token",
"name":"transfer",
"authorization":[
{
"actor":"consumer1111",
"permission":"active"
}
],
"data":"10420857498d274520841057498d2745010000000000000004454f530000000008627920656f737079"
}
],
"transaction_extensions":[]
},
"signatures":[
"SIG_K1_Kei6azGcSWP61M5uVNU7s7HAizGnrP4Q9BA6j557XVmeWsFKGEkdNv1QtaHAP7JKzhCSRFaxq5HbX3dqkzVzMraKtnoLT3"
]
}
接下来使用【eosjs】发送一样的交易:
async function transfer() {
const consumer_name = "consumer1111";
const consumer_private_key = "5KWxgG4rPEXzHnRBaiVRCCE6WAfnqkRpTu1uHzJoQRzixqBB1k3";
const rpc = new eosjs_jsonrpc.JsonRpc("https://jungle3.greymass.com");
const provider = new eosjs_jssig.JsSignatureProvider([consumer_private_key]);
const api = new eosjs_api.Api({ rpc:rpc, signatureProvider: provider });
const result = await api.transact({
actions: [{
account: 'eosio.token',
name: 'transfer',
authorization: [
{
actor: consumer_name,
permission: "active",
},
],
data: {
from: consumer_name,
to: "consumer2222",
quantity: '0.0001 EOS',
memo: 'by eosjs',
},
}]
}, {
blocksBehind: 3,
expireSeconds: 90,
});
console.log(result)
}
通过调试或抓包,我们可以看到最终【eosjs】发出的HTTP请求正文是:
{
"signatures":[
"SIG_K1_KkKSHRez98XBv6EyQhgmiLtRUbM5WKTb72iHUFK7K9zf4cMHFDZQi8Kd4vttRHxjRYseMo1kQa7vKvvKbojkJHrqCF12bK"
],
"compression":0,
"packed_context_free_data":"",
"packed_trx":"00588e621619c0bc13a1000000000100a6823403ea3055000000572d3ccdcd0110420857498d274500000000a8ed32322910420857498d274520841057498d2745010000000000000004454f530000000008627920656f736a7300"
}
可以看到差异,【eospy】并未打包序列化交易本体,而【eosjs】将交易序列化为二进制数据后再发送。
虽然这两种方式,很多 eos 或 wax 的节点rpc服务端都能接受,但【eospy】这种做法明显是一种过时的方法,没有及时更新。
我们可以查看最新的eosio rpc 文档以证实这点:
https://developers.eos.io/manuals/eos/latest/nodeos/plugins/chain_api_plugin/api-reference/index#operation/push_transaction
目前最推荐的方式还是打包成 packed_trx 后再发送。
这样会带来两个问题:
1.有的公共 eos 或 wax 节点,不支持【eospy】这样的老数据格式,只支持最新的 packed_trx 方式,导致这些节点不能用。
2.如果本地没有私钥,需要通过服务端签名交易,比如 wax 云钱包,需要将交易打包成 packed_trx 后,post到 wax 云钱包服务端进行签名,才能push到wax网络。【eospy】不支持打包成packed_trx 的话,就比较麻烦了。
其实仔细研读【eospy】源码就会发现,其实【eospy】本身已经提供了打包交易packed_trx 所需的序列化函数,要解决该问题,我们也可以修改【eospy】源码,但修修补补改出来的代码总是不够优雅。
【pyeoskit】存在的问题:示例代码:
from pyeoskit import eosapi, wallet
consumer_name = "consumer1111"
consumer_private_key = "5KWxgG4rPEXzHnRBaiVRCCE6WAfnqkRpTu1uHzJoQRzixqBB1k3"
wallet.import_key(consumer_name, consumer_private_key)
eosapi.set_node("https://jungle3.greymass.com")
def main():
data = {
"from": consumer_name,
"to": "consumer2222",
"quantity": "0.0001 EOS",
"memo": "by pyeoskit",
}
authorization = {
consumer_name: "active"
}
action = ["eosio.token", "transfer", data, authorization]
resp = eosapi.push_action(*action)
print(resp)
if __name__ == '__main__':
main()
【pyeoskit】解决了【eospy】的很多痛点,可以将交易打包成 packed_trx 再发送,但它又带来了新的问题:
【pyeoskit】不是纯python实现的库,他在底层使用 golang 来实现交易的序列化以及签名,然后编译成本机代码给python调用,它的python代码部分只是一小层皮而已,这样做虽然可以提高运行效率,尤其是对二进制数据的处理, golang 实现会更快。但是依赖 golang 后,如果我们需要随时对代码进行修修补补,则比较麻烦。
信任问题,由于【pyeoskit】发布在【pypi】上的包,包含已经编译好的二进制可执行文件( golang 实现的部分),这部分是否和github上的开源代码保持一致,或插入恶意代码,我们不得而知,我们用来开发交易机器人,链游脚本,往往需要直接导入私钥,安全性方面一定要非常重视。当然我们可以从github上下载他的 golang 代码进行编译,不过搞起来略麻烦。
兼容性,不清楚该项目作者是如何编译的,但是他发布在【pypi】的包,其中的个别版本,在我的win10电脑上运行会crash。
BUG很多,深度使用下来就会发现这个库有很多bug,我们已经提交了几个issue让其修复,而且这个库内部比较混乱,他同时使用了三套HTTP库,【requests】【httpx】和 golang 自带的http库,这样在发送交易的时候会非常混乱,abi_json_to_bin的时候使用【httpx】,push_transaction 的时候又使用【requests】,容易踩坑。
同样存在【eospy】的 1、2、3 三个问题。
不过【pyeoskit】也有一些优点:
它支持从abi文件序列化data,并且内置了eosio常用的一些abi文件,这样在打包交易的时候,就不需要再发送 abi_json_to_bin 请求序列化 data 数据,降低了对eos节点的http请求压力。
它支持 abi 缓存,对于【pyeoskit】没有内置 abi 文件的智能合约,比如农民世界的【farmersworld】,它在第一次调用合约的时候会通过 /v1/chain/get_abi 获取该合约的 abi 保存在内存,在接下来的反复调用中,无需再调用 abi_json_to_bin 序列化 data ,降低了对eos节点的http请求压力。
它在功能上做的非常完善,比如在发送交易前还会调用 get_required_keys 检查签署交易所需的 key,以及本地的 key是否满足条件,避免发送签名不全的交易给服务端,在本地就做好检查抛出异常。又比如它实现了push_transaction的compression,即对序列化成二进制数据的 packed_trx 进一步压缩,减少传输体积。
总体来说【pyeoskit】还是一个很优秀很“现代”的 eosio sdk,毕竟它是最近一年才发布的,比起【eospy】这样的老古董来说,代码结构和设计理念更先进一些。唯一可惜的是它基本是 golang 写的,然后用python包装了一下,如果是纯 python 写的就完美了。至于bug多的问题,主要是用的人少,关注度低,测试不完善导致的,如果人气上来了,相信再多的bug都会很快被修复。
交流讨论欢迎分享,转载请注明来源:内存溢出
评论列表(0条)