【区块链】Python开发EOS机器人与WAX链游脚本常用工具

前言

众所周知,开发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 再发送,但它又带来了新的问题:

  1. 【pyeoskit】不是纯python实现的库,他在底层使用 golang 来实现交易的序列化以及签名,然后编译成本机代码给python调用,它的python代码部分只是一小层皮而已,这样做虽然可以提高运行效率,尤其是对二进制数据的处理, golang 实现会更快。但是依赖 golang 后,如果我们需要随时对代码进行修修补补,则比较麻烦。

  2. 信任问题,由于【pyeoskit】发布在【pypi】上的包,包含已经编译好的二进制可执行文件( golang 实现的部分),这部分是否和github上的开源代码保持一致,或插入恶意代码,我们不得而知,我们用来开发机器人,链游脚本,往往需要直接导入私钥,安全性方面一定要非常重视。当然我们可以从github上下载他的 golang 代码进行编译,不过搞起来略麻烦。

  3. 兼容性,不清楚该项目作者是如何编译的,但是他发布在【pypi】的包,其中的个别版本,在我的win10电脑上运行会crash。

  4. BUG很多,深度使用下来就会发现这个库有很多bug,我们已经提交了几个issue让其修复,而且这个库内部比较混乱,他同时使用了三套HTTP库,【requests】【httpx】和 golang 自带的http库,这样在发送交易的时候会非常混乱,abi_json_to_bin的时候使用【httpx】,push_transaction 的时候又使用【requests】,容易踩坑。

  5. 同样存在【eospy】的 1、2、3 三个问题。

不过【pyeoskit】也有一些优点:

  1. 它支持从abi文件序列化data,并且内置了eosio常用的一些abi文件,这样在打包交易的时候,就不需要再发送 abi_json_to_bin 请求序列化 data 数据,降低了对eos节点的http请求压力。

  2. 它支持 abi 缓存,对于【pyeoskit】没有内置 abi 文件的智能合约,比如农民世界的【farmersworld】,它在第一次调用合约的时候会通过 /v1/chain/get_abi 获取该合约的 abi 保存在内存,在接下来的反复调用中,无需再调用 abi_json_to_bin 序列化 data ,降低了对eos节点的http请求压力。

  3. 它在功能上做的非常完善,比如在发送交易前还会调用 get_required_keys 检查签署交易所需的 key,以及本地的 key是否满足条件,避免发送签名不全的交易给服务端,在本地就做好检查抛出异常。又比如它实现了push_transaction的compression,即对序列化成二进制数据的 packed_trx 进一步压缩,减少传输体积。

总体来说【pyeoskit】还是一个很优秀很“现代”的 eosio sdk,毕竟它是最近一年才发布的,比起【eospy】这样的老古董来说,代码结构和设计理念更先进一些。唯一可惜的是它基本是 golang 写的,然后用python包装了一下,如果是纯 python 写的就完美了。至于bug多的问题,主要是用的人少,关注度低,测试不完善导致的,如果人气上来了,相信再多的bug都会很快被修复。

交流讨论

猜你喜欢

转载自blog.csdn.net/CharlesSimonyi/article/details/124975726
今日推荐