区块链发布一个纯Python实现的EOSIO WAX SDK

Posted encoderlee

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了区块链发布一个纯Python实现的EOSIO WAX SDK相关的知识,希望对你有一定的参考价值。

回顾

《【区块链】Python开发WAX链游脚本常用工具》
在上一篇文章中,我们介绍了Python与EOS或WAX网络交互的两个第三方开源库【eospy】和【pyeoskit】。
不过经过深度使用,我们发现这两个库或多或少有一些不足,均无法完全满足我们的需求。

需求

整理一下,我们的需求是什么?我们使用这些库的主要目的,主要是用来开发WAX链游自动化脚本,和开发用户端DAPP的需求是有一些区别的。

比如【eosjs】常用于web客户端(浏览器),直接面向用户,用户往往同时只需登录一个账号,在人工交互下半自动的提交 transaction,无需处理代理问题,代理取决于浏览器设置。

而自动化机器人,往往需要在一个进程内管理上百个上千个账号,需要同时连接多个rpc节点,使用代理池来发起HTTP请求,同时要细致的处理好每种错误,做好日志记录。

最终我们结合自身需求,决定自行开发一个纯python实现的 eos api 库,于是花了两天时间,【eosapi】诞生了。

eosapi

【eosapi】项目地址:https://github.com/encoderlee/eosapi

已经发布到pypi:https://pypi.org/project/eosapi

可以直接用pip安装

pip install eosapi

定位

正如【eosapi】主页所描述的那样,【eosapi】主要有四大特点:

1.simple

使用起来非常简单,尽量用最少的代码达成目的,仍然是一个transfer eos token的例子:

from eosapi import EosApi
api = EosApi(rpc_host="https://jungle3.greymass.com")
api.import_key("consumer1111", "5KWxgG4rPEXzHnRBaiVRCCE6WAfnqkRpTu1uHzJoQRzixqBB1k3")
trx = 
    "actions": [
        "account": "eosio.token",
        "name": "transfer",
        "authorization": [
            
                "actor": "consumer1111",
                "permission": "active",
            ,
        ],
        "data": 
            "from": "consumer1111",
            "to": "consumer2222",
            "quantity": "0.0001 EOS",
            "memo": "by eosapi",
        ,
    ]

resp = api.push_transaction(trx)

2.high-level

【eosapi】将提供一些高级别的API,对一些常用功能进行封装。

比如我们之前提到的CPU代付《【WAX链游】EOS网络第三方代付CPU资源【实现代码】》

现在代付功能已经整合到了【eosapi】内部,只需一个函数就可以实现,而无需自行修改 trx 的 authorization ,更无需处理多个私钥签名合并的问题。

from eosapi import EosApi

account_name = "consumer1111"
private_key = "5KWxgG4rPEXzHnRBaiVRCCE6WAfnqkRpTu1uHzJoQRzixqBB1k3"
payer_name = "payer2222222"
payer_private_key = "5KAskRRbqYVCRhZxLXqeg9yvWYQQHifDtf7BPceZUDw6zybjaQh"

api = EosApi(rpc_host="https://jungle3.greymass.com")
api.import_key(account_name, private_key)
api.set_cpu_payer(payer_name, payer_private_key)

def main():
    print("transfer EOS token from [consumer1111] to [consumer2222] by eospy")
    print("but let [payer2222222] pay for CPU/NET resources of this transaction")
    trx = 
        "actions": [
            "account": "eosio.token",
            "name": "transfer",
            "authorization": [
                
                    "actor": account_name,
                    "permission": "active",
                ,
            ],
            "data": 
                "from": account_name,
                "to": "consumer2222",
                "quantity": "0.0001 EOS",
                "memo": "by eosapi",
            ,
        ]
    
    resp = api.push_transaction(trx)
    print("transaction ok: 0".format(resp))

if __name__ == '__main__':
    main()

可以看到示例代码,只需调用一下set_cpu_payer,此后该EosApi实例发起的任何transaction,均会让set_cpu_payer指定的账号代付CPU/NET资源。这点在开发WAX链游脚本的时候非常好用,因为一个脚本往往需要管理上千个账号,而这些账号往往都是0质押,均依赖代付功能才能运作。

3.lightweight

【eosapi】是一个轻量级的库,它目前仅仅只有550行python代码(包括空行),而【eosjs】总共有9451行代码。

