【Blockchain】Release a pure Python implementation of EOSIO WAX SDK

review

"[Blockchain] Common tools for developing WAX chain game scripts with Python"
In the previous article, we introduced two third-party open source libraries [eospy] and [pyeoskit] for Python to interact with EOS or WAX networks.
However, after in-depth use, we found that these two libraries have more or less shortcomings, and neither of them can fully meet our needs.

need

To sort it out, what are our needs? The main purpose of our use of these libraries is to develop automation scripts for WAX chain games, which is somewhat different from the requirements for developing client-side DAPPs.

For example, [eosjs] is commonly used in web clients (browsers) and is directly oriented to users. Users often only need to log in to an account at the same time, and submit transactions semi-automatically under manual interaction. There is no need to deal with proxy issues. The proxy depends on the browser settings.

Automated robots often need to manage hundreds or thousands of accounts in one process, and need to connect to multiple rpc nodes at the same time, use proxy pools to initiate HTTP requests, and at the same time handle each error carefully and make log records.

In the end, we decided to develop a pure python implementation of the eos api library based on our own needs, so it took two days, and [eosapi] was born.

eosapi

insert image description here

[eosapi] Project address: https://github.com/encoderlee/eosapi

Published to pypi: https://pypi.org/project/eosapi

Can be installed directly with pip

pip install eosapi

position

As described on the [eosapi] homepage, [eosapi] has four main features:

1.simple

It is very simple to use, try to use the least code to achieve the goal, still an example of 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] will provide some high-level APIs to encapsulate some common functions.

For example, the CPU payment we mentioned before "[WAX chain game] EOS network third-party payment of CPU resources [implementation code]"

Now the proxy payment function has been integrated into [eosapi], which can be realized with only one function, without modifying the authorization of trx by yourself, and without dealing with the problem of merging multiple private key signatures.

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()

You can see the sample code, just call set_cpu_payer, and then any transaction initiated by the EosApi instance will make the account specified by set_cpu_payer pay for CPU/NET resources. This is very useful when developing WAX chain game scripts, because a script often needs to manage thousands of accounts, and these accounts are often 0 pledged, and they all rely on the payment function to operate.

3.lightweight

[eosapi] is a lightweight library, it currently only has 550 lines of python code (including blank lines), and [eosjs] has a total of 9451 lines of code.

[eosapi] mainly focuses on automated script development, so it does not implement all the functions in eosio chain api:
https://developers.eos.io/manuals/eos/latest/nodeos/plugins/chain_api_plugin/api-reference/index

The advantage of lightweight is that the code is easy to read. If it is for learning purposes, then reading the code of [eosapi] will be much easier than reading [eosjs].

Of course, we will continue to improve it later, and it is enough for now.

4.flexible

[Eosapi] Although it is a high-level class library, it also has good flexibility.

Generally speaking, the more encapsulated into a high-level API, the less flexible it is generally, and it is difficult to modify some internal parameter details because these details are blocked. But we have learned the design concept of [requests] , which provides an easy-to-use high-level API. Under the default parameters, you can simply call it without losing flexibility. It also allows you to customize internal details, such as You can modify every parameter of the HTTP request header, decide whether to redirect 302, you can modify the timeout value, set multiple types of proxies, specify ssl certificates, and even customize HTTP verbs, etc.

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()

You can see this from the sample code above, [eosapi] internally initiates HTTP requests through [requests], and at the same time it exposes the internal [session] object, this [api.session] is actually [requests.Session], Friends who are familiar with [requests] will soon understand that you can modify any of its parameters to affect the behavior of [eosapi] when submitting HTTP requests.

At the same time, in this sample code, you can see that [eosapi] clearly throws three exceptions when submitting a transaction, representing three different types of errors:

1. [RequestException]: Network error, such as network timeout, network interruption, proxy problem
2. [NodeException]: Node error, eos/wax node denial of service, for example, the node itself fails, or the access is too frequent to return http 429 or http 403 denial of service
3. [TransactionException] transaction error, that is, the transaction has been submitted, but because of insufficient CPU, insufficient balance, signature error, permission problem, smart contract error, etc., return http 500 error

