-
Notifications
You must be signed in to change notification settings - Fork 8
PYVSYSTEMS 使用详细指南(中文)
在您的主机上安装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