【eosapi】主要专注于自动化脚本开发,所以并没有实现eosio chain api中的所有功能:
https://developers.eos.io/manuals/eos/latest/nodeos/plugins/chain_api_plugin/api-reference/index

轻量级的好处是代码容易阅读,如果是作为学习目的,那么阅读【eosapi】的代码会比阅读【eosjs】容易得多。

当然,后面我们会不断完善它,目前它已经够用了。

4.flexible

【eosapi】虽然是一个high-level的类库,但也有很好的灵活性。

通常来讲,越是封装成高级别的API,那么它一般就越不灵活,难以修改内部的一些参数细节,因为这些细节都被屏蔽了。但我们学习了【requests】的设计理念,它即提供简单易用的高级别API,在默认参数下,你可以简单的调用,同时也不失灵活性,它也可以让你定制内部的细节,比如你可以修改HTTP请求头的每一个参数,决定302是否重定向,你可以修改超时值,设置多种类型的代理,指定ssl证书,甚至可以自定义HTTP动词,等等。

from eosapi import EosApi, NodeException, TransactionException
from requests import RequestException

account_name = "consumer1111"
private_key = "5KWxgG4rPEXzHnRBaiVRCCE6WAfnqkRpTu1uHzJoQRzixqBB1k3"

api = EosApi(rpc_host="https://jungle3.greymass.com", timeout=60)
api.import_key(account_name, private_key)

def main():
    print("transfer EOS token from [consumer1111] to [consumer2222] by eosapi")
    trx = 
        "actions": [
            "account": "eosio.token",
            "name": "transfer",
            "authorization": [
                
                    "actor": account_name,
                    "permission": "active",
                ,
            ],
            "data": 
                "from": account_name,
                "to": "consumer2222",
                "quantity": "0.0001 EOS",
                "memo": "by eosapi",
            ,
        ]
    
    try:
        resp = api.push_transaction(trx)
        print("transaction ok: 0".format(resp))
    except RequestException as e:
        print("network error: 0".format(str(e)))
    except NodeException as e:
        print("eos node error, http status code 0, response text: 1".format(e.resp.status_code, e.resp.text))
    except TransactionException as e:
        print("eos transaction error, http status code 0, response text: 1".format(e.resp.status_code, e.resp.text))


def advance():
    # api.session isinstance of requests.Session
    # you can modify any of its properties

    # e.g If you want to set up an http proxy
    proxy = "127.0.0.1:1081"
    api.session.proxies = 
        "http": "http://0".format(proxy),
        "https": "http://0".format(proxy),
    

    # e.g if you want to modify the http request header
    api.session.headers["User-Agent"] = "Mozilla/5.0"


if __name__ == '__main__':
    main()

从上面示例代码中可以看到这一点,【eosapi】内部通过【requests】来发起HTTP请求,同时它暴露了内部的【session】对象,这个【api.session】实际上就是【requests.Session】,熟悉【requests】的朋友很快就明白了,你可以修改它的任何参数,影响【eosapi】提交HTTP请求时的行为。

同时在这个示例代码中你可以看到【eosapi】在提交 transaction 的时候,明确会抛出三种异常,代表三种不同类型的错误:

1.【RequestException 】:网络错误,比如网络超时,网络中断,代理问题
2.【NodeException 】:节点错误,eos/wax节点拒绝服务,比如节点本身出故障,或者访问太频繁导致返回http 429 或 http 403拒绝服务
3. 【TransactionException】 transaction 错误,即 transaction 已提交,但因为CPU不足,余额不足,签名错误,权限问题,智能合约报错等原因,返回http 500错误

packed_trx

还有一个重要的功能,就是把transaction打包成packed_trx,参考示例代码:

from eosapi import EosApi, Transaction
from requests import RequestException

api = EosApi(rpc_host="https://jungle3.greymass.com")

def main():
    print("packe transaction to packed_trx")
    trx = 
        "actions": [
            "account": "eosio.token",
            "name": "transfer",
            "authorization": [
                
                    "actor": "consumer1111",
                    "permission": "active",
                ,
            ],
            "data": 
                "from": "consumer1111",
                "to": "consumer2222",
                "quantity": "0.0001 EOS",
                "memo": "by eosapi",
            ,
        ]
    
    trx = api.make_transaction(trx)
    packed_trx = list(trx.pack())
    print("packed_trx: 0".format(packed_trx))