packed_trx

Another important function is to pack the transaction into packed_trx, refer to the sample code:

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] is mainly used for [hosting account] and [free CPU].

【Custody account】:

For example, the private key of the wax cloud wallet is hosted on the wax cloud wallet server, so in order to realize the automation of the wax cloud wallet, we need to pack the transaction into [packed_trx], and then submit it to the wax cloud wallet server https://public-wax -on.wax.io/wam/sign to sign before submitting to the WAX ​​Network.

【White CPU】:

When we play [Alien Worlds] , we will find that each account has a certain number of free collections per day.

When we send NFT in the atomic market, we will find that even if the account 0 is pledged, it can be sent.

Wait, we found that some operations do not need to consume CPU.

why? In fact, the project party used the [ONLY_BILL_FIRST_AUTHORIZER] feature of EOSIO to pay for our account.

When you manually operate on the webpage, the javascript code written by the project party processes the transaction, so you can enjoy free CPU resources just by moving the mouse, but when you write scripts, how to enjoy the free CPU resources provided by these project parties? As for CPU resources, of course, you have to do the same as the project party does in the javascript code on the WEB page. Generally speaking, a no-op such as [boost.wax] [noop] is usually added to the actions of the transaction, and then the transaction is packaged into [packed_trx] and sent to the server of the project party, and the server signs the transaction , and finally you merge the signatures together and submit them to the WAX ​​Network.

For example, the atomic market is submitted to https://wax-mainnet-signer.api.atomichub.io/v1/sign for signature

About the automation of [Hosting Account] and [White CPU] will not be expanded here in detail, and I will write another article for another day.

principle

The internal principle of [eosapi] is not complicated. In fact, whether it is [eospy] or [pyeoskit], the internal things to do are very simple. It is nothing more than serializing the transaction content in json format into binary form, signing it with a private key, sending an HTTP request and submitting it to the eosio rpc endpoint.

The eosio rpc documentation is here:
https://developers.eos.io/manuals/eos/latest/nodeos/plugins/chain_api_plugin/api-reference/index

How each api sends http requests, what is the content and format of the request, the document is clearly written, [eosapi] [eospy] [pyeoskit] all use the [requests] package to send HTTP requests.

As for the signature part, in fact, [eosapi] [eospy] [pyeoskit] is not implemented by its author itself, they are all implemented through other third-party libraries, such as [eosapi] [eospy] actually use

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

to sign. This is a general algorithm, not limited to the EOSIO network. In fact, [cryptos] is designed for BTC. Most public chains have something in common. The signature and encryption basically use the SHA-256 algorithm. , so they are generic.

Then there is only transaction packaging and serialization left. This part of our implementation in [eosapi] is to read and learn
[ueosio] first: https://github.com/EOSArgentina/ueosio
and then use our own code style to rebuild Written, thanks to the project author

functional plan

Since [eosapi] is positioned as a high-level class library, we will continue to move towards this goal later, and encapsulate some commonly used smart contract functions into [eosapi].

Functions such as pledge, transfer, account creation, RAM purchase, voting, NFT transfer, authorization, key modification, etc., are integrated into [eosapi], and ultimately only need to call a function, such as calling api.stake() and api.unstake () to realize pledge and unstake, for example, call api.transfer() to realize transfer.

performance plan

[eosapi] It is not possible to serialize data from abi files at present, and we will implement this function next, and built in [eosio.token] the abi files of these general smart contracts. At the same time, for third-party smart contracts, we will implement the abi cache function , which will further reduce the number of HTTP requests required to send a transaction, and reduce the pressure on rpc endpoint requests. Improving the timeliness of initiating transactions is very important for automated robots. One less HTTP request will save tens of milliseconds or even hundreds of milliseconds.

exchange discussion

insert image description here

Guess you like

Origin blog.csdn.net/CharlesSimonyi/article/details/125076661