Skip to content

PYVSYSTEMS 使用详细指南(中文)

Sheldon edited this page Oct 28, 2019 · 2 revisions

启动V Systems Python客户端

第一步: 准备

在您的主机上安装Python和PIP,本项目支持在Python 2.7和3上运行。本文档以Python 3.6为例。

$ sudo apt-get install python3.6 python3-dev
$ curl "https://bootstrap.pypa.io/get-pip.py" -o "get-pip.py"
$ python3 get-pip.py

在GitHub上下载v systems python库的源代码。然后安装项目所需的依赖库。

$ git clone https://github.com/virtualeconomy/pyvsystems.git
$ pip3 install -r ./pyvsystems/requirements.txt

第二步: 运行

建立一个新screen,然后启动Python控制台:

$ screen -S vsys-client
$ python3
Python 3.6.0
[Clang 9.1.0 (clang-902.0.39.2)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> 

首先,我们导入需要的包和类

>>> import pyvsystems as pv
>>> from pyvsystems import Account
>>> from pyvsystems import Wrapper
>>> import datetime
>>> import base58

然后我们创建一个chain(链)对象:

# (测试网)
>>> chain = pv.testnet_chain()

或者

# (主网)
>>> m_wrapper = pv.create_api_wrapper('http://<full node ip>:9922')
>>> chain = pv.Chain(chain_name='mainnet', chain_id='M', address_version=5, api_wrapper= m_wrapper)

链相关的函数

  • 查询当前链上的区块高度:
>>> chain.height()
643306
  • 查询当前链上最后一个区块的信息:
>>> chain.lastblock()
{u'SPOSConsensus': {u'mintTime': 1538055636000000000, u'mintBalance': 105251999961600}, u'fee': 0, u'resourcePricingData': {u'sequentialIO': 0, u'storage': 0, u'randomIO': 0, u'computation': 0, u'memory': 0}, u'reference': u'2THdUU3R7qCSs6Wq9VcD76EnZvVL7XjJsDNSLKteUDdFJmRLoJsGqNrkrDvopjhGeDvL84cqw1BTdKVY53scaaFZ', u'transactions': [{u'status': u'Success', u'type': 5, u'feeCharged': 0, u'amount': 900000000, u'timestamp': 1538055636000742434, u'currentBlockHeight': 643642, u'recipient': u'AUF4We2c4TAFr6KDsQy6WCwSYDx1VUih283', u'id': u'GfihGXgsf9Uu44uoGG2sp3yHktyGffxUcfijbA8CbBAB'}], u'timestamp': 1538055636000742434, u'generator': u'ATyLwDefhDnAaeaqtKhF3rWetkoYw1r4Gi6', u'signature': u'DAbYoJ4WJLZT25MC1ScFxwRv6Hv95TSKJ7XBsAzPzqvFaAij4Komx75zGTDakizZBQbmx9Nt2xHAAav6tdFFqZs', u'blocksize': 330, u'version': 1, u'TransactionMerkleRoot': u'4dBVqrH4hocfjNovDTWgLf2de42xVrxJjmNAXcqpWLeF', u'height': 643642, u'transaction count': 1}
  • 查询当前链上某一个高度的区块信息:
>>> n = 1
>>> chain.block(n)
{u'SPOSConsensus': {u'mintTime': 1535356440000000000, u'mintBalance': 0}, u'fee': 0, u'resourcePricingData': {u'sequentialIO': 0, u'storage': 0, u'randomIO': 0, u'computation': 0, u'memory': 0}, u'reference': u'67rpwLCuS5DGA8KGZXKsVQ7dnPb9goRLoKfgGbLfQg9WoLUgNY77E2jT11fem3coV9nAkguBACzrU1iyZM4B8roQ', u'transactions': [{u'recipient': u'ATxpELPa3yhE5h4XELxtPrW9TfXPrmYE7ze', u'status': u'Success', u'fee': 0, u'timestamp': 1535356447650226656, u'slotId': 0, u'amount': 300000000000000000, u'feeCharged': 0, u'signature': u'4N1NnwqJ6MTF46VeQLnQDSvDqxnYetqd6t8WMfKp1ZTwG1RhboS9KV1tZ21xy6QujpbrArsYfYamizg2GU5UUoNz', u'type': 1, u'id': u'4JqUudiHYHkwCPvdZDTw59qBJpqmQgvVxMzS7Bfc1EJZ'}, {u'recipient': u'ATtRykARbyJS1RwNsA6Rn1Um3S7FuVSovHK', u'status': u'Success', u'fee': 0, u'timestamp': 1535356447650226656, u'slotId': 1, u'amount': 200000000000000000, u'feeCharged': 0, u'signature': u'5B5awtvy7U62vGAjPyM1nGCytG9mKN3Map7EZuetpBSFL9VsVCRW787ZwMTJuDnFj2mkWWgMvfq18xyCLYPseEpM', u'type': 1, u'id': u'A7mgwn98VyzcEC2hmrgt9sRPbFfyBvs44rjGPu7dHxbH'}, {u'recipient': u'ATtchuwHVQmNTsRA8ba19juGK9m1gNsUS1V', u'status': u'Success', u'fee': 0, u'timestamp': 1535356447650226656, u'slotId': 2, u'amount': 150000000000000000, u'feeCharged': 0, u'signature': u'3t3Dh49RmvrEnG3nQvNFQN4bLN1Jvkw1Ffa2u1AXQdU8jetngagdD2n3pWmUeXWXbVRYSNgQX8My3WEGteQTUVDy', u'type': 1, u'id': u'ZdpAvtegbnPSBTyRDD2xTvbQ5LxhAX6jrykuG7hxugB'}, {u'recipient': u'AU4AoB2WzeXiJvgDhCZmr6B7uDqAzGymG3L', u'status': u'Success', u'fee': 0, u'timestamp': 1535356447650226656, u'slotId': 3, u'amount': 50000000000000000, u'feeCharged': 0, u'signature': u'33EftL6giA2xYBkx9x4R2xdd3ikcDBb3aw3mDQAHt7Y3QkoEvozKKsnqGGTYiRVut8sexSE3EuDYxyviXA6hhkzT', u'type': 1, u'id': u'257QZpUxYRiKVRVQr8FgzTngbzF6MW7fMWqyxB7wFawq'}, {u'recipient': u'AUBHchRBY4mVNktgCgJdGNcYbwvmzPKgBgN', u'status': u'Success', u'fee': 0, u'timestamp': 1535356447650226656, u'slotId': 4, u'amount': 60000000000000000, u'feeCharged': 0, u'signature': u'2Thag4Lo4ixcyw9p2p4dMHRwoqs4LJ3Jwkjdf9qNT34FNk7sKRAGFbNUezgCc9XfVw6obTn1m4zRjx4kRLCYyWcz', u'type': 1, u'id': u'5G27tFPDFBXSmS6muMiXMDc3VuAyoA4TtSWtgJaWrfY7'}, {u'recipient': u'AU6qstXoazCHDK5dmuCqEnnTWgTqRugHwzm', u'status': u'Success', u'fee': 0, u'timestamp': 1535356447650226656, u'slotId': 5, u'amount': 60000000000000000, u'feeCharged': 0, u'signature': u'z7MzwmKLJQJLMu8Z2ivFCTBTwNKYyfomdxN65cdyCwy6YsL7UbBFciQXUo7u1W5Uikwwx8dePPzYfKokxBfaJn5', u'type': 1, u'id': u'GnLWJrk8cs9p8MzasEiAPmH77atNuCo2U58Yk46EBqvi'}, {u'recipient': u'AU9HYFXuPZPbFVw8vmp7mFmHb7qiaMmgEYE', u'status': u'Success', u'fee': 0, u'timestamp': 1535356447650226656, u'slotId': 6, u'amount': 60000000000000000, u'feeCharged': 0, u'signature': u'2PPHXX9UjwFxFBujqE2bMzqEYTbJy1ydS6b5BywAymrgUXhpsWQHa84uUpEdbh9PcFSjehbbSvw8b9Yt74S1W58W', u'type': 1, u'id': u'DXWZgR3KPXYuHccAXY6uLSrW3VWVT3uNVKkjqWmfFPeV'}, {u'recipient': u'AUBLPMpHVV74fHQD8D6KosA76nusw4FqRr1', u'status': u'Success', u'fee': 0, u'timestamp': 1535356447650226656, u'slotId': 7, u'amount': 60000000000000000, u'feeCharged': 0, u'signature': u'bBJ6eTAUgMP8TbmafSwLUT3vFvNY955ddpYuByk6v8CFn6VzeuMUz11veM9bGAuwki3gTevDYf9N8SVPy5AhA8N', u'type': 1, u'id': u'8JEe9ibrSajZXpQxhxNzxPZ81RRbXSBy4EpRpophFCvz'}, {u'recipient': u'AUBbpPbymsrM8QiXqS3NU7CrD1vy1EyonCa', u'status': u'Success', u'fee': 0, u'timestamp': 1535356447650226656, u'slotId': 8, u'amount': 40000000000000000, u'feeCharged': 0, u'signature': u'5fbpe2GsLHaGEATiEZL7EgsfxGTAsbTRg14UzGkRcQiTpHhzxdqx1CJ31nVr4ndBgkkkKG1xc9waVBtBKjagDF59', u'type': 1, u'id': u'3XECBDJVne2SwSnct48LE3fM8FEYVWgpLx3iSfqh9ABx'}, {u'recipient': u'AU7nJLcT1mThXGTT1KDkoAtfPzc82Sgay1V', u'status': u'Success', u'fee': 0, u'timestamp': 1535356447650226656, u'slotId': 9, u'amount': 20000000000000000, u'feeCharged': 0, u'signature': u'59k1YWqXgermPUocbS6JtJMmkSBsWSiyJtiGQd7Artkgddns6WdSmUPYezymSXPo9gpEpgbRGEKsFsxRC16pwoXz', u'type': 1, u'id': u'FMJd2cyYasRPm6jWqGi3CuMhwE5dF7LiNQCXbKAsAhPi'}], u'timestamp': 1535356447650226656, u'generator': u'ATrBur3QznqYo8kouKPgDYki7AtbHEsmVid', u'signature': u'5n4ewwZh9F4MMpSvtdxLCu5MUKnhEyUth2w3zEfpuiX3vwS1STNCdi51fmowJuLT1CfFg1DuodSvxwBZDANvGNej', u'blocksize': 870, u'version': 1, u'TransactionMerkleRoot': u'85w9jZL7o6evWqt3qpy4JGC3144ekGcSfxVnznMBwUAP', u'height': 1, u'transaction count': 10}
  • 用交易ID查询当前链上的交易信息:
>>> tx_id = "EBuXG2y5yLa8j9QUaXK535sU7xUjokVuCkmgwFKAjJw6"
>>> resp = chain.tx(tx_id)
>>> resp
{u'status': u'Success', u'fee': 10000000, u'timestamp': 1536230749375858944, u'feeCharged': 10000000, u'feeScale': 100, u'height': 143985, u'amount': 100000000, u'attachment': u'', u'type': 2, u'proofs': [{u'publicKey': u'CbUPwcCJaMqYSjZGXy4LrkTfV2ncP27Chqyd2QKXfJxn', u'proofType': u'Curve25519', u'signature': u'64cyK8CdgaaNh199VghmrUieCF7XVQmiV5EmpwJaHRZ9mWbMBs87saPCrvrdWELBn9sqLpiJ5KicqBQMM77jYvpr'}], u'recipient': u'ATt6P4vSpBvBTHdV5V9PJEHMFp4msJ1fkkX', u'id': u'EBuXG2y5yLa8j9QUaXK535sU7xUjokVuCkmgwFKAjJw6'}

将返回的交易时间转化成常规显示时间的办法:

>>> display_time = datetime.datetime.fromtimestamp(resp['timestamp'] // 1000000000)
>>> print("Time: {}".format(display_time))
Time: 2018-09-06 18:45:49

发送者的地址其实是隐藏在proofs里的public key的,如果需要转换成地址:

>>> sender_public_key = base58.b58decode(resp['proofs'][0]['publicKey'])
>>> sender_address = ts_chain.public_key_to_address(sender_public_key)
>>> print("From: {}".format(sender_address))
AU6GsBinGPqW8zUuvmjgwpBNLfyyTU3p83Q

从返回结果中获取接受者的地址:

>>> print("To: {}".format(resp['recipient']))
ATt6P4vSpBvBTHdV5V9PJEHMFp4msJ1fkkX

从返回结果中获取交易额

>>> print("Amount: {}".format(resp['amount']))
Amount: 100000000

从返回结果中获取交易手续费

>>> print("Transaction fee: {}".format(resp['fee']))
Transaction fee: 10000000
  • 验证地址是否在链上合法:
>>> addr = "AUC63SmgmUNmie784Ld9CRjbecvPg2beShy"
>>> chain.validate_address(addr)
True
>>> addr = "An invalid address"
>>> chain.validate_address(addr)
False

钱包账号相关的函数

  • 创建一个钱包账号
>>> my_account = Account(chain=chain)
  • 获取钱包账号的信息
>>> my_account.get_info()
{u'available': 0, u'mintingAverage': 0, u'effective': 0, u'height': 643936, 'publicKey': '8PtidoeohqEyAyuqdBGudWfr7i1mUVrhNkQHNRfvaXvq', u'regular': 0, u'address': u'AU1vwC7C6CqV2q4oZYFNTbBJYNaakMBQyZV'}

以下具体解释一下返回结果:

{
	'regular': 0, 		# 常规余额
	'available': 0,  	# 可用余额 (常规余额 - 借出的钱)
	'effective': 0,  	# 有效余额 (常规余额 - 借出的钱 + 借入的钱)
	'mintingAverage': 0,    # 竞选超级节点挖矿用的计算值
	'height': 643936, 
	'publicKey': '8PtidoeohqEyAyuqdBGudWfr7i1mUVrhNkQHNRfvaXvq', 
	'address': 'AU1vwC7C6CqV2q4oZYFNTbBJYNaakMBQyZV'
}

其中,available 是我们最终可用的余额。

出于安全考虑,钱包的私钥不会出现在常规的钱包信息中。如需查询钱包私钥,可用这样使用:

>>> my_account.privateKey
'28XHDwLzKaXRLspTBuxqurrah6apEfbJGh6Nnt914u2T'

这些钱包数据只会保存在内存里,所以对于创建的钱包,钱包私钥最好保存备份到您的数据库中。

  • 通过私钥恢复钱包
>>> pk = "28XHDwLzKaXRLspTBuxqurrah6apEfbJGh6Nnt914u2T"
>>> restore_account = Account(chain=chain, private_key=pk)
  • 检查全节点是否正常运转

为了保证发出去的交易能广播到全网,我们用check_node()方法来检查全节点是否运转正常,正常连通主网情况下全节点的区块高度是不断增加的。检查全节点有两种方法调用。

下面这种调用方法会检查全节点的区块高度是否不断增加。这种检查方法建议放在定期任务中定时对自己的全节点进行检查。

>>> # 如果节点正常,返回 True;否则返回 False。
>>> my_account.check_node()
True

另一个方法是用参考节点来对比验证自己的全节点是否正常。这样的好处不仅是检查高度,同时也会确保没有短暂的分叉发生。这种检查建议在做关键交易前进行以确保交易能广播到全网。

>>> # 如果节点正常,返回 True;否则返回 False。
>>> my_account.check_node("http://<reference node ip>:9922")
False
  • 发送交易请求

(默认的交易费是0.1 VSYS,这个交易费也是最小的交易费用。fee_scale目前必须是固定值100)

>>> # For example, the address of recipient is "AU18gPQDnhG3PqmdzLjUyRTM3t8bbffGPo8"
>>> recipient_address = "AU18gPQDnhG3PqmdzLjUyRTM3t8bbffGPo8"
>>> recipient = Account(chain=chain, address=recipient_address)
>>> # send payment (100000000 = 1 VSYS)
>>> resp = my_account.send_payment(recipient, amount=100000000)
>>> resp
{u'fee': 10000000, u'timestamp': 1538059747188733952, u'feeScale': 100, u'amount': 100000000, u'attachment': u'', u'type': 2, u'proofs': [{u'publicKey': u'B2Khd89jtnpuzGdnyGRcnKycZMBCo6PsotFcWWi1wMDV', u'proofType': u'Curve25519', u'signature': u'4ZR6FW68YbwPUnxt18hx4df7Nrdx5th3YruHR672JGw3U4simXgXGSNvxodDV8q7H4cm4XwZCkGDF9ic1aPiExMh'}], u'recipient': u'AU18gPQDnhG3PqmdzLjUyRTM3t8bbffGPo8', u'id': u'ABgT8xs3EtR9spKopMdSmsdCE5pZRoZEqdnKX3nmD9fv'}

从返回结果中获取交易时间

>>> display_time = datetime.datetime.fromtimestamp(resp['timestamp'] // 1000000000)
>>> print("Time: {}".format(display_time))
Time: 2018-09-27 22:49:07

发送者的地址其实是隐藏在proofs里的public key的,如果需要转换成发送者地址:

>>> sender_public_key = base58.b58decode(resp['proofs'][0]['publicKey'])
>>> sender_address = ts_chain.public_key_to_address(sender_public_key)
>>> print("From: {}".format(sender_address))
ATt6P4vSpBvBTHdV5V9PJEHMFp4msJ1fkkX

从返回结果中获取接受者的地址:

>>> print("To: {}".format(resp['recipient']))
AU18gPQDnhG3PqmdzLjUyRTM3t8bbffGPo8

从返回结果中获取交易额:

>>> print("Amount: {}".format(resp['amount']))
Amount: 100000000

从返回结果中获取交易费:

>>> print("Transaction fee: {}".format(resp['fee']))
Transaction fee: 10000000
  • 确认交易记录已经上链

根据交易ID调用 check_tx() 方法可以用来确认记录是否已经在链上。confirmations变量应该设置为M * 超级节点数量 + 1 (M是当交易发送后为确保交易在链上可以等待的最大分钟)。因为可能有超级节点出块慢导致的短期分叉的情况,有些区块可能会被撤回。所以理论上如果填的confirmations值越大,则被误确认的交易发生概率就越小。目前全网有15个超级节点,所以我们建议confirmations最好大于15。例如,为了确认刚发送完的交易在链上您可以等待2分钟,那么confirmations值应该设为 2*15+1=31 :

>>> tx_id = "dE4s1joxLqP1sSVRBB1KmEfB6a3vND2k3Uwsao12paG"
>>> my_account.check_tx(tx_id, 31)
True
  • 获取交易历史
>>> # For example, we get last 10 transations and show payment transations only:
>>> limit = 10
>>> tx_filter = pv.setting.PAYMENT_TX_TYPE
>>> my_account.get_tx_history(limit, tx_filter)
[{u'status': u'Success', u'fee': 10000000, u'timestamp': 1538059747188733952, u'feeCharged': 10000000, u'feeScale': 100, u'amount': 100000000, u'attachment': u'', u'type': 2, u'proofs': [{u'publicKey': u'B2Khd89jtnpuzGdnyGRcnKycZMBCo6PsotFcWWi1wMDV', u'proofType': u'Curve25519', u'signature': u'4ZR6FW68YbwPUnxt18hx4df7Nrdx5th3YruHR672JGw3U4simXgXGSNvxodDV8q7H4cm4XwZCkGDF9ic1aPiExMh'}], u'recipient': u'AU18gPQDnhG3PqmdzLjUyRTM3t8bbffGPo8', u'id': u'ABgT8xs3EtR9spKopMdSmsdCE5pZRoZEqdnKX3nmD9fv'}, {u'status': u'Success', u'fee': 10000000, u'timestamp': 1536663958559000000, u'feeCharged': 10000000, u'feeScale': 100, u'amount': 210000000000000, u'attachment': u'', u'type': 2, u'proofs': [{u'publicKey': u'B2Khd89jtnpuzGdnyGRcnKycZMBCo6PsotFcWWi1wMDV', u'proofType': u'Curve25519', u'signature': u'4VkfFztUQUPBTcKWBLrwcPfSgtHnAuHYYyWSMfVAu2Z1WZyWE2i5VPBghCkJCJCacqHjALKHr8qRGD9PW6WTBZU'}], u'recipient': u'AU7nJLcT1mThXGTT1KDkoAtfPzc82Sgay1V', u'id': u'Mv2WZyurYtKAMhdFazvPZy2Nt8NapmHuwvAR8KKrbog'}]

如需退出Python控制台,按Ctrl + D或者输入 "exit()"

>>> exit()

代码范例

import base58
import datetime
import pyvsystems as pv
from pyvsystems import Account
from pyvsystems.error import *


def test_payment():
    # set chain
    ts_chain = pv.testnet_chain()

    # get block height
    try:
        height = ts_chain.height()
        print("The current block height of the chain: {}".format(height))
    except NetworkException as ex:
        # Handle Network issue here
        print("Failed to get block height: {}".format(ex))
        return False

    # create / restore account
    try:
        # retrieve account by private key
        private_key = "XXXXXXXXXX"
        my_account = Account(chain=ts_chain, private_key=private_key)
        # create recipient with address
        recipient_address = "XXXXXXXXXXXXXXXXX"
        recipient = Account(chain=ts_chain, address=recipient_address)
    except InvalidParameterException as ex:
        # Handle Invalid Parameter issue here
        print("Invalid Parameter: {}".format(ex))
        return False
    except InvalidAddressException as ex:
        # Handle Invalid Address issue here
        print("Invalid Address: {}".format(ex))
        return False

    # send payment (100000000 = 1 VSYS)
    try:
        print("========Do payment transaction===========")
        resp = my_account.send_payment(recipient, amount=100000000)
        print("Payment TX result: {}".format(resp))
        print("Transaction ID: {}".format(resp['id']))
        display_time = datetime.datetime.fromtimestamp(resp['timestamp'] // 1000000000)
        print("Time: {}".format(display_time))
        sender_public_key = base58.b58decode(resp['proofs'][0]['publicKey'])
        sender_address = ts_chain.public_key_to_address(sender_public_key)
        print("From: {}".format(sender_address))
        print("To: {}".format(resp['recipient']))
        print("Amount: {}".format(resp['amount']))
        print("Transaction fee: {}".format(resp['fee']))
    except InvalidParameterException as ex:
        # Handle Invalid Parameter issue here
        print("Invalid Parameter: {}".format(ex))
        return False
    except MissingPrivateKeyException:
        # Handle Missing Private Key issue here
        print("No private key for `my_account`")
        return False
    except InvalidAddressException as ex:
        # Handle Invalid Address issue here
        print("Invalid Address: {}".format(ex))
        return False
    except InsufficientBalanceException:
        # Handle Insufficient Balance issue here
        print("Insufficient Balance.")
        return False
    except NetworkException as ex:
        # Handle Network issue here
        print("Failed to get HTTP response: {}".format(ex))
        return False

    # check payment history
    try:
        print("========Check payment history===========")
        history = my_account.get_tx_history(10)
        print("Payment history: {}".format(history))
        for record in history:
            if not record.get('proofs'):
                continue
            sender_public_key = base58.b58decode(record['proofs'][0]['publicKey'])
            sender_address = ts_chain.public_key_to_address(sender_public_key)
            if sender_address == my_account.address:
                print("####### Send #######")
            else:
                print("####### Received #######")
            print("From: {}".format(sender_address))
            print("To: {}".format(record['recipient']))
            print("Transaction ID: {}".format(record['id']))
            display_time = datetime.datetime.fromtimestamp(record['timestamp'] // 1000000000)
            print("Time: {}".format(display_time))
            print("Amount: {}".format(record['amount']))
            print("Transaction fee: {}".format(record['fee']))
    except InvalidParameterException as ex:
        # Handle Invalid Parameter issue here
        print("Invalid Parameter: {}".format(ex))
        return False
    except MissingAddressException:
        # Handle Address issue here
        print("No address for `my_account`")
        return False
    except NetworkException as ex:
        # Handle Network issue here
        print("Failed to get HTTP response: {}".format(ex))
        return False
    return True


if __name__ == "__main__":
    test_payment()

将文件保存到里的硬盘,例如命名为"sample.py",然后这样运行

$ python3 sample.py