if __name__ == '__main__':
    main()

【packed_trx】主要用于【托管账号】和【白嫖CPU】。

【托管账号】:

比如wax云钱包的私钥托管在wax云钱包服务器上,那么我们为了实现wax云钱包自动化,就需要先把 transaction 打包成【packed_trx】,然后提交到wax云钱包服务端 https://public-wax-on.wax.io/wam/sign 进行签名,最后再提交到WAX网络。

【白嫖CPU】:

我们在玩【Alien Worlds】的时候会发现每个账号每天有一定的免费采集次数。

我们在原子市场发送NFT的时候,会发现即使账号0质押也能发送。

等等,我们发现有的操作并不需要消耗CPU。

为什么呢?其实就是项目方使用EOSIO的【ONLY_BILL_FIRST_AUTHORIZER】特性为我们的账号做了代付。

你在网页上手动操作的时候,项目方编写的javascript代码对 transaction 进行了处理,所以只需动一动鼠标就可以享受免费CPU资源,但当你编写脚本的时候,如何享受这些项目方提供的免费CPU资源呢,当然得学着项目方在WEB页面上的javascript代码做的那样一样去做就行了。总体来讲,通常都是在 transaction 的 actions 里加一个【boost.wax】【noop】之类的空操作,让后把 transaction 打包成【packed_trx】发送到项目方的服务端,服务端对 transaction 进行签名,最后你合并签名一起提交到WAX网络。

比如原子市场是提交到 https://wax-mainnet-signer.api.atomichub.io/v1/sign 进行签名

关于【托管账号】的自动化以及【白嫖CPU】此处不具体展开了,改天另写文章再讲。

原理

【eosapi】内部的原理并不复杂,其实不管是【eospy】和【pyeoskit】,它内部要做的事情很简单。无非就是把json格式的 transaction 内容,序列化成二进制形式,使用私钥签名后,发送HTTP请求提交到eosio rpc endpoint。

eosio rpc 文档在这里:
https://developers.eos.io/manuals/eos/latest/nodeos/plugins/chain_api_plugin/api-reference/index

每个api怎么发送http请求,请求内容和格式是什么,文档都写的很清楚,【eosapi】【eospy】【pyeoskit】均是使用【requests】包来发HTTP请求。

而签名部分,其实【eosapi】【eospy】【pyeoskit】都不是它的作者自行实现的,他们都是通过其他第三方库来实现,比如【eosapi】【eospy】实际上都是使用

【cryptos】:https://github.com/primal100/pybitcointools

来进行签名。这是一个通用的算法,并不局限于EOSIO网络,实际上【cryptos】是为BTC设计的,大多数公链都有共同之处,签名和加密这一块,基本上都是使用SHA-256算法,所以它们是通用的。

那么接下来只剩 transaction 打包和序列化了,这部分我们在【eosapi】中的实现,是先阅读和学习了
【ueosio】:https://github.com/EOSArgentina/ueosio
之后用自己代码风格重写的,感谢该项目作者

功能计划

既然【eosapi】的定位是一个 high-level 的类库,那么后面我们将会朝着这个目标继续推进,将一些常用的智能合约功能封装到【eosapi】中。

比如质押,转账,创建账户,购买RAM,投票,NFT转移,授权,修改秘钥,等功能,集成到【eosapi】中,最终只需要调用一个函数,比如调用 api.stake() 和 api.unstake() 就可以实现质押和解除质押,比如调用 api.transfer() 就可以实现转账。

性能计划

【eosapi】目前还不能从abi文件序列化data,接下来我们会实现这一功能,并且内置【eosio.token】这些通用智能合约的abi文件,同时对于第三方智能合约,我们将实现abi缓存功能,这样将进一步降低发送一笔 transaction 所需的HTTP请求数,降低对rpc endpoint请求的压力。提高发起 transaction 的及时性,这对于自动化机器人来说非常重要,少一个HTTP请求,就少几十毫秒甚至几百毫秒的时间。

交流讨论

以上是关于区块链发布一个纯Python实现的EOSIO WAX SDK的主要内容,如果未能解决你的问题,请参考以下文章

区块链发布一个纯Python实现的EOSIO WAX SDK

EOSIO源码分析 - EOSIO简介

区块链生产者由1到多

错误 3080006:EOSIO 区块链中的交易时间过长

EOS 核心功能

Rust区块链开发包