├── __init__.py ├── tests ├── __init__.py ├── .performance.py ├── test_witness.py ├── test_message.py ├── test_fee.py ├── test_base_objects.py ├── test_rbac_small.py ├── test_cachedlist.py ├── test_asset.py ├── archived │ ├── test_objectcache.py │ ├── test_account2.py │ └── test_son.py ├── test_nft.py ├── test_account.py ├── test_market_place.py ├── test_object_creation_parents.py ├── fixtures_hrp.py ├── test_txbuffers.py ├── test_proposals.py ├── fixtures.py └── performance │ ├── performance_son.py │ └── performance_fund_transfer.py ├── peerplays ├── cli │ ├── __init__.py │ ├── witness.py │ ├── committee.py │ ├── rpc.py │ ├── asset.py │ ├── message.py │ ├── bos.py │ ├── main.py │ ├── ui.py │ ├── proposal.py │ ├── cli.py │ └── info.py ├── blockchainobject.py ├── bet.py ├── __init__.py ├── committee.py ├── message.py ├── storage.py ├── wallet.py ├── blockchain.py ├── proposal.py ├── block.py ├── witness.py ├── genesisbalance.py ├── utils.py ├── asset.py ├── sport.py ├── instance.py ├── rule.py ├── eventgroup.py ├── memo.py ├── event.py ├── bettingmarket.py ├── transactionbuilder.py ├── amount.py ├── account.py ├── bettingmarketgroup.py ├── exceptions.py └── notify.py ├── MANIFEST.in ├── cli.py ├── docs ├── quickstart.rst ├── modules.rst ├── requirements.txt ├── peerplays.bet.rst ├── peerplays.son.rst ├── peerplays.memo.rst ├── peerplays.rule.rst ├── peerplays.asset.rst ├── peerplays.block.rst ├── peerplays.event.rst ├── peerplays.price.rst ├── peerplays.sport.rst ├── peerplays.utils.rst ├── peerplays.amount.rst ├── peerplays.cli.ui.rst ├── peerplays.market.rst ├── peerplays.notify.rst ├── peerplays.wallet.rst ├── peerplays.account.rst ├── peerplays.cli.bos.rst ├── peerplays.cli.cli.rst ├── peerplays.cli.info.rst ├── peerplays.cli.main.rst ├── peerplays.cli.rpc.rst ├── peerplays.instance.rst ├── peerplays.message.rst ├── peerplays.proposal.rst ├── peerplays.storage.rst ├── peerplays.witness.rst ├── peerplays.cli.asset.rst ├── peerplays.committee.rst ├── peerplays.peerplays.rst ├── peerplays.blockchain.rst ├── peerplays.cli.bookie.rst ├── peerplays.cli.wallet.rst ├── peerplays.eventgroup.rst ├── peerplays.exceptions.rst ├── peerplays.peerplays2.rst ├── peerplays.cli.account.rst ├── peerplays.cli.message.rst ├── peerplays.cli.witness.rst ├── peerplays.bettingmarket.rst ├── peerplays.cli.committee.rst ├── peerplays.cli.proposal.rst ├── peerplays.cli.decorators.rst ├── peerplays.genesisbalance.rst ├── peerplays.blockchainobject.rst ├── peerplays.bettingmarketgroup.rst ├── peerplays.transactionbuilder.rst ├── tutorials │ ├── index.rst │ ├── howto-exchanges.rst │ ├── howto-json-rpc.rst │ ├── howto-build-peerplays.rst │ ├── howto-decode-memo.rst │ ├── howto-monitor-blocks.rst │ ├── howto-trusted-node.rst │ └── howto-blockproducer.rst ├── support.rst ├── installation.rst ├── configuration.rst ├── peerplays.cli.rst ├── peerplays.rst ├── contribute.rst ├── stati.rst ├── rpc.rst ├── cli.rst ├── index.rst └── tutorials.rst ├── peerplaysapi ├── __init__.py ├── wallet.py ├── exceptions.py └── node.py ├── peerplaysbase ├── memo.py ├── __init__.py ├── signedtransactions.py ├── objecttypes.py ├── asset_permissions.py ├── types.py ├── chains.py ├── operationids.py └── account.py ├── requirements-test.txt ├── requirements.txt ├── .travis.yml ├── sonar-project.properties ├── examples ├── freeze_group.py ├── resolve_bet.py ├── cli-wallet-propose.py └── create_all.py ├── tox.ini ├── setup.cfg ├── .gitignore ├── Makefile ├── LICENSE.txt ├── README.md ├── setup.py └── .gitlab-ci.yml /__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /peerplays/cli/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.md 2 | include requirements.txt 3 | -------------------------------------------------------------------------------- /cli.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from peerplays.cli import cli 4 | 5 | cli.main() 6 | -------------------------------------------------------------------------------- /docs/quickstart.rst: -------------------------------------------------------------------------------- 1 | *********** 2 | Quickstart 3 | *********** 4 | 5 | under construction 6 | -------------------------------------------------------------------------------- /docs/modules.rst: -------------------------------------------------------------------------------- 1 | peerplays 2 | ========= 3 | 4 | .. toctree:: 5 | :maxdepth: 6 6 | 7 | peerplays 8 | -------------------------------------------------------------------------------- /peerplaysapi/__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = [ 2 | "exceptions", 3 | "node", 4 | "websocket" 5 | ] 6 | -------------------------------------------------------------------------------- /peerplaysbase/memo.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from graphenebase.memo import get_shared_secret, encode_memo, decode_memo, _unpad, _pad 3 | -------------------------------------------------------------------------------- /requirements-test.txt: -------------------------------------------------------------------------------- 1 | # Unit testing 2 | tox 3 | pytest 4 | pytest-mock 5 | coverage 6 | mock 7 | 8 | # Code style 9 | # black 10 | isort 11 | pre-commit 12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | peerplays 2 | 3 | urllib3>=1.25.9 # not directly required, pinned by Snyk to avoid a vulnerability 4 | ecdsa>=0.14 # not directly required, pinned by Snyk to avoid a vulnerability -------------------------------------------------------------------------------- /docs/peerplays.bet.rst: -------------------------------------------------------------------------------- 1 | peerplays.bet module 2 | ==================== 3 | 4 | .. automodule:: peerplays.bet 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :inherited-members: 9 | -------------------------------------------------------------------------------- /docs/peerplays.son.rst: -------------------------------------------------------------------------------- 1 | peerplays.son module 2 | ==================== 3 | 4 | .. automodule:: peerplays.son 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :inherited-members: 9 | -------------------------------------------------------------------------------- /docs/peerplays.memo.rst: -------------------------------------------------------------------------------- 1 | peerplays.memo module 2 | ===================== 3 | 4 | .. automodule:: peerplays.memo 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :inherited-members: 9 | -------------------------------------------------------------------------------- /docs/peerplays.rule.rst: -------------------------------------------------------------------------------- 1 | peerplays.rule module 2 | ===================== 3 | 4 | .. automodule:: peerplays.rule 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :inherited-members: 9 | -------------------------------------------------------------------------------- /peerplaysapi/wallet.py: -------------------------------------------------------------------------------- 1 | from grapheneapi.grapheneapi import GrapheneAPI 2 | 3 | 4 | class PeerPlaysWalletRPC(GrapheneAPI): 5 | """ This class inherits everything from GrapheneAPI 6 | """ 7 | pass 8 | -------------------------------------------------------------------------------- /docs/peerplays.asset.rst: -------------------------------------------------------------------------------- 1 | peerplays.asset module 2 | ====================== 3 | 4 | .. automodule:: peerplays.asset 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :inherited-members: 9 | -------------------------------------------------------------------------------- /docs/peerplays.block.rst: -------------------------------------------------------------------------------- 1 | peerplays.block module 2 | ====================== 3 | 4 | .. automodule:: peerplays.block 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :inherited-members: 9 | -------------------------------------------------------------------------------- /docs/peerplays.event.rst: -------------------------------------------------------------------------------- 1 | peerplays.event module 2 | ====================== 3 | 4 | .. automodule:: peerplays.event 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :inherited-members: 9 | -------------------------------------------------------------------------------- /docs/peerplays.price.rst: -------------------------------------------------------------------------------- 1 | peerplays.price module 2 | ====================== 3 | 4 | .. automodule:: peerplays.price 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :inherited-members: 9 | -------------------------------------------------------------------------------- /docs/peerplays.sport.rst: -------------------------------------------------------------------------------- 1 | peerplays.sport module 2 | ====================== 3 | 4 | .. automodule:: peerplays.sport 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :inherited-members: 9 | -------------------------------------------------------------------------------- /docs/peerplays.utils.rst: -------------------------------------------------------------------------------- 1 | peerplays.utils module 2 | ====================== 3 | 4 | .. automodule:: peerplays.utils 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :inherited-members: 9 | -------------------------------------------------------------------------------- /docs/peerplays.amount.rst: -------------------------------------------------------------------------------- 1 | peerplays.amount module 2 | ======================= 3 | 4 | .. automodule:: peerplays.amount 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :inherited-members: 9 | -------------------------------------------------------------------------------- /docs/peerplays.cli.ui.rst: -------------------------------------------------------------------------------- 1 | peerplays.cli.ui module 2 | ======================= 3 | 4 | .. automodule:: peerplays.cli.ui 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :inherited-members: 9 | -------------------------------------------------------------------------------- /docs/peerplays.market.rst: -------------------------------------------------------------------------------- 1 | peerplays.market module 2 | ======================= 3 | 4 | .. automodule:: peerplays.market 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :inherited-members: 9 | -------------------------------------------------------------------------------- /docs/peerplays.notify.rst: -------------------------------------------------------------------------------- 1 | peerplays.notify module 2 | ======================= 3 | 4 | .. automodule:: peerplays.notify 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :inherited-members: 9 | -------------------------------------------------------------------------------- /docs/peerplays.wallet.rst: -------------------------------------------------------------------------------- 1 | peerplays.wallet module 2 | ======================= 3 | 4 | .. automodule:: peerplays.wallet 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :inherited-members: 9 | -------------------------------------------------------------------------------- /docs/peerplays.account.rst: -------------------------------------------------------------------------------- 1 | peerplays.account module 2 | ======================== 3 | 4 | .. automodule:: peerplays.account 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :inherited-members: 9 | -------------------------------------------------------------------------------- /docs/peerplays.cli.bos.rst: -------------------------------------------------------------------------------- 1 | peerplays.cli.bos module 2 | ======================== 3 | 4 | .. automodule:: peerplays.cli.bos 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :inherited-members: 9 | -------------------------------------------------------------------------------- /docs/peerplays.cli.cli.rst: -------------------------------------------------------------------------------- 1 | peerplays.cli.cli module 2 | ======================== 3 | 4 | .. automodule:: peerplays.cli.cli 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :inherited-members: 9 | -------------------------------------------------------------------------------- /docs/peerplays.cli.info.rst: -------------------------------------------------------------------------------- 1 | peerplays.cli.info module 2 | ========================= 3 | 4 | .. automodule:: peerplays.cli.info 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :inherited-members: 9 | -------------------------------------------------------------------------------- /docs/peerplays.cli.main.rst: -------------------------------------------------------------------------------- 1 | peerplays.cli.main module 2 | ========================= 3 | 4 | .. automodule:: peerplays.cli.main 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :inherited-members: 9 | -------------------------------------------------------------------------------- /docs/peerplays.cli.rpc.rst: -------------------------------------------------------------------------------- 1 | peerplays.cli.rpc module 2 | ======================== 3 | 4 | .. automodule:: peerplays.cli.rpc 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :inherited-members: 9 | -------------------------------------------------------------------------------- /docs/peerplays.instance.rst: -------------------------------------------------------------------------------- 1 | peerplays.instance module 2 | ========================= 3 | 4 | .. automodule:: peerplays.instance 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :inherited-members: 9 | -------------------------------------------------------------------------------- /docs/peerplays.message.rst: -------------------------------------------------------------------------------- 1 | peerplays.message module 2 | ======================== 3 | 4 | .. automodule:: peerplays.message 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :inherited-members: 9 | -------------------------------------------------------------------------------- /docs/peerplays.proposal.rst: -------------------------------------------------------------------------------- 1 | peerplays.proposal module 2 | ========================= 3 | 4 | .. automodule:: peerplays.proposal 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :inherited-members: 9 | -------------------------------------------------------------------------------- /docs/peerplays.storage.rst: -------------------------------------------------------------------------------- 1 | peerplays.storage module 2 | ======================== 3 | 4 | .. automodule:: peerplays.storage 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :inherited-members: 9 | -------------------------------------------------------------------------------- /docs/peerplays.witness.rst: -------------------------------------------------------------------------------- 1 | peerplays.witness module 2 | ======================== 3 | 4 | .. automodule:: peerplays.witness 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :inherited-members: 9 | -------------------------------------------------------------------------------- /docs/peerplays.cli.asset.rst: -------------------------------------------------------------------------------- 1 | peerplays.cli.asset module 2 | ========================== 3 | 4 | .. automodule:: peerplays.cli.asset 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :inherited-members: 9 | -------------------------------------------------------------------------------- /docs/peerplays.committee.rst: -------------------------------------------------------------------------------- 1 | peerplays.committee module 2 | ========================== 3 | 4 | .. automodule:: peerplays.committee 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :inherited-members: 9 | -------------------------------------------------------------------------------- /docs/peerplays.peerplays.rst: -------------------------------------------------------------------------------- 1 | peerplays.peerplays module 2 | ========================== 3 | 4 | .. automodule:: peerplays.peerplays 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :inherited-members: 9 | -------------------------------------------------------------------------------- /docs/peerplays.blockchain.rst: -------------------------------------------------------------------------------- 1 | peerplays.blockchain module 2 | =========================== 3 | 4 | .. automodule:: peerplays.blockchain 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :inherited-members: 9 | -------------------------------------------------------------------------------- /docs/peerplays.cli.bookie.rst: -------------------------------------------------------------------------------- 1 | peerplays.cli.bookie module 2 | =========================== 3 | 4 | .. automodule:: peerplays.cli.bookie 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :inherited-members: 9 | -------------------------------------------------------------------------------- /docs/peerplays.cli.wallet.rst: -------------------------------------------------------------------------------- 1 | peerplays.cli.wallet module 2 | =========================== 3 | 4 | .. automodule:: peerplays.cli.wallet 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :inherited-members: 9 | -------------------------------------------------------------------------------- /docs/peerplays.eventgroup.rst: -------------------------------------------------------------------------------- 1 | peerplays.eventgroup module 2 | =========================== 3 | 4 | .. automodule:: peerplays.eventgroup 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :inherited-members: 9 | -------------------------------------------------------------------------------- /docs/peerplays.exceptions.rst: -------------------------------------------------------------------------------- 1 | peerplays.exceptions module 2 | =========================== 3 | 4 | .. automodule:: peerplays.exceptions 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :inherited-members: 9 | -------------------------------------------------------------------------------- /docs/peerplays.peerplays2.rst: -------------------------------------------------------------------------------- 1 | peerplays.peerplays2 module 2 | =========================== 3 | 4 | .. automodule:: peerplays.peerplays2 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :inherited-members: 9 | -------------------------------------------------------------------------------- /docs/peerplays.cli.account.rst: -------------------------------------------------------------------------------- 1 | peerplays.cli.account module 2 | ============================ 3 | 4 | .. automodule:: peerplays.cli.account 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :inherited-members: 9 | -------------------------------------------------------------------------------- /docs/peerplays.cli.message.rst: -------------------------------------------------------------------------------- 1 | peerplays.cli.message module 2 | ============================ 3 | 4 | .. automodule:: peerplays.cli.message 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :inherited-members: 9 | -------------------------------------------------------------------------------- /docs/peerplays.cli.witness.rst: -------------------------------------------------------------------------------- 1 | peerplays.cli.witness module 2 | ============================ 3 | 4 | .. automodule:: peerplays.cli.witness 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :inherited-members: 9 | -------------------------------------------------------------------------------- /docs/peerplays.bettingmarket.rst: -------------------------------------------------------------------------------- 1 | peerplays.bettingmarket module 2 | ============================== 3 | 4 | .. automodule:: peerplays.bettingmarket 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :inherited-members: 9 | -------------------------------------------------------------------------------- /docs/peerplays.cli.committee.rst: -------------------------------------------------------------------------------- 1 | peerplays.cli.committee module 2 | ============================== 3 | 4 | .. automodule:: peerplays.cli.committee 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :inherited-members: 9 | -------------------------------------------------------------------------------- /docs/peerplays.cli.proposal.rst: -------------------------------------------------------------------------------- 1 | peerplays.cli.proposal module 2 | ============================= 3 | 4 | .. automodule:: peerplays.cli.proposal 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :inherited-members: 9 | -------------------------------------------------------------------------------- /docs/peerplays.cli.decorators.rst: -------------------------------------------------------------------------------- 1 | peerplays.cli.decorators module 2 | =============================== 3 | 4 | .. automodule:: peerplays.cli.decorators 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :inherited-members: 9 | -------------------------------------------------------------------------------- /docs/peerplays.genesisbalance.rst: -------------------------------------------------------------------------------- 1 | peerplays.genesisbalance module 2 | =============================== 3 | 4 | .. automodule:: peerplays.genesisbalance 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :inherited-members: 9 | -------------------------------------------------------------------------------- /peerplaysbase/__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = [ 2 | "account", 3 | "chains", 4 | "objects", 5 | "objecttypes", 6 | "operationids", 7 | "operations", 8 | "signedtransactions", 9 | "transactions", 10 | "memo", 11 | ] 12 | -------------------------------------------------------------------------------- /docs/peerplays.blockchainobject.rst: -------------------------------------------------------------------------------- 1 | peerplays.blockchainobject module 2 | ================================= 3 | 4 | .. automodule:: peerplays.blockchainobject 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :inherited-members: 9 | -------------------------------------------------------------------------------- /docs/peerplays.bettingmarketgroup.rst: -------------------------------------------------------------------------------- 1 | peerplays.bettingmarketgroup module 2 | =================================== 3 | 4 | .. automodule:: peerplays.bettingmarketgroup 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :inherited-members: 9 | -------------------------------------------------------------------------------- /docs/peerplays.transactionbuilder.rst: -------------------------------------------------------------------------------- 1 | peerplays.transactionbuilder module 2 | =================================== 3 | 4 | .. automodule:: peerplays.transactionbuilder 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | :inherited-members: 9 | -------------------------------------------------------------------------------- /tests/.performance.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from locust import HttpUser, task 5 | 6 | class TestUser(HttpUser): 7 | @task 8 | def authenticate_task(self): 9 | self.client.get("/login") 10 | self.client.get("/register") 11 | -------------------------------------------------------------------------------- /docs/tutorials/index.rst: -------------------------------------------------------------------------------- 1 | ****************** 2 | Tutorials 3 | ****************** 4 | 5 | .. toctree:: 6 | :maxdepth: 1 7 | 8 | 9 | howto-build-peerplays 10 | howto-exchanges 11 | howto-trusted-node 12 | howto-json-rpc 13 | howto-monitor-blocks 14 | howto-decode-memo 15 | howto-blockproducer 16 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | scrypt==0.8.13 2 | appdirs==1.4.3 3 | graphenelib==1.5.4 4 | prettytable 5 | events 6 | pyyaml 7 | 8 | # for the CLI tool 9 | click 10 | treelib 11 | 12 | ecdsa==0.18 # not directly required, pinned by Snyk to avoid a vulnerability 13 | websockets>=10.0 # not directly required, pinned by Snyk to avoid a vulnerability 14 | -------------------------------------------------------------------------------- /docs/support.rst: -------------------------------------------------------------------------------- 1 | ********************* 2 | Support and Questions 3 | ********************* 4 | 5 | We have currently not setup a distinct channel for development around 6 | pypeerplays. However, many of the contributors are frequently reading 7 | through these channels: 8 | 9 | * https://peerplaystalk.org 10 | * https://t.me/PeerPlaysDEX 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | sudo: false 3 | python: 4 | - 3.5 5 | matrix: 6 | include: 7 | - env: TOXENV=py35 8 | python: 3.5 9 | - env: TOXENV=py36 10 | python: 3.6 11 | #- env: TOXENV=py37 12 | # python: 3.7 13 | install: 14 | - pip install tox-travis codecov 15 | script: 16 | - tox 17 | after_success: 18 | - codecov 19 | -------------------------------------------------------------------------------- /tests/test_witness.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from peerplays import PeerPlays 3 | from peerplays.witness import Witnesses 4 | 5 | 6 | class Testcases(unittest.TestCase): 7 | def test__contains__(self): 8 | witnesses = Witnesses(peerplays_instance=PeerPlays()) 9 | self.assertNotIn("committee-account", witnesses) 10 | self.assertNotIn("1.2.6", witnesses) 11 | self.assertNotIn("1.6.1000", witnesses) 12 | -------------------------------------------------------------------------------- /sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.projectKey=PBSA_python-peerplays 2 | sonar.organization=pbsa 3 | sonar.python.version=3 4 | 5 | # This is the name and version displayed in the SonarCloud UI. 6 | sonar.projectName=python-peerplays 7 | sonar.projectVersion=0.5.0 8 | 9 | # Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows. 10 | #sonar.sources=. 11 | 12 | # Encoding of the source code. Default is default system encoding 13 | #sonar.sourceEncoding=UTF-8 14 | -------------------------------------------------------------------------------- /peerplays/blockchainobject.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from .instance import BlockchainInstance 3 | from graphenecommon.blockchainobject import ( 4 | BlockchainObject as GrapheneBlockchainObject, 5 | BlockchainObjects as GrapheneBlockchainObjects, 6 | ObjectCache, 7 | ) 8 | 9 | 10 | @BlockchainInstance.inject 11 | class BlockchainObject(GrapheneBlockchainObject): 12 | pass 13 | 14 | 15 | @BlockchainInstance.inject 16 | class BlockchainObjects(GrapheneBlockchainObjects): 17 | pass 18 | -------------------------------------------------------------------------------- /tests/test_message.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import mock 3 | from peerplays import PeerPlays 4 | from peerplays.message import Message 5 | from peerplays.instance import set_shared_peerplays_instance 6 | from .fixtures import fixture_data, peerplays, core_unit 7 | 8 | 9 | class Testcases(unittest.TestCase): 10 | 11 | def setUp(self): 12 | fixture_data() 13 | 14 | """ 15 | def test_sign_message(self): 16 | p = Message("message foobar").sign() 17 | Message(p).verify() 18 | """ 19 | -------------------------------------------------------------------------------- /docs/installation.rst: -------------------------------------------------------------------------------- 1 | ************ 2 | Installation 3 | ************ 4 | 5 | Installation 6 | ############ 7 | 8 | Install with `pip`: 9 | 10 | :: 11 | 12 | $ sudo apt-get install libffi-dev libssl-dev python-dev 13 | $ pip3 install peerplays 14 | 15 | Manual installation: 16 | 17 | :: 18 | 19 | $ git clone https://github.com/xeroc/python-peerplays/ 20 | $ cd python-peerplays 21 | $ python3 setup.py install --user 22 | 23 | Upgrade 24 | ####### 25 | 26 | :: 27 | 28 | $ pip install --user --upgrade 29 | -------------------------------------------------------------------------------- /docs/configuration.rst: -------------------------------------------------------------------------------- 1 | ************* 2 | Configuration 3 | ************* 4 | 5 | The pypeerplays library comes with its own local configuration database 6 | that stores information like 7 | 8 | * API node URL 9 | * default account name 10 | * the encrypted master password 11 | 12 | and potentially more. 13 | 14 | You can access those variables like a regular dictionary by using 15 | 16 | .. code-block:: python 17 | 18 | from peerplays import PeerPlays 19 | peerplays = PeerPlays() 20 | print(peerplays.config.items()) 21 | 22 | Keys can be added and changed like they are for regular dictionaries. 23 | -------------------------------------------------------------------------------- /peerplays/bet.py: -------------------------------------------------------------------------------- 1 | from .exceptions import BetDoesNotExistException 2 | from .blockchainobject import BlockchainObject 3 | 4 | 5 | class Bet(BlockchainObject): 6 | """ Read data about a Bet on the chain 7 | 8 | :param str identifier: Identifier 9 | :param peerplays blockchain_instance: PeerPlays() instance to use when accesing a RPC 10 | 11 | """ 12 | 13 | type_id = 26 14 | 15 | def refresh(self): 16 | data = self.blockchain.rpc.get_object(self.identifier) 17 | if not data: 18 | raise BetDoesNotExistException(self.identifier) 19 | dict.__init__(self, data) 20 | -------------------------------------------------------------------------------- /peerplays/__init__.py: -------------------------------------------------------------------------------- 1 | from .peerplays import PeerPlays 2 | 3 | __all__ = [ 4 | "account", 5 | "aes", 6 | "amount", 7 | "asset", 8 | "bet", 9 | "bettingmarket", 10 | "bettingmarketgroup", 11 | "block", 12 | "blockchain", 13 | "committee", 14 | "event", 15 | "eventgroup", 16 | "exceptions", 17 | "instance", 18 | "memo", 19 | "peerplays", 20 | "proposal", 21 | "sport", 22 | "storage", 23 | "transactionbuilder", 24 | "utils", 25 | "wallet", 26 | "witness", 27 | "notify", 28 | "message", 29 | ] 30 | 31 | GRAPHENE_BETTING_ODDS_PRECISION = 10000 32 | -------------------------------------------------------------------------------- /examples/freeze_group.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from pprint import pprint 3 | from peerplays import PeerPlays 4 | 5 | ppy = PeerPlays( 6 | # this account creates the proposal 7 | proposer="init0", 8 | # Proposal needs to be approve within 1 hour 9 | proposal_expiration=60 * 5, 10 | # For testing, set this to true 11 | nobroadcast=False, 12 | # We want to bundle many operations into a single transaction 13 | bundle=True, 14 | ) 15 | ppy.wallet.unlock("") 16 | 17 | ppy.betting_market_group_freeze( 18 | "1.20.0", True 19 | ) 20 | 21 | # Broadcast the whole transaction 22 | pprint( 23 | ppy.txbuffer.broadcast() 24 | ) 25 | -------------------------------------------------------------------------------- /peerplays/committee.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from .account import Account 3 | from .instance import BlockchainInstance 4 | from graphenecommon.committee import Committee as GrapheneCommittee 5 | 6 | 7 | @BlockchainInstance.inject 8 | class Committee(GrapheneCommittee): 9 | """ Read data about a Committee Member in the chain 10 | 11 | :param str member: Name of the Committee Member 12 | :param instance blockchain_instance: instance to use when accesing a RPC 13 | :param bool lazy: Use lazy loading 14 | 15 | """ 16 | 17 | def define_classes(self): 18 | self.type_id = 5 19 | self.account_class = Account 20 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py39,docs 3 | skip_missing_interpreters = false 4 | 5 | [testenv] 6 | deps=-rrequirements-test.txt 7 | commands= 8 | 9 | peerplays set node wss://mint.peerplays.download/api 10 | coverage run -m pytest --ignore=tests/archived/ --ignore=tests/performance/ 11 | # coverage xml 12 | 13 | [coverage:run] 14 | relative_files = True 15 | source = my_project/ 16 | branch = True 17 | 18 | [testenv:lint] 19 | deps= 20 | flake8 21 | commands= 22 | flake8 peerplays* 23 | 24 | [testenv:docs] 25 | basepython= 26 | python3 27 | changedir= 28 | docs 29 | deps=-rdocs/requirements.txt 30 | sphinx 31 | commands= 32 | sphinx-build -b html ./ ./html 33 | -------------------------------------------------------------------------------- /peerplays/message.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from peerplaysbase.account import PublicKey 3 | from .account import Account 4 | from .instance import BlockchainInstance 5 | 6 | from graphenecommon.message import Message as GrapheneMessage, InvalidMessageSignature 7 | 8 | 9 | @BlockchainInstance.inject 10 | class Message(GrapheneMessage): 11 | MESSAGE_SPLIT = ( 12 | "-----BEGIN PEERPLAYS SIGNED MESSAGE-----", 13 | "-----BEGIN META-----", 14 | "-----BEGIN SIGNATURE-----", 15 | "-----END PEERPLAYS SIGNED MESSAGE-----", 16 | ) 17 | 18 | def define_classes(self): 19 | self.account_class = Account 20 | self.publickey_class = PublicKey 21 | -------------------------------------------------------------------------------- /examples/resolve_bet.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from pprint import pprint 3 | from peerplays import PeerPlays 4 | 5 | ppy = PeerPlays( 6 | # this account creates the proposal 7 | proposer="init0", 8 | # Proposal needs to be approve within 1 hour 9 | proposal_expiration=60 * 5, 10 | # For testing, set this to true 11 | nobroadcast=False, 12 | # We want to bundle many operations into a single transaction 13 | bundle=True, 14 | ) 15 | ppy.wallet.unlock("") 16 | 17 | ppy.betting_market_resolve( 18 | "1.20.0", 19 | [["1.21.257", "win"], ["1.21.258", "not_win"], ["1.21.259", "cancel"]] 20 | ) 21 | 22 | # Broadcast the whole transaction 23 | pprint( 24 | ppy.txbuffer.broadcast() 25 | ) 26 | -------------------------------------------------------------------------------- /docs/peerplays.cli.rst: -------------------------------------------------------------------------------- 1 | peerplays.cli package 2 | ===================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | .. toctree:: 8 | 9 | peerplays.cli.account 10 | peerplays.cli.asset 11 | peerplays.cli.bookie 12 | peerplays.cli.bos 13 | peerplays.cli.cli 14 | peerplays.cli.committee 15 | peerplays.cli.decorators 16 | peerplays.cli.info 17 | peerplays.cli.main 18 | peerplays.cli.message 19 | peerplays.cli.proposal 20 | peerplays.cli.rpc 21 | peerplays.cli.ui 22 | peerplays.cli.wallet 23 | peerplays.cli.witness 24 | 25 | Module contents 26 | --------------- 27 | 28 | .. automodule:: peerplays.cli 29 | :members: 30 | :undoc-members: 31 | :show-inheritance: 32 | :inherited-members: 33 | -------------------------------------------------------------------------------- /examples/cli-wallet-propose.py: -------------------------------------------------------------------------------- 1 | from peerplaysbase.transactions import formatTimeFromNow 2 | from pprint import pprint 3 | from grapheneapi.grapheneapi import GrapheneAPI 4 | rpc = GrapheneAPI("localhost", 8092) 5 | 6 | op = rpc.get_prototype_operation("betting_market_rules_create_operation") 7 | 8 | op[1]["name"] = [["en", "NHL Rules v1.0"]] 9 | op[1]["description"] = [["en", "The winner will be the team with the most points at the end of the game. The team with fewer points will not be the winner."]] 10 | 11 | handle = rpc.begin_builder_transaction() 12 | rpc.add_operation_to_builder_transaction(handle, op) 13 | rpc.set_fees_on_builder_transaction(handle, "1.3.0") 14 | pprint(rpc.propose_builder_transaction2(handle, "init0", formatTimeFromNow(60), 0, True)) 15 | -------------------------------------------------------------------------------- /peerplaysbase/signedtransactions.py: -------------------------------------------------------------------------------- 1 | from graphenebase.signedtransactions import ( 2 | Signed_Transaction as GrapheneSigned_Transaction, 3 | ) 4 | 5 | from .operations import Operation 6 | from .chains import known_chains 7 | 8 | 9 | class Signed_Transaction(GrapheneSigned_Transaction): 10 | """ Create a signed transaction and offer method to create the 11 | signature 12 | 13 | :param num refNum: parameter ref_block_num (see ``getBlockParams``) 14 | :param num refPrefix: parameter ref_block_prefix (see ``getBlockParams``) 15 | :param str expiration: expiration date 16 | :param Array operations: array of operations 17 | """ 18 | 19 | known_chains = known_chains 20 | default_prefix = "PPY" 21 | operation_klass = Operation 22 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md 3 | 4 | [aliases] 5 | test=pytest 6 | 7 | [coverage:run] 8 | #source=bitshares* 9 | omit= 10 | tests/* 11 | .tox/* 12 | .eggs/* 13 | 14 | [coverage:report] 15 | include=peerplays* 16 | ignore_errors=True 17 | show_missing=True 18 | 19 | [flake8] 20 | ignore = E501,F401 21 | exclude = 22 | # No need to traverse our git directory 23 | .git, 24 | # There's no value in checking cache directories 25 | __pycache__, 26 | # The conf file is mostly autogenerated, ignore it 27 | docs/conf.py, 28 | # The old directory contains Flake8 2.0 29 | old, 30 | # This contains our built documentation 31 | build, 32 | # This contains builds of flake8 that we don't want to check 33 | dist 34 | max-complexity = 14 35 | -------------------------------------------------------------------------------- /peerplays/storage.py: -------------------------------------------------------------------------------- 1 | from graphenestorage import ( 2 | InRamConfigurationStore, 3 | InRamEncryptedKeyStore, 4 | InRamPlainKeyStore, 5 | SqliteConfigurationStore, 6 | SqliteEncryptedKeyStore, 7 | SQLiteFile, 8 | SqlitePlainKeyStore, 9 | ) 10 | 11 | 12 | url = "wss://node.peerplays.download" 13 | InRamConfigurationStore.setdefault("node", url) 14 | SqliteConfigurationStore.setdefault("node", url) 15 | 16 | 17 | def get_default_config_store(*args, **kwargs): 18 | if "appname" not in kwargs: 19 | kwargs["appname"] = "peerplays" 20 | return SqliteConfigurationStore(*args, **kwargs) 21 | 22 | 23 | def get_default_key_store(config, *args, **kwargs): 24 | if "appname" not in kwargs: 25 | kwargs["appname"] = "peerplays" 26 | return SqliteEncryptedKeyStore(config=config, **kwargs) 27 | -------------------------------------------------------------------------------- /docs/tutorials/howto-exchanges.rst: -------------------------------------------------------------------------------- 1 | Howto Interface your Exchange with PeerPlays 2 | ============================================ 3 | 4 | This Howto serves as an introduction for exchanges that want to 5 | interface with PeerPlays to allow trading of assets from the PeerPlays 6 | network. 7 | 8 | We here start by introducing the overall concept of trusted node setup, 9 | having different APIs that reply in JSON and describe the structure of 10 | the received information (blocks etc). 11 | 12 | Afterwards, we will go into more detail w.r.t. to the python-peerplays 13 | library that helps you deal with the blockchain and can be seen as a 14 | full-featured wallet (to replace the cli-wallet). 15 | 16 | .. toctree:: 17 | 18 | howto-build-peerplays.rst 19 | howto-trusted-node 20 | ../rpc 21 | howto-json-rpc 22 | howto-monitor-blocks 23 | howto-decode-memo 24 | -------------------------------------------------------------------------------- /peerplaysbase/objecttypes.py: -------------------------------------------------------------------------------- 1 | ts = [ 2 | "null", 3 | "base", 4 | "account", 5 | "asset", 6 | "force_settlement", 7 | "committee_member", 8 | "witness", 9 | "limit_order", 10 | "call_order", 11 | "custom", 12 | "proposal", 13 | "operation_history", 14 | "withdraw_permission", 15 | "vesting_balance", 16 | "worker", 17 | "balance", 18 | "tournament", 19 | "tournament_details", 20 | "match", 21 | "game", 22 | "sport", 23 | "event_group", 24 | "event", 25 | "betting_market_rules", 26 | "betting_market_group", 27 | "betting_market", 28 | "bet" 29 | "bet", 30 | "custom_permission", 31 | "custom_account_authority", 32 | "offer_object_type", 33 | "nft_metadata_type", 34 | "nft_object_type", 35 | ] 36 | 37 | object_type = {k: ts.index(k) for k in ts} 38 | -------------------------------------------------------------------------------- /peerplaysbase/asset_permissions.py: -------------------------------------------------------------------------------- 1 | asset_permissions = {} 2 | asset_permissions["charge_market_fee"] = 0x01 3 | asset_permissions["white_list"] = 0x02 4 | asset_permissions["override_authority"] = 0x04 5 | asset_permissions["transfer_restricted"] = 0x08 6 | asset_permissions["disable_force_settle"] = 0x10 7 | asset_permissions["global_settle"] = 0x20 8 | asset_permissions["disable_confidential"] = 0x40 9 | asset_permissions["witness_fed_asset"] = 0x80 10 | asset_permissions["committee_fed_asset"] = 0x100 11 | 12 | 13 | def toint(permissions): 14 | permissions_int = 0 15 | for p in permissions: 16 | if permissions[p]: 17 | permissions_int += asset_permissions[p] 18 | return permissions_int 19 | 20 | 21 | def todict(number): 22 | r = {} 23 | for k, v in asset_permissions.items(): 24 | r[k] = bool(number & v) 25 | return r 26 | -------------------------------------------------------------------------------- /peerplays/cli/witness.py: -------------------------------------------------------------------------------- 1 | import click 2 | from pprint import pprint 3 | from .decorators import onlineChain, unlockWallet 4 | from .main import main 5 | 6 | 7 | @main.command() 8 | @click.pass_context 9 | @onlineChain 10 | @click.argument("witnesses", nargs=-1) 11 | @click.option("--account", help="Account that takes this action", type=str) 12 | @unlockWallet 13 | def approvewitness(ctx, witnesses, account): 14 | """ Approve witness(es) 15 | """ 16 | pprint(ctx.peerplays.approvewitness(witnesses, account=account)) 17 | 18 | 19 | @main.command() 20 | @click.pass_context 21 | @onlineChain 22 | @click.argument("witnesses", nargs=-1) 23 | @click.option("--account", help="Account that takes this action", type=str) 24 | @unlockWallet 25 | def disapprovewitness(ctx, witnesses, account): 26 | """ Disapprove witness(es) 27 | """ 28 | pprint(ctx.peerplays.disapprovewitness(witnesses, account=account)) 29 | -------------------------------------------------------------------------------- /peerplays/cli/committee.py: -------------------------------------------------------------------------------- 1 | import click 2 | from pprint import pprint 3 | from .decorators import onlineChain, unlockWallet 4 | from .main import main 5 | 6 | 7 | @main.command() 8 | @click.pass_context 9 | @onlineChain 10 | @click.argument("members", nargs=-1) 11 | @click.option("--account", help="Account that takes this action", type=str) 12 | @unlockWallet 13 | def approvecommittee(ctx, members, account): 14 | """ Approve committee member(s) 15 | """ 16 | pprint(ctx.peerplays.approvecommittee(members, account=account)) 17 | 18 | 19 | @main.command() 20 | @click.pass_context 21 | @onlineChain 22 | @click.argument("members", nargs=-1) 23 | @click.option("--account", help="Account that takes this action", type=str) 24 | @unlockWallet 25 | def disapprovecommittee(ctx, members, account): 26 | """ Disapprove committee member(s) 27 | """ 28 | pprint(ctx.peerplays.disapprovecommittee(members, account=account)) 29 | -------------------------------------------------------------------------------- /tests/test_fee.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from pprint import pprint 3 | from peerplays import PeerPlays 4 | from peerplays.instance import set_shared_peerplays_instance 5 | from .fixtures import fixture_data, peerplays 6 | 7 | 8 | class Testcases(unittest.TestCase): 9 | 10 | def setUp(self): 11 | fixture_data() 12 | 13 | """ 14 | Test has been temporary removed until proper CER is installed on chain! 15 | """ 16 | """ 17 | def test_fee_on_transfer(self): 18 | tx = peerplays.transfer("init1", 1, "1.3.0", account="init0", fee_asset="1.3.1") 19 | op = tx["operations"][0][1] 20 | self.assertEqual(op["fee"]["asset_id"], "1.3.1") 21 | 22 | def test_feeasset_on_transfer(self): 23 | tx = peerplays.transfer("init1", 1, "1.3.0", account="init0", fee_asset="BTF") 24 | op = tx["operations"][0][1] 25 | self.assertEqual(op["fee"]["asset_id"], "1.3.1") 26 | """ 27 | -------------------------------------------------------------------------------- /peerplays/wallet.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from peerplaysbase.account import PrivateKey 3 | from graphenecommon.wallet import Wallet as GrapheneWallet 4 | from graphenecommon.exceptions import ( 5 | InvalidWifError, 6 | KeyAlreadyInStoreException, 7 | KeyNotFound, 8 | NoWalletException, 9 | OfflineHasNoRPCException, 10 | WalletExists, 11 | WalletLocked, 12 | ) 13 | from graphenestorage.exceptions import WrongMasterPasswordException 14 | from .instance import BlockchainInstance 15 | 16 | 17 | @BlockchainInstance.inject 18 | class Wallet(GrapheneWallet): 19 | def define_classes(self): 20 | # identical to those in peerplays.py! 21 | self.default_key_store_app_name = "peerplays" 22 | self.privatekey_class = PrivateKey 23 | 24 | def create(self, pwd): 25 | try: 26 | self.newWallet(pwd) 27 | except WrongMasterPasswordException: 28 | raise WalletExists 29 | 30 | -------------------------------------------------------------------------------- /tests/test_base_objects.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from peerplays import PeerPlays, exceptions 3 | from peerplays.instance import set_shared_peerplays_instance 4 | from peerplays.account import Account 5 | from peerplays.committee import Committee 6 | from .fixtures import fixture_data, peerplays 7 | 8 | 9 | class Testcases(unittest.TestCase): 10 | 11 | def setUp(self): 12 | fixture_data() 13 | 14 | def test_Committee(self): 15 | """ Testing for Committee exceptions""" 16 | with self.assertRaises( 17 | exceptions.AccountDoesNotExistsException 18 | ): 19 | Committee("FOObarNonExisting") 20 | 21 | c = Committee("init2") 22 | self.assertEqual(c["id"], "1.5.2") 23 | self.assertIsInstance(c.account, Account) 24 | 25 | with self.assertRaises( 26 | exceptions.CommitteeMemberDoesNotExistsException 27 | ): 28 | Committee("1.5.10000") 29 | -------------------------------------------------------------------------------- /peerplays/blockchain.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from .block import Block 3 | from .instance import BlockchainInstance 4 | from peerplaysbase import operationids 5 | from graphenecommon.blockchain import Blockchain as GrapheneBlockchain 6 | 7 | 8 | @BlockchainInstance.inject 9 | class Blockchain(GrapheneBlockchain): 10 | """ This class allows to access the blockchain and read data 11 | from it 12 | 13 | :param instance blockchain_instance: instance 14 | :param str mode: (default) Irreversible block (``irreversible``) or 15 | actual head block (``head``) 16 | :param int max_block_wait_repetition: (default) 3 maximum wait time for 17 | next block ismax_block_wait_repetition * block_interval 18 | 19 | This class let's you deal with blockchain related data and methods. 20 | """ 21 | 22 | def define_classes(self): 23 | self.block_class = Block 24 | self.operationids = operationids 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | # Byte-compiled / optimized / DLL files 3 | __pycache__/ 4 | *.py[cod] 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | *.eggs 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | 45 | # Translations 46 | *.mo 47 | *.pot 48 | 49 | # Django stuff: 50 | *.log 51 | 52 | # Sphinx documentation 53 | docs/_build/ 54 | docs/html 55 | 56 | # PyBuilder 57 | target/ 58 | 59 | # Vim temp files 60 | *.swp 61 | 62 | .ropeproject/ 63 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean-pyc clean-build docs 2 | 3 | clean: clean-build clean-pyc 4 | 5 | clean-build: 6 | rm -fr build/ dist/ *.egg-info .eggs/ .tox/ __pycache__/ .cache/ .coverage htmlcov src 7 | rm -rf contrib/tmp/piston/ 8 | 9 | clean-pyc: 10 | find . -name '*.pyc' -exec rm -f {} + 11 | find . -name '*.pyo' -exec rm -f {} + 12 | find . -name '*~' -exec rm -f {} + 13 | 14 | lint: 15 | flake8 peerplays*/ 16 | 17 | test: 18 | tox 19 | 20 | build: 21 | python3 setup.py build 22 | 23 | install: build 24 | python3 setup.py install 25 | 26 | install-user: build 27 | python3 setup.py install --user 28 | 29 | git: 30 | git push --all 31 | git push --tags 32 | 33 | check: 34 | python3 setup.py check 35 | 36 | dist: 37 | python3 setup.py sdist upload -r pypi 38 | python3 setup.py bdist_wheel upload 39 | 40 | docs: 41 | SPHINX_APIDOC_OPTIONS="members,undoc-members,show-inheritance,inherited-members" sphinx-apidoc -d 6 -e -f -o docs peerplays* 42 | make -C docs clean html 43 | 44 | release: clean check dist git 45 | -------------------------------------------------------------------------------- /peerplays/cli/rpc.py: -------------------------------------------------------------------------------- 1 | import ast 2 | import click 3 | from pprint import pprint 4 | from .decorators import ( 5 | onlineChain, 6 | ) 7 | from .main import main 8 | 9 | 10 | @main.command() 11 | @click.pass_context 12 | @onlineChain 13 | @click.argument( 14 | 'call', 15 | nargs=1) 16 | @click.argument( 17 | 'arguments', 18 | nargs=-1) 19 | @click.option( 20 | "--api", 21 | default="database", 22 | help="Provide API node, if not 'database'", 23 | type=str) 24 | def rpc(ctx, call, arguments, api): 25 | """ Construct RPC call directly 26 | \b 27 | You can specify which API to send the call to: 28 | 29 | peerplays rpc --api bookie get_matched_bets_for_bettor 1.2.0 30 | 31 | You can also specify lists using 32 | 33 | peerplays rpc get_objects "['2.0.0', '2.1.0']" 34 | 35 | """ 36 | try: 37 | data = list(ast.literal_eval(d) for d in arguments) 38 | except: 39 | data = arguments 40 | ret = getattr(ctx.peerplays.rpc, call)(*data, api=api) 41 | pprint(ret) 42 | -------------------------------------------------------------------------------- /peerplaysapi/exceptions.py: -------------------------------------------------------------------------------- 1 | import re 2 | from grapheneapi.exceptions import RPCError 3 | 4 | 5 | def decodeRPCErrorMsg(e): 6 | """ Helper function to decode the raised Exception and give it a 7 | python Exception class 8 | """ 9 | found = re.search( 10 | ( 11 | "(10 assert_exception: Assert Exception\n|" 12 | "3030000 tx_missing_posting_auth)" 13 | ".*: (.*)\n" 14 | ), 15 | str(e), 16 | flags=re.M) 17 | if found: 18 | return found.group(2).strip() 19 | else: 20 | return str(e) 21 | 22 | 23 | class MissingRequiredActiveAuthority(RPCError): 24 | pass 25 | 26 | 27 | class NoMethodWithName(RPCError): 28 | pass 29 | 30 | 31 | class UnhandledRPCError(RPCError): 32 | pass 33 | 34 | 35 | class NumRetriesReached(Exception): 36 | pass 37 | 38 | 39 | class OperationInProposalExistsException(Exception): 40 | """ An operation in a proposal was already seen by the API node and will 41 | not be accepted for rebroadcast 42 | """ 43 | pass 44 | -------------------------------------------------------------------------------- /peerplays/proposal.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from .account import Account 3 | from .instance import BlockchainInstance 4 | from graphenecommon.proposal import ( 5 | Proposal as GrapheneProposal, 6 | Proposals as GrapheneProposals, 7 | ) 8 | 9 | 10 | @BlockchainInstance.inject 11 | class Proposal(GrapheneProposal): 12 | """ Read data about a Proposal Balance in the chain 13 | 14 | :param str id: Id of the proposal 15 | :param peerplays blockchain_instance: peerplays() instance to use when accesing a RPC 16 | 17 | """ 18 | 19 | def define_classes(self): 20 | self.type_id = 10 21 | self.account_class = Account 22 | 23 | 24 | @BlockchainInstance.inject 25 | class Proposals(GrapheneProposals): 26 | """ Obtain a list of pending proposals for an account 27 | 28 | :param str account: Account name 29 | :param peerplays blockchain_instance: peerplays() instance to use when accesing a RPC 30 | """ 31 | 32 | def define_classes(self): 33 | self.account_class = Account 34 | self.proposal_class = Proposal 35 | -------------------------------------------------------------------------------- /peerplays/cli/asset.py: -------------------------------------------------------------------------------- 1 | from .decorators import ( 2 | onlineChain, 3 | ) 4 | 5 | import click 6 | 7 | from peerplays.asset import Asset 8 | from peerplays.exceptions import AssetDoesNotExistsException 9 | 10 | from prettytable import PrettyTable 11 | 12 | from .main import main 13 | 14 | 15 | @main.command() 16 | @click.pass_context 17 | @onlineChain 18 | def assets(ctx): 19 | "List Assets" 20 | MAX_ASSET = 100000 21 | assets = [] 22 | for i in range(0, MAX_ASSET): 23 | try: 24 | assets.append(Asset("1.3.{}".format(i))) 25 | except AssetDoesNotExistsException: 26 | break 27 | 28 | assetTable = PrettyTable() 29 | assetTable.field_names = ["ID", "Symbol", "Precision", "Description", "Max Supply"] 30 | 31 | for i in range (0, len(assets)): 32 | try: 33 | description = assets[i].description 34 | if description == "": 35 | description = "--" 36 | except AttributeError: 37 | description = "--" 38 | assetTable.add_row([assets[i].id, assets[i].symbol, assets[i].precision, description, assets[i].max_supply["amount"]]) 39 | 40 | click.echo(assetTable) -------------------------------------------------------------------------------- /docs/peerplays.rst: -------------------------------------------------------------------------------- 1 | peerplays package 2 | ================= 3 | 4 | Subpackages 5 | ----------- 6 | 7 | .. toctree:: 8 | 9 | peerplays.cli 10 | 11 | Submodules 12 | ---------- 13 | 14 | .. toctree:: 15 | 16 | peerplays.account 17 | peerplays.amount 18 | peerplays.asset 19 | peerplays.bet 20 | peerplays.bettingmarket 21 | peerplays.bettingmarketgroup 22 | peerplays.block 23 | peerplays.blockchain 24 | peerplays.blockchainobject 25 | peerplays.committee 26 | peerplays.event 27 | peerplays.eventgroup 28 | peerplays.exceptions 29 | peerplays.genesisbalance 30 | peerplays.instance 31 | peerplays.market 32 | peerplays.memo 33 | peerplays.message 34 | peerplays.notify 35 | peerplays.peerplays 36 | peerplays.peerplays2 37 | peerplays.price 38 | peerplays.proposal 39 | peerplays.rule 40 | peerplays.son 41 | peerplays.sport 42 | peerplays.storage 43 | peerplays.transactionbuilder 44 | peerplays.utils 45 | peerplays.wallet 46 | peerplays.witness 47 | 48 | Module contents 49 | --------------- 50 | 51 | .. automodule:: peerplays 52 | :members: 53 | :undoc-members: 54 | :show-inheritance: 55 | :inherited-members: 56 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Peerplays Blockchain Standards Association 4 | Copyright (c) 2017-2018 ChainSquad GmbH 5 | Copyright (c) 2015-2016 Fabian Schuh 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /peerplays/block.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from .instance import BlockchainInstance 3 | from graphenecommon.block import ( 4 | Block as GrapheneBlock, 5 | BlockHeader as GrapheneBlockHeader, 6 | ) 7 | 8 | 9 | @BlockchainInstance.inject 10 | class Block(GrapheneBlock): 11 | """ Read a single block from the chain 12 | 13 | :param int block: block number 14 | :param instance blockchain_instance: blockchain instance 15 | :param bool lazy: Use lazy loading 16 | 17 | Instances of this class are dictionaries that come with additional 18 | methods (see below) that allow dealing with a block and it's 19 | corresponding functions. 20 | 21 | .. code-block:: python 22 | 23 | from .block import Block 24 | block = Block(1) 25 | print(block) 26 | 27 | .. note:: This class comes with its own caching function to reduce the 28 | load on the API server. Instances of this class can be 29 | refreshed with ``Account.refresh()``. 30 | 31 | """ 32 | 33 | def define_classes(self): 34 | self.type_id = "-none-" 35 | 36 | 37 | @BlockchainInstance.inject 38 | class BlockHeader(GrapheneBlockHeader): 39 | def define_classes(self): 40 | self.type_id = "-none-" 41 | -------------------------------------------------------------------------------- /peerplays/witness.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from .account import Account 3 | from .blockchainobject import BlockchainObject 4 | from .instance import BlockchainInstance 5 | from graphenecommon.witness import ( 6 | Witness as GrapheneWitness, 7 | Witnesses as GrapheneWitnesses, 8 | ) 9 | 10 | 11 | @BlockchainInstance.inject 12 | class Witness(GrapheneWitness): 13 | """ Read data about a witness in the chain 14 | 15 | :param str account_name: Name of the witness 16 | :param peerplays blockchain_instance: peerplays() instance to use when 17 | accesing a RPC 18 | 19 | """ 20 | 21 | def define_classes(self): 22 | self.account_class = Account 23 | self.type_ids = [6, 2] 24 | 25 | 26 | @BlockchainInstance.inject 27 | class Witnesses(GrapheneWitnesses): 28 | """ Obtain a list of **active** witnesses and the current schedule 29 | 30 | :param bool only_active: (False) Only return witnesses that are 31 | actively producing blocks 32 | :param peerplays blockchain_instance: peerplays() instance to use when 33 | accesing a RPC 34 | """ 35 | 36 | def define_classes(self): 37 | self.account_class = Account 38 | self.witness_class = Witness 39 | self.blockchain_object_class = BlockchainObject 40 | -------------------------------------------------------------------------------- /peerplays/cli/message.py: -------------------------------------------------------------------------------- 1 | import click 2 | from peerplays.message import Message 3 | from .decorators import onlineChain, unlockWallet 4 | from .main import main 5 | 6 | 7 | @main.group() 8 | @click.pass_context 9 | def message(ctx): 10 | pass 11 | 12 | 13 | @message.command() 14 | @click.pass_context 15 | @onlineChain 16 | @unlockWallet 17 | @click.option("--account", type=str, help="Account to use") 18 | @click.option("--file", type=click.File("r")) 19 | def sign(ctx, file, account): 20 | """ Sign a message with an account 21 | """ 22 | if not file: 23 | # click.echo("Prompting for message. Terminate with CTRL-D") 24 | file = click.get_text_stream("stdin") 25 | m = Message(file.read(), peerplays_instance=ctx.peerplays) 26 | click.echo(m.sign(account)) 27 | 28 | 29 | @message.command() 30 | @click.pass_context 31 | @onlineChain 32 | @click.option("--account", type=str, help="Account to use") 33 | @click.option("--file", type=click.File("r")) 34 | def verify(ctx, file, account): 35 | """ Verify a signed message 36 | """ 37 | if not file: 38 | # click.echo("Prompting for message. Terminate with CTRL-D") 39 | file = click.get_text_stream("stdin") 40 | m = Message(file.read(), peerplays_instance=ctx.peerplays) 41 | click.echo("Verified" if m.verify() else "not verified") 42 | -------------------------------------------------------------------------------- /peerplays/genesisbalance.py: -------------------------------------------------------------------------------- 1 | from .account import Account 2 | from .instance import BlockchainInstance 3 | from graphenecommon.genesisbalance import ( 4 | GenesisBalance as GrapheneGenesisBalance, 5 | GenesisBalances as GrapheneGenesisBalances, 6 | ) 7 | 8 | from peerplaysbase.account import Address, PublicKey 9 | from peerplaysbase import operations 10 | 11 | 12 | @BlockchainInstance.inject 13 | class GenesisBalance(GrapheneGenesisBalance): 14 | """ Read data about a Committee Member in the chain 15 | 16 | :param str member: Name of the Committee Member 17 | :param peerplays blockchain_instance: PeerPlays() instance to use when 18 | accesing a RPC 19 | :param bool lazy: Use lazy loading 20 | 21 | """ 22 | 23 | type_id = 15 24 | 25 | def define_classes(self): 26 | self.account_class = Account 27 | self.operations = operations 28 | self.address_class = Address 29 | self.publickey_class = PublicKey 30 | 31 | 32 | @BlockchainInstance.inject 33 | class GenesisBalances(GrapheneGenesisBalances): 34 | """ List genesis balances that can be claimed from the 35 | keys in the wallet 36 | """ 37 | 38 | def define_classes(self): 39 | self.genesisbalance_class = GenesisBalance 40 | self.publickey_class = PublicKey 41 | self.address_class = Address 42 | -------------------------------------------------------------------------------- /tests/test_rbac_small.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from pprint import pprint 3 | from peerplays import PeerPlays 4 | from peerplaysbase.operationids import getOperationNameForId 5 | from peerplays.instance import set_shared_peerplays_instance 6 | from .fixtures import fixture_data, peerplays, core_unit 7 | 8 | 9 | class Testcases(unittest.TestCase): 10 | 11 | def setUp(self): 12 | fixture_data() 13 | self.testperm = "testperm1" 14 | self.clean_previous_test() 15 | 16 | def clean_previous_test(self): 17 | custom_permissions_existing = peerplays.rpc.get_custom_permissions("1.2.7") 18 | for permission in custom_permissions_existing: 19 | if permission["permission_name"] == self.testperm: 20 | permission_id = permission['id'] 21 | peerplays.custom_permission_delete( 22 | permission_id, 23 | owner_account="1.2.7") 24 | print(self.testperm, " deleted!") 25 | print("Cleaning done!") 26 | 27 | 28 | def test_custom_permission_create(self): 29 | peerplays.custom_permission_create( 30 | self.testperm, 31 | owner_account="1.2.7", 32 | weight_threshold=1, 33 | account_auths=[["1.2.8", 1]]) 34 | self.assertEqual(1, 1) 35 | 36 | if __name__ == "__main__": 37 | unittest.main() 38 | -------------------------------------------------------------------------------- /tests/test_cachedlist.py: -------------------------------------------------------------------------------- 1 | import time 2 | import unittest 3 | from mock import MagicMock 4 | from peerplays import PeerPlays 5 | from peerplays.sport import Sports 6 | from peerplays.eventgroup import EventGroups 7 | from peerplays.event import Events 8 | from peerplays.bettingmarketgroup import BettingMarketGroups 9 | from peerplays.bettingmarket import BettingMarkets 10 | import logging 11 | 12 | logging.basicConfig(level=logging.DEBUG) 13 | 14 | 15 | class Testcases(unittest.TestCase): 16 | def test_evg(self): 17 | EventGroups("1.20.0") 18 | EventGroups("1.20.0") 19 | 20 | Sports() 21 | Sports() 22 | 23 | Events("1.21.12") 24 | Events("1.21.12") 25 | 26 | BettingMarketGroups("1.22.12") 27 | BettingMarketGroups("1.22.12") 28 | 29 | BettingMarkets("1.24.241") 30 | BettingMarkets("1.24.241") 31 | 32 | """ 33 | def test_proposals(self): 34 | from peerplays.proposal import Proposals 35 | 36 | Proposals("witness-account") 37 | Proposals("witness-account") 38 | time.sleep(11) 39 | Proposals("witness-account") 40 | 41 | def test_bms(self): 42 | from peerplays.bettingmarket import BettingMarkets 43 | 44 | BettingMarkets("1.20.0") 45 | BettingMarkets("1.20.0") 46 | time.sleep(11) 47 | BettingMarkets("1.20.0") 48 | """ 49 | -------------------------------------------------------------------------------- /peerplays/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from .exceptions import ObjectNotInProposalBuffer 3 | from .instance import BlockchainInstance 4 | 5 | # Load methods from graphene and provide them here 6 | from graphenecommon.utils import ( 7 | formatTime, 8 | timeFormat, 9 | formatTimeString, 10 | formatTimeFromNow, 11 | parse_time, 12 | assets_from_string, 13 | ) 14 | 15 | 16 | def test_proposal_in_buffer(buf, operation_name, id): 17 | from .transactionbuilder import ProposalBuilder 18 | from peerplaysbase.operationids import operations 19 | 20 | assert isinstance(buf, ProposalBuilder) 21 | 22 | operationid = operations.get(operation_name) 23 | _, _, j = id.split(".") 24 | 25 | ops = buf.list_operations() 26 | if len(ops) <= int(j): 27 | raise ObjectNotInProposalBuffer( 28 | "{} with id {} not found".format(operation_name, id) 29 | ) 30 | op = ops[int(j)].json() 31 | if op[0] != operationid: 32 | raise ObjectNotInProposalBuffer( 33 | "{} with id {} not found".format(operation_name, id) 34 | ) 35 | 36 | 37 | def map2dict(darray): 38 | """ Reformat a list of maps to a dictionary 39 | """ 40 | return {v[0]: v[1] for v in darray} 41 | 42 | 43 | def dList2Dict(l): 44 | return map2dict(l) 45 | 46 | 47 | def dict2dList(l): 48 | return [[k, v] for k, v in l.items()] 49 | -------------------------------------------------------------------------------- /peerplays/cli/bos.py: -------------------------------------------------------------------------------- 1 | import click 2 | from pprint import pprint 3 | from prettytable import PrettyTable 4 | from .decorators import onlineChain, unlockWallet, customchain 5 | from .main import main 6 | from .ui import pretty_print 7 | 8 | from peerplays.account import Account 9 | from peerplays.asset import Asset 10 | from peerplays.sport import Sport, Sports 11 | from peerplays.eventgroup import EventGroup, EventGroups 12 | from peerplays.event import Event, Events 13 | from peerplays.bettingmarketgroup import BettingMarketGroup, BettingMarketGroups 14 | from peerplays.rule import Rule, Rules 15 | from peerplays.proposal import Proposals 16 | 17 | 18 | @main.group() 19 | def bos(): 20 | """ BOS related calls 21 | """ 22 | pass 23 | 24 | 25 | @bos.command() 26 | @click.pass_context 27 | @click.argument("eventids", nargs=-1) 28 | @customchain(bundle=True) 29 | @unlockWallet 30 | def cancel_event(ctx, eventids): 31 | for eventid in eventids: 32 | Event(eventid) 33 | ctx.blockchain.event_update_status(eventid, "canceled") 34 | click.echo(ctx.broadcast()) 35 | 36 | 37 | @bos.command() 38 | @click.pass_context 39 | @click.argument("account") 40 | @onlineChain 41 | @unlockWallet 42 | def approve_all(ctx, account): 43 | proposals = Proposals(account) 44 | proposal_ids = {x["id"] for x in proposals} 45 | click.echo(ctx.peerplays.approveproposal(proposal_ids, account=account)) 46 | -------------------------------------------------------------------------------- /tests/test_asset.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from peerplays import PeerPlays 3 | from peerplays.asset import Asset 4 | from peerplays.instance import set_shared_peerplays_instance 5 | from peerplays.exceptions import AssetDoesNotExistsException 6 | from .fixtures import fixture_data, peerplays 7 | 8 | 9 | class Testcases(unittest.TestCase): 10 | 11 | def setUp(self): 12 | fixture_data() 13 | 14 | def test_assert(self): 15 | with self.assertRaises(AssetDoesNotExistsException): 16 | Asset("FOObarNonExisting", full=False) 17 | 18 | def test_refresh(self): 19 | asset = Asset("1.3.0", full=False) 20 | asset.ensure_full() 21 | self.assertIn("dynamic_asset_data", asset) 22 | self.assertIn("flags", asset) 23 | self.assertIn("permissions", asset) 24 | self.assertIsInstance(asset["flags"], dict) 25 | self.assertIsInstance(asset["permissions"], dict) 26 | 27 | def test_properties(self): 28 | asset = Asset("1.3.0", full=False) 29 | self.assertIsInstance(asset.symbol, str) 30 | self.assertIsInstance(asset.precision, int) 31 | self.assertIsInstance(asset.is_bitasset, bool) 32 | self.assertIsInstance(asset.permissions, dict) 33 | self.assertEqual(asset.permissions, asset["permissions"]) 34 | self.assertIsInstance(asset.flags, dict) 35 | self.assertEqual(asset.flags, asset["flags"]) 36 | -------------------------------------------------------------------------------- /docs/tutorials/howto-json-rpc.rst: -------------------------------------------------------------------------------- 1 | ********************************** 2 | Interfacing via RPC and Websockets 3 | ********************************** 4 | 5 | Overview 6 | ======== 7 | 8 | APIs are separated into two categories, namely 9 | 10 | * the **Blockchain API** which is used to query blockchain data (account, assets, trading history, etc.) and 11 | * the **CLI Wallet API** which has your private keys loaded and is required when interacting with the blockchain with new transactions. 12 | 13 | Blockchain API 14 | -------------- 15 | 16 | The blockchain API (as provided by the ``witness_node`` application), 17 | allows to read the blockchain. 18 | 19 | .. code-block:: python 20 | 21 | from peerplaysapi.node import PeerPlaysNodeRPC 22 | ppy = PeerPlaysNodeRPC("wss://hostname") 23 | print(ppy.get_account_by_name("init0")) 24 | print(ppy.get_block(1)) 25 | 26 | 27 | .. note:: It is important to understand that the blockchain API does not 28 | know about private keys, and cannot sign transactions for you. 29 | All it does is validate and broadcast transactions to the P2P 30 | network. 31 | 32 | CLI Wallet API 33 | -------------- 34 | 35 | The cli-wallet api, as provided by the ``cli_wallet`` binary, allows to 36 | **create and sign transactions** and broadcast them. 37 | 38 | .. code-block:: python 39 | 40 | from peerplaysapi.wallet import PeerPlaysWalletRPC 41 | rpc = PeerPlaysWalletRPC("localhost", 8090) 42 | print(rpc.info()) 43 | -------------------------------------------------------------------------------- /peerplays/asset.py: -------------------------------------------------------------------------------- 1 | import json 2 | from peerplaysbase.asset_permissions import todict 3 | from .exceptions import AssetDoesNotExistsException 4 | from .instance import BlockchainInstance 5 | 6 | from graphenecommon.asset import Asset as GrapheneAsset 7 | 8 | 9 | @BlockchainInstance.inject 10 | class Asset(GrapheneAsset): 11 | """ Deals with Assets of the network. 12 | 13 | :param str Asset: Symbol name or object id of an asset 14 | :param bool lazy: Lazy loading 15 | :param bool full: Also obtain bitasset-data and dynamic asset data 16 | :param instance blockchain_instance: Instance of blockchain 17 | :returns: All data of an asset 18 | :rtype: dict 19 | 20 | .. note:: This class comes with its own caching function to reduce the 21 | load on the API server. Instances of this class can be 22 | refreshed with ``Asset.refresh()``. 23 | """ 24 | 25 | def define_classes(self): 26 | self.type_id = 3 27 | 28 | def __init__(self, *args, **kwargs): 29 | super().__init__(*args, **kwargs) 30 | 31 | # Permissions and flags 32 | self["permissions"] = todict(self["options"].get("issuer_permissions")) 33 | self["flags"] = todict(self["options"].get("flags")) 34 | try: 35 | self["description"] = json.loads(self["options"]["description"]) 36 | except Exception: 37 | self["description"] = self["options"]["description"] 38 | -------------------------------------------------------------------------------- /peerplays/sport.py: -------------------------------------------------------------------------------- 1 | from .instance import BlockchainInstance 2 | from .exceptions import SportDoesNotExistException 3 | from .blockchainobject import BlockchainObject, BlockchainObjects 4 | 5 | 6 | class Sport(BlockchainObject): 7 | """ Read data about a sport on the chain 8 | 9 | :param str identifier: Identifier 10 | :param peerplays blockchain_instance: PeerPlays() instance to use when 11 | accesing a RPC 12 | 13 | """ 14 | 15 | type_id = 20 16 | 17 | def refresh(self): 18 | data = self.blockchain.rpc.get_object(self.identifier) 19 | if not data: 20 | raise SportDoesNotExistException(self.identifier) 21 | super(Sport, self).__init__(data) 22 | 23 | @property 24 | def eventgroups(self): 25 | from .eventgroup import EventGroups 26 | 27 | return EventGroups(self["id"]) 28 | 29 | 30 | class Sports(BlockchainObjects, BlockchainInstance): 31 | """ List of all available sports 32 | """ 33 | 34 | def __init__(self, *args, **kwargs): 35 | super().__init__(self, *args, **kwargs) 36 | 37 | def refresh(self, *args, **kargs): 38 | self._sports = self.blockchain.rpc.list_sports() 39 | self.store( 40 | [ 41 | Sport(x, lazy=False, blockchain_instance=self.blockchain) 42 | for x in self._sports 43 | ] 44 | ) 45 | 46 | @property 47 | def sports(self): 48 | """ DEPRECATED 49 | """ 50 | return list(self) 51 | -------------------------------------------------------------------------------- /peerplaysbase/types.py: -------------------------------------------------------------------------------- 1 | from binascii import unhexlify 2 | from graphenebase.types import varint 3 | import struct 4 | 5 | 6 | def zigzag_encode(i): 7 | return (i >> 31) ^ (i << 1) 8 | 9 | 10 | def zigzag_decode(i): 11 | return (i >> 1) ^ -(i & 1) 12 | 13 | 14 | class Signed_Int(): 15 | def __init__(self, d): 16 | self.data = int(d) 17 | 18 | def __bytes__(self): 19 | return varint(zigzag_encode(self.data)) 20 | 21 | def __str__(self): 22 | return '%d' % self.data 23 | 24 | 25 | class Enum(Signed_Int): 26 | def __init__(self, selection): 27 | assert selection in self.options or \ 28 | isinstance(selection, int) and len(self.options) < selection, \ 29 | "Options are %s. Given '%s'" % ( 30 | self.options, selection) 31 | if selection in self.options: 32 | super().__init__(self.options.index(selection)) 33 | else: 34 | super().__init__(selection) 35 | 36 | def __str__(self): 37 | return str(self.options[self.data]) 38 | 39 | 40 | class Sha256(): 41 | def __init__(self, d): 42 | self.data = d 43 | assert len(self.data) == 64 44 | 45 | def __bytes__(self): 46 | return unhexlify(bytes(self.data, 'ascii')) 47 | 48 | def __str__(self): 49 | return str(self.data) 50 | 51 | 52 | class Double(): 53 | def __init__(self, d): 54 | self.data = float(d) 55 | 56 | def __bytes__(self): 57 | return struct.pack(" 4 | ### Master: 5 | 6 | [![docs master](https://readthedocs.org/projects/python-peerplays/badge/?version=master)](http://python-peerplays.readthedocs.io/en/latest/) 7 | [![Build Status](https://travis-ci.org/PBSA/python-peerplays.svg?branch=master)](https://travis-ci.org/PBSA/python-peerplays) 8 | [![codecov](https://codecov.io/gh/pbsa/python-peerplays/branch/master/graph/badge.svg)](https://codecov.io/gh/pbsa/python-peerplays) 9 | 10 | ### Develop: 11 | 12 | [![docs master](https://readthedocs.org/projects/python-peerplays/badge/?version=develop)](http://python-peerplays.readthedocs.io/en/latest/) 13 | [![Build Status](https://travis-ci.org/PBSA/python-peerplays.svg?branch=develop)](https://travis-ci.org/PBSA/python-peerplays) 14 | [![codecov](https://codecov.io/gh/pbsa/python-peerplays/branch/develop/graph/badge.svg)](https://codecov.io/gh/pbsa/python-peerplays) 15 | 16 | 17 | This is a communications library which allows interface with the Peerplays blockchain directly and without the need for a cli_wallet. It provides a wallet interface and can construct any kind of transactions and properly sign them for broadcast. 18 | 19 | When installed with pip3, also provides a command-line interface invocable at the command line as `peerplays`. 20 | 21 | ## Installation 22 | 23 | The python-peerplays library has following dependencies 24 | python3-dev 25 | build-essential 26 | libssl-dev 27 | libffi-dev 28 | libxml2-dev 29 | libxslt1-dev 30 | zlib1g-dev 31 | 32 | Make sure that the above dependencies are installed, if not install with: 33 | 34 | $ sudo apt-get install 35 | 36 | Install with `pip3`: 37 | 38 | $ pip3 install peerplays 39 | 40 | -------------------------------------------------------------------------------- /peerplays/rule.py: -------------------------------------------------------------------------------- 1 | from .instance import BlockchainInstance 2 | from .exceptions import RuleDoesNotExistException 3 | from .blockchainobject import BlockchainObject, BlockchainObjects 4 | 5 | 6 | class Rule(BlockchainObject): 7 | """ Read data about a Rule object 8 | 9 | :param str identifier: Identifier for the rule 10 | :param peerplays blockchain_instance: PeerPlays() instance to use 11 | when accesing a RPC 12 | 13 | """ 14 | 15 | type_id = 23 16 | 17 | def refresh(self): 18 | rule = self.blockchain.rpc.get_object(self.identifier) 19 | if not rule: 20 | raise RuleDoesNotExistException 21 | super().__init__(rule) 22 | 23 | @property 24 | def grading(self): 25 | import json 26 | from .utils import map2dict 27 | 28 | desc = map2dict(self["description"]) 29 | assert "grading" in desc, "Rule {} has no grading!".format(self["id"]) 30 | grading = json.loads(desc.get("grading", {})) 31 | assert "metric" in grading 32 | assert "resolutions" in grading 33 | return grading 34 | 35 | 36 | class Rules(BlockchainObjects, BlockchainInstance): 37 | """ List of all Rules 38 | """ 39 | 40 | def __init__(self, *args, limit=1000, **kwargs): 41 | self.limit = limit 42 | super().__init__(self, *args, **kwargs) 43 | 44 | def refresh(self, *args, **kwargs): 45 | self.rules = self.blockchain.rpc.get_objects( 46 | ["1.23.{}".format(id) for id in range(0, self.limit)] 47 | ) 48 | self.store( 49 | [ 50 | Rule(x, lazy=False, blockchain_instance=self.blockchain) 51 | for x in self.rules 52 | if x 53 | ] 54 | ) 55 | -------------------------------------------------------------------------------- /docs/contribute.rst: -------------------------------------------------------------------------------- 1 | Contributing to python-peerplays 2 | ================================ 3 | 4 | We welcome your contributions to our project. 5 | 6 | Repository 7 | ---------- 8 | 9 | The *main* repository of python-peerplays is currently located at: 10 | 11 | https://github.com/peerplays-network/python-peerplays 12 | 13 | Flow 14 | ---- 15 | 16 | This project makes heavy use of `git flow `_. 17 | If you are not familiar with it, then the most important thing for your 18 | to understand is that: 19 | 20 | pull requests need to be made against the develop branch 21 | 22 | How to Contribute 23 | ----------------- 24 | 25 | 0. Familiarize yourself with `contributing on github ` 26 | 1. Fork or branch from the master. 27 | 2. Create commits following the commit style 28 | 3. Start a pull request to the master branch 29 | 4. Wait for a @xeroc or another member to review 30 | 31 | Issues 32 | ------ 33 | 34 | Feel free to submit issues and enhancement requests. 35 | 36 | Contributing 37 | ------------ 38 | 39 | Please refer to each project's style guidelines and guidelines for 40 | submitting patches and additions. In general, we follow the 41 | "fork-and-pull" Git workflow. 42 | 43 | 1. **Fork** the repo on GitHub 44 | 2. **Clone** the project to your own machine 45 | 3. **Commit** changes to your own branch 46 | 4. **Push** your work back up to your fork 47 | 5. Submit a **Pull request** so that we can review your changes 48 | 49 | NOTE: Be sure to merge the latest from "upstream" before making a pull 50 | request! 51 | 52 | Copyright and Licensing 53 | ----------------------- 54 | 55 | This library is open sources under the MIT license. We require your to 56 | release your code under that license as well. 57 | -------------------------------------------------------------------------------- /peerplays/eventgroup.py: -------------------------------------------------------------------------------- 1 | from peerplays.instance import BlockchainInstance 2 | from .exceptions import EventGroupDoesNotExistException 3 | from .blockchainobject import BlockchainObject, BlockchainObjects 4 | 5 | 6 | class EventGroup(BlockchainObject): 7 | """ Read data about an event group on the chain 8 | 9 | :param str identifier: Identifier 10 | :param peerplays blockchain_instance: PeerPlays() instance to use when 11 | accesing a RPC 12 | 13 | """ 14 | 15 | type_id = 21 16 | 17 | def refresh(self): 18 | data = self.blockchain.rpc.get_object(self.identifier) 19 | if not data: 20 | raise EventGroupDoesNotExistException(self.identifier) 21 | super(EventGroup, self).__init__(data) 22 | self.cached = True 23 | 24 | @property 25 | def sport(self): 26 | from .sport import Sport 27 | 28 | return Sport(self["sport_id"]) 29 | 30 | @property 31 | def events(self): 32 | from .event import Events 33 | 34 | return Events(self["id"]) 35 | 36 | 37 | class EventGroups(BlockchainObjects, BlockchainInstance): 38 | """ List of all available EventGroups 39 | 40 | :param str sport_id: Sport ID (``1.20.xxx``) 41 | """ 42 | 43 | def __init__(self, sport_id, *args, **kwargs): 44 | self.sport_id = sport_id 45 | BlockchainInstance.__init__(self, *args, **kwargs) 46 | BlockchainObjects.__init__(self, sport_id, *args, **kwargs) 47 | 48 | def refresh(self, *args, **kwargs): 49 | self.eventgroups = self.blockchain.rpc.list_event_groups(self.sport_id) 50 | self.store( 51 | [ 52 | EventGroup(x, lazy=False, blockchain_instance=self.blockchain) 53 | for x in self.eventgroups 54 | ], 55 | self.sport_id, 56 | ) 57 | -------------------------------------------------------------------------------- /peerplays/cli/main.py: -------------------------------------------------------------------------------- 1 | import click 2 | from .ui import print_version 3 | 4 | 5 | @click.group() 6 | @click.option( 7 | "--debug/--no-debug", 8 | default=False, 9 | help="Enable/Disable Debugging (no-broadcasting mode)", 10 | ) 11 | @click.option("--node", type=str, help="Websocket URL for public Peerplays API") 12 | @click.option( 13 | "--rpcuser", type=str, help="Websocket user if authentication is required" 14 | ) 15 | @click.option( 16 | "--rpcpassword", type=str, help="Websocket password if authentication is required" 17 | ) 18 | @click.option( 19 | "--nobroadcast/--broadcast", "-d", default=False, help="Do not broadcast anything" 20 | ) 21 | @click.option( 22 | "--unsigned/--signed", 23 | "-x", 24 | default=False, 25 | help="Do not try to sign the transaction", 26 | ) 27 | @click.option("--proposer", help="Propose transaction with this account", type=str) 28 | @click.option( 29 | "--proposal_review", 30 | help="Propose review time in seconds (defaults to 0)", 31 | type=int, 32 | default=0, 33 | ) 34 | @click.option( 35 | "--proposal_expiration", 36 | help="Propose expiration time in seconds (defaults to 24h)", 37 | type=int, 38 | default=60 * 60 * 24, 39 | ) 40 | @click.option( 41 | "--expiration", "-e", default=30, help="Expiration time in seconds (defaults to 30)" 42 | ) 43 | @click.option("--verbose", "-v", type=int, default=3, help="Verbosity (0-15)") 44 | @click.option( 45 | "--version", 46 | is_flag=True, 47 | callback=print_version, 48 | expose_value=False, 49 | is_eager=True, 50 | help="Show version", 51 | ) 52 | @click.option( 53 | "--blocking", is_flag=True, help="Wait for transaction to be included into a block" 54 | ) 55 | @click.pass_context 56 | def main(ctx, **kwargs): 57 | ctx.obj = {} 58 | for k, v in kwargs.items(): 59 | ctx.obj[k] = v 60 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from setuptools import setup 4 | import re 5 | 6 | # Work around mbcs bug in distutils. 7 | # http://bugs.python.org/issue10945 8 | import codecs 9 | 10 | try: 11 | codecs.lookup("mbcs") 12 | except LookupError: 13 | ascii = codecs.lookup("ascii") 14 | codecs.register(lambda name, enc=ascii: {True: enc}.get(name == "mbcs")) 15 | 16 | VERSION = "0.5.0" 17 | 18 | # Strip some stuff from README for PyPI description: 19 | long_desc = open("README.md").read() 20 | long_desc = re.sub( 21 | '?(.*?)', 22 | '', 23 | long_desc, 24 | flags=re.DOTALL 25 | ) 26 | 27 | setup( 28 | name="peerplays", 29 | version=VERSION, 30 | description="Python library for PEERPLAYS", 31 | long_description=long_desc, 32 | long_description_content_type='text/markdown', 33 | author="PBSA and contributors. Original by Fabian Schuh.", 34 | author_email="info@pbsa.info", 35 | maintainer="PBSA", 36 | maintainer_email="info@pbsa.info", 37 | url="https://gitlab.com/PBSA/tools-libs/python-peerplays", 38 | keywords=["peerplays", "library", "api", "rpc"], 39 | packages=["peerplays", "peerplays.cli", "peerplaysapi", "peerplaysbase"], 40 | classifiers=[ 41 | "License :: OSI Approved :: MIT License", 42 | "Operating System :: OS Independent", 43 | "Programming Language :: Python :: 3", 44 | "Development Status :: 3 - Alpha", 45 | "Intended Audience :: Developers", 46 | "Intended Audience :: System Administrators", 47 | "Topic :: Games/Entertainment", 48 | ], 49 | entry_points={"console_scripts": ["peerplays = peerplays.cli.cli:main"]}, 50 | install_requires=open("requirements.txt").readlines(), 51 | setup_requires=["pytest-runner"], 52 | tests_require=["pytest"], 53 | include_package_data=True, 54 | ) 55 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | workflow: 2 | rules: 3 | - if: $CI_PIPELINE_SOURCE == "merge_request_event" 4 | - if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS 5 | when: never 6 | - if: $CI_COMMIT_BRANCH 7 | 8 | include: 9 | - template: Jobs/Dependency-Scanning.latest.gitlab-ci.yml 10 | - template: Jobs/License-Scanning.latest.gitlab-ci.yml 11 | - template: Jobs/SAST.latest.gitlab-ci.yml 12 | - template: Jobs/Secret-Detection.latest.gitlab-ci.yml 13 | 14 | stages: 15 | - test 16 | 17 | test: 18 | stage: test 19 | script: 20 | - lsb_release -a 21 | - python3 --version 22 | - sudo apt-get update -qy 23 | # - sudo apt-get install -y python3-dev python3-pip 24 | - sudo apt-get install -y python3-dev build-essential libssl-dev libffi-dev libxml2-dev libxslt1-dev zlib1g-dev python3-pip 25 | # - sudo apt-get -y install autoconf bash build-essential ca-certificates cmake dnsutils doxygen git graphviz libbz2-dev libcurl4-openssl-dev libncurses-dev libreadline-dev libssl-dev libtool libzmq3-dev locales ntp pkg-config wget autotools-dev libicu-dev python-dev 26 | # build-essential 27 | - pip3 install -r requirements.txt 28 | - pip3 install -r requirements-test.txt 29 | - tox 30 | tags: 31 | - testing 32 | # - python3 -m unittest tests/test_nft.py 33 | # - python3 -m unittest tests/test_market_place.py 34 | # - python3 -m unittest tests/test_hrp.py 35 | 36 | variables: 37 | SONAR_USER_HOME: "${CI_PROJECT_DIR}/.sonar" # Defines the location of the analysis task cache 38 | GIT_DEPTH: "0" # Tells git to fetch all the branches of the project, required by the analysis task 39 | sonarcloud-check: 40 | image: 41 | name: sonarsource/sonar-scanner-cli:latest 42 | entrypoint: [""] 43 | cache: 44 | key: "${CI_JOB_NAME}" 45 | paths: 46 | - .sonar/cache 47 | script: 48 | - sonar-scanner 49 | only: 50 | - merge_requests 51 | - branches 52 | 53 | -------------------------------------------------------------------------------- /peerplays/memo.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from peerplaysbase.account import PrivateKey, PublicKey 3 | from .account import Account 4 | from .instance import BlockchainInstance 5 | 6 | from graphenecommon.memo import Memo as GrapheneMemo 7 | 8 | 9 | @BlockchainInstance.inject 10 | class Memo(GrapheneMemo): 11 | """ Deals with Memos that are attached to a transfer 12 | 13 | :param peerplays.account.Account from_account: Account that has sent 14 | the memo 15 | :param peerplays.account.Account to_account: Account that has received 16 | the memo 17 | :param peerplays.peerplays.PeerPlays blockchain_instance: instance 18 | 19 | A memo is encrypted with a shared secret derived from a private key of 20 | the sender and a public key of the receiver. Due to the underlying 21 | mathematics, the same shared secret can be derived by the private key 22 | of the receiver and the public key of the sender. The encrypted message 23 | is perturbed by a nonce that is part of the transmitted message. 24 | 25 | .. code-block:: python 26 | 27 | from peerplays.memo import Memo 28 | m = Memo("from", "to") 29 | m.unlock_wallet("secret") 30 | enc = (m.encrypt("foobar")) 31 | print(enc) 32 | >> {'nonce': '17329630356955254641', 'message': '8563e2bb2976e0217806d642901a2855'} 33 | print(m.decrypt(enc)) 34 | >> foobar 35 | 36 | To decrypt a memo, simply use 37 | 38 | .. code-block:: python 39 | 40 | from peerplays.memo import Memo 41 | m = Memo() 42 | m.blockchain.wallet.unlock("secret") 43 | print(memo.decrypt(op_data["memo"])) 44 | 45 | if ``op_data`` being the payload of a transfer operation. 46 | 47 | """ 48 | 49 | def define_classes(self): 50 | self.account_class = Account 51 | self.privatekey_class = PrivateKey 52 | self.publickey_class = PublicKey 53 | -------------------------------------------------------------------------------- /peerplays/event.py: -------------------------------------------------------------------------------- 1 | from peerplays.instance import BlockchainInstance 2 | from .exceptions import EventDoesNotExistException 3 | from .blockchainobject import BlockchainObject, BlockchainObjects 4 | 5 | 6 | class Event(BlockchainObject): 7 | """ Read data about an event on the chain 8 | 9 | :param str identifier: Identifier 10 | :param peerplays blockchain_instance: PeerPlays() instance to use when 11 | accesing a RPC 12 | 13 | """ 14 | 15 | type_id = 22 16 | 17 | def refresh(self): 18 | data = self.blockchain.rpc.get_object(self.identifier) 19 | if not data: 20 | raise EventDoesNotExistException(self.identifier) 21 | super(Event, self).__init__(data) 22 | self.cached = True 23 | 24 | @property 25 | def eventgroup(self): 26 | from .eventgroup import EventGroup 27 | 28 | return EventGroup(self["event_group_id"]) 29 | 30 | @property 31 | def bettingmarketgroups(self): 32 | from .bettingmarketgroup import BettingMarketGroups 33 | 34 | return BettingMarketGroups(self["id"]) 35 | 36 | def set_status(self, status, scores=[], **kwargs): 37 | return self.blockchain.event_update_status( 38 | self["id"], status, scores=scores, **kwargs 39 | ) 40 | 41 | 42 | class Events(BlockchainObjects, BlockchainInstance): 43 | """ List of all available events in an eventgroup 44 | """ 45 | 46 | def __init__(self, eventgroup_id, *args, **kwargs): 47 | self.eventgroup_id = eventgroup_id 48 | BlockchainInstance.__init__(self, *args, **kwargs) 49 | BlockchainObjects.__init__(self, eventgroup_id, *args, **kwargs) 50 | 51 | def refresh(self, *args, **kwargs): 52 | self.events = self.blockchain.rpc.list_events_in_group(self.eventgroup_id) 53 | self.store( 54 | [ 55 | Event(x, lazy=False, blockchain_instance=self.blockchain) 56 | for x in self.events 57 | ], 58 | self.eventgroup_id, 59 | ) 60 | -------------------------------------------------------------------------------- /peerplays/bettingmarket.py: -------------------------------------------------------------------------------- 1 | from peerplays.instance import BlockchainInstance 2 | from .exceptions import BettingMarketDoesNotExistException 3 | from .blockchainobject import BlockchainObject, BlockchainObjects 4 | 5 | 6 | class BettingMarket(BlockchainObject): 7 | """ Read data about a Betting Market on the chain 8 | 9 | :param str identifier: Identifier 10 | :param peerplays blockchain_instance: PeerPlays() instance to use when 11 | accesing a RPC 12 | 13 | """ 14 | 15 | type_id = 25 16 | 17 | def refresh(self): 18 | assert ( 19 | self.identifier[:5] == "1.25." 20 | ), "Identifier needs to be of form '1.25.xx'" 21 | data = self.blockchain.rpc.get_object(self.identifier) 22 | if not data: 23 | raise BettingMarketDoesNotExistException(self.identifier) 24 | super(BettingMarket, self).__init__(data) 25 | self.cached = True 26 | 27 | @property 28 | def bettingmarketgroup(self): 29 | from .bettingmarketgroup import BettingMarketGroup 30 | 31 | return BettingMarketGroup(self["group_id"]) 32 | 33 | 34 | class BettingMarkets(BlockchainObjects, BlockchainInstance): 35 | """ List of all available BettingMarkets 36 | 37 | :param str betting_market_group_id: Market Group ID (``1.24.xxx``) 38 | """ 39 | 40 | def __init__(self, betting_market_group_id, *args, **kwargs): 41 | self.betting_market_group_id = betting_market_group_id 42 | BlockchainInstance.__init__(self, *args, **kwargs) 43 | BlockchainObjects.__init__(self, betting_market_group_id, *args, **kwargs) 44 | 45 | def refresh(self, *args, **kwargs): 46 | 47 | self.bettingmarkets = self.blockchain.rpc.list_betting_markets( 48 | self.betting_market_group_id 49 | ) 50 | self.store( 51 | [ 52 | BettingMarket(x, lazy=False, blockchain_instance=self.blockchain) 53 | for x in self.bettingmarkets 54 | ], 55 | self.betting_market_group_id, 56 | ) 57 | -------------------------------------------------------------------------------- /docs/tutorials/howto-build-peerplays.rst: -------------------------------------------------------------------------------- 1 | *********************** 2 | Building PeerPlays Node 3 | *********************** 4 | 5 | Downloading the sources 6 | ####################### 7 | 8 | The sources can be downloaded from:: 9 | 10 | https://github.com/peerplays-network/peerplays 11 | 12 | Dependencies 13 | ############# 14 | 15 | Development Toolkit 16 | ******************* 17 | 18 | The following dependencies were necessary for a clean install of Ubuntu 16.10: 19 | 20 | .. code-block:: sh 21 | 22 | sudo apt-get update 23 | sudo apt-get install gcc-5 g++-5 gcc g++ cmake make \ 24 | libbz2-dev libdb++-dev libdb-dev \ 25 | libssl-dev openssl libreadline-dev \ 26 | autotools-dev build-essential \ 27 | g++ libbz2-dev libicu-dev python-dev \ 28 | autoconf libtool git 29 | 30 | Boost 1.60 31 | ********** 32 | 33 | You need to download the Boost tarball for Boost 1.60.0. 34 | 35 | .. code-block:: sh 36 | 37 | export BOOST_ROOT=$HOME/opt/boost_1.60.0 38 | wget -c 'http://sourceforge.net/projects/boost/files/boost/1.60.0/boost_1.60.0.tar.bz2/download'\ 39 | -O boost_1.60.0.tar.bz2 40 | tar xjf boost_1.60.0.tar.bz2 41 | cd boost_1.60.0/ 42 | ./bootstrap.sh "--prefix=$BOOST_ROOT" 43 | ./b2 install 44 | 45 | Building PeerPlays 46 | ################## 47 | 48 | After downloading the PeerPlays sources we can run ``cmake`` for configuration 49 | and compile with ``make``: 50 | 51 | .. code-block:: sh 52 | 53 | cd peerplays 54 | export CC=gcc-5 CXX=g++-5 55 | cmake -DBOOST_ROOT="$BOOST_ROOT" -DCMAKE_BUILD_TYPE=Debug . 56 | make 57 | 58 | Note that the environmental variable ``$BOOST_ROOT`` should point to your 59 | install directory of boost if you have installed it manually (see first line in 60 | the previous example) 61 | 62 | Binaries 63 | ######## 64 | 65 | After compilation, the binaries are located in:: 66 | 67 | ./programs/witness_node 68 | ./programs/cli_wallet 69 | ./programs/delayed_node 70 | -------------------------------------------------------------------------------- /examples/create_all.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from getpass import getpass 3 | from pprint import pprint 4 | from peerplays import PeerPlays 5 | 6 | ppy = PeerPlays( 7 | # this account creates the proposal 8 | proposer="stefan", 9 | # Proposal needs to be approve within 1 hour 10 | proposal_expiration=60 * 60 * 24 * 14, 11 | # For testing, set this to true 12 | nobroadcast=False, 13 | # We want to bundle many operations into a single transaction 14 | bundle=True, 15 | ) 16 | ppy.wallet.unlock(getpass()) 17 | 18 | ppy.sport_create([ # relative id 0.0.0 19 | ["de", "Fussball"], 20 | ["en", "Soccer"], 21 | ]) 22 | 23 | ppy.event_group_create([ # relative id 0.0.1 24 | ["de", "1. Bundesliga"], 25 | ["en", "First Country League"], 26 | ], sport_id="0.0.0") 27 | 28 | ppy.event_create( # relative id 0.0.2 29 | [["de", "Bundesliga"], ["en", "Germany Scoccer Championship"]], 30 | [["de", "Januar 2016"], ["en", "January 2016"]], # season 31 | datetime.datetime(2016, 1, 1, 0, 0, 0), # start_time 32 | event_group_id="0.0.3" # event group 33 | ) 34 | 35 | ppy.betting_market_rules_create( # relative id 0.0.3 36 | [["en", "NHL Rules v1.0"]], 37 | [["en", "The winner will be the team with the most points at the end of the game. The team with fewer points will not be the winner."]], 38 | ) 39 | 40 | ppy.betting_market_group_create( # relative id 0.0.4 41 | [["de", "Meine Market Group"], ["en", "My betting market group"]], 42 | event_id="0.0.2", 43 | rules_id="0.0.3", 44 | ) 45 | 46 | ppy.betting_market_create( 47 | [["de", "Fuerth gewinnt"], ["en", "Fuerth wins"]], 48 | [["de", "Description: Fuerth gewinnt"], ["en", "Description: Fuerth wins"]], 49 | group_id="0.0.4", 50 | ) 51 | 52 | ppy.betting_market_create( 53 | [["de", "Nuernberg gewinnt"], ["en", "Nuremberg wins"]], 54 | [["de", "Description: Fuerth gewinnt"], ["en", "Description: Fuerth wins"]], 55 | group_id="0.0.4", 56 | ) 57 | 58 | # Broadcast the whole transaction 59 | pprint( 60 | ppy.txbuffer.broadcast() 61 | ) 62 | -------------------------------------------------------------------------------- /peerplays/transactionbuilder.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from graphenecommon.transactionbuilder import ( 3 | TransactionBuilder as GrapheneTransactionBuilder, 4 | ProposalBuilder as GrapheneProposalBuilder, 5 | ) 6 | 7 | from peerplaysbase import operations 8 | from peerplaysbase.account import PrivateKey, PublicKey 9 | from peerplaysbase.objects import Operation 10 | from peerplaysbase.signedtransactions import Signed_Transaction 11 | 12 | from .amount import Amount 13 | from .asset import Asset 14 | from .account import Account 15 | from .instance import BlockchainInstance 16 | 17 | 18 | @BlockchainInstance.inject 19 | class ProposalBuilder(GrapheneProposalBuilder): 20 | """ Proposal Builder allows us to construct an independent Proposal 21 | that may later be added to an instance ot TransactionBuilder 22 | 23 | :param str proposer: Account name of the proposing user 24 | :param int proposal_expiration: Number seconds until the proposal is 25 | supposed to expire 26 | :param int proposal_review: Number of seconds for review of the 27 | proposal 28 | :param .transactionbuilder.TransactionBuilder: Specify 29 | your own instance of transaction builder (optional) 30 | :param instance blockchain_instance: Blockchain instance 31 | """ 32 | 33 | def define_classes(self): 34 | self.operation_class = Operation 35 | self.operations = operations 36 | self.account_class = Account 37 | 38 | 39 | @BlockchainInstance.inject 40 | class TransactionBuilder(GrapheneTransactionBuilder): 41 | """ This class simplifies the creation of transactions by adding 42 | operations and signers. 43 | """ 44 | 45 | def define_classes(self): 46 | self.account_class = Account 47 | self.asset_class = Asset 48 | self.operation_class = Operation 49 | self.operations = operations 50 | self.privatekey_class = PrivateKey 51 | self.publickey_class = PublicKey 52 | self.signed_transaction_class = Signed_Transaction 53 | self.amount_class = Amount 54 | -------------------------------------------------------------------------------- /docs/stati.rst: -------------------------------------------------------------------------------- 1 | ***** 2 | Stati 3 | ***** 4 | 5 | List of statis and types used within PeerPlays: 6 | 7 | .. code-block:: python 8 | 9 | class BetType(Enum): 10 | options = [ 11 | "back", 12 | "lay", 13 | ] 14 | 15 | 16 | class BettingMarketResolution(Enum): 17 | options = [ 18 | "win", 19 | "not_win", 20 | "cancel", 21 | "BETTING_MARKET_RESOLUTION_COUNT", 22 | ] 23 | 24 | 25 | class BettingMarketStatus(Enum): 26 | options = [ 27 | "unresolved", # no grading has been published for this betting market 28 | "frozen", # bets are suspended, no bets allowed 29 | "graded", # grading of win or not_win has been published 30 | "canceled", # the betting market is canceled, no further bets are allowed 31 | "settled", # the betting market has been paid out 32 | "BETTING_MARKET_STATUS_COUNT" 33 | ] 34 | 35 | 36 | class BettingMarketGroupStatus(Enum): 37 | options = [ 38 | "upcoming", # betting markets are accepting bets, will never go "in_play" 39 | "in_play", # betting markets are delaying bets 40 | "closed", # betting markets are no longer accepting bets 41 | "graded", # witnesses have published win/not win for the betting markets 42 | "re_grading", # initial win/not win grading has been challenged 43 | "settled", # paid out 44 | "frozen", # betting markets are not accepting bets 45 | "canceled", # canceled 46 | "BETTING_MARKET_GROUP_STATUS_COUNT" 47 | ] 48 | 49 | 50 | class EventStatus(Enum): 51 | options = [ 52 | "upcoming", # Event has not started yet, betting is allowed 53 | "in_progress", # Event is in progress, if "in-play" betting is enabled, bets will be delayed 54 | "frozen", # Betting is temporarily disabled 55 | "finished", # Event has finished, no more betting allowed 56 | "canceled", # Event has been canceled, all betting markets have been canceled 57 | "settled", # All betting markets have been paid out 58 | "STATUS_COUNT" 59 | ] 60 | -------------------------------------------------------------------------------- /tests/archived/test_account2.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import mock 3 | from pprint import pprint 4 | from peerplays import PeerPlays 5 | from peerplays.account import Account 6 | from peerplays.amount import Amount 7 | from peerplays.asset import Asset 8 | from peerplays.instance import set_shared_peerplays_instance 9 | from peerplaysbase.operationids import getOperationNameForId 10 | from .fixtures import fixture_data, peerplays, peerplays2, publicKey 11 | import string 12 | import random 13 | 14 | 15 | class Testcases(unittest.TestCase): 16 | 17 | def setUp(self): 18 | # fixture_data() 19 | peerplays.nobroadcast = False 20 | peerplays.blocking = True 21 | pass 22 | 23 | def test_account2(self): 24 | Account("witness-account") 25 | Account("1.2.3") 26 | asset = Asset("1.3.0") 27 | symbol = asset["symbol"] 28 | account = Account("witness-account", full=True) 29 | self.assertEqual(account.name, "witness-account") 30 | self.assertEqual(account["name"], account.name) 31 | self.assertEqual(account["id"], "1.2.1") 32 | self.assertIsInstance(account.balance("1.3.0"), Amount) 33 | self.assertIsInstance(account.balance({"symbol": symbol}), Amount) 34 | self.assertIsInstance(account.balances, list) 35 | 36 | # BlockchainObjects method 37 | account.cached = False 38 | self.assertTrue(account.items()) 39 | account.cached = False 40 | self.assertIn("id", account) 41 | account.cached = False 42 | self.assertEqual(account["id"], "1.2.1") 43 | # self.assertEqual(str(account), "") 44 | self.assertIsInstance(Account(account), Account) 45 | 46 | def test_account_creation(self): 47 | account_name = "".join(random.choices(string.ascii_lowercase, k=10)) 48 | account_name = account_name + "".join(random.choices(string.digits, k=10)) 49 | print("account_name:", account_name) 50 | 51 | op_res = peerplays2.create_account(account_name, registrar="nathan", owner_key=publicKey, active_key=publicKey, memo_key=publicKey) 52 | print("======op_res===========") 53 | print(op_res) 54 | print("======op_res===========") 55 | 56 | self.assertEqual(op_res["result"]["operations"][0][0], 5) 57 | print (account_name, "account created") 58 | -------------------------------------------------------------------------------- /docs/tutorials/howto-decode-memo.rst: -------------------------------------------------------------------------------- 1 | ***************** 2 | Decoding the Memo 3 | ***************** 4 | 5 | In Peerplays, memos are usually encrypted using a distinct memo key. That way, 6 | exposing the memo private key will only expose transaction memos (for that key) 7 | and not compromise any funds. It is thus safe to store the memo private key in 8 | 3rd party services and scripts. 9 | 10 | Obtaining memo wif key from cli_wallet 11 | ====================================== 12 | 13 | The memo public key can be obtained from the cli_wallet account settings 14 | or via command line::: 15 | 16 | get_account myaccount 17 | 18 | in the cli wallet. The corresponding private key can be obtain from::: 19 | 20 | get_private_key 21 | 22 | Note that the latter command exposes all private keys in clear-text wif. 23 | 24 | That private key can be added to the pypeerplays wallet with: 25 | 26 | .. code-block:: python 27 | 28 | from peerplays import PeerPlays 29 | ppy = PeerPlays() 30 | # Create a new wallet if not yet exist 31 | ppy.wallet.create("wallet-decrypt-password") 32 | ppy.wallet.unlock("wallet-decrypt-password") 33 | ppy.wallet.addPrivateKey("5xxxxxxxxxxx") 34 | 35 | Decoding the memo 36 | ================= 37 | 38 | The memo is encoded with a DH-shared secret key. We don't want to go 39 | into too much detail here, but a simple python module can help you here: 40 | 41 | The encrypted memo can be decoded with: 42 | 43 | .. code-block:: python 44 | 45 | from peerplays.memo import Memo 46 | transfer_operation = { 47 | 'amount': {'amount': 100000, 'asset_id': '1.3.0'}, 48 | 'extensions': [], 49 | 'fee': {'amount': 2089843, 'asset_id': '1.3.0'}, 50 | 'from': '1.2.18', 51 | 'memo': {'from': 'PPY1894jUspGi6fZwnUmaeCPDZpke6m4T9bHtKrd966M7qYz665xjr', 52 | 'message': '5d09c06c4794f9bcdef9d269774209be', 53 | 'nonce': 7364013452905740719, 54 | 'to': 'PPY16MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV'}, 55 | 'to': '1.2.6'} 56 | memo = Memo( 57 | transfer_operation["from"], 58 | transfer_operation["to"], 59 | ) 60 | memo.peerplays.wallet.unlock("wallet-decrypt-password") 61 | print(memo.decrypt(transfer_operation["memo"])) 62 | 63 | 64 | Alternatively, the 'history' command on the *cli-wallet* API, exposes 65 | the decrypted memo aswell. 66 | -------------------------------------------------------------------------------- /peerplays/amount.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from .asset import Asset 3 | from .instance import BlockchainInstance 4 | from graphenecommon.amount import Amount as GrapheneAmount 5 | 6 | 7 | @BlockchainInstance.inject 8 | class Amount(GrapheneAmount): 9 | """ This class deals with Amounts of any asset to simplify dealing with the tuple:: 10 | 11 | (amount, asset) 12 | 13 | :param list args: Allows to deal with different representations of an amount 14 | :param float amount: Let's create an instance with a specific amount 15 | :param str asset: Let's you create an instance with a specific asset (symbol) 16 | :param peerplays.peerplays.peerplays blockchain_instance: peerplays instance 17 | :returns: All data required to represent an Amount/Asset 18 | :rtype: dict 19 | :raises ValueError: if the data provided is not recognized 20 | 21 | .. code-block:: python 22 | 23 | from peerplays.amount import Amount 24 | from peerplays.asset import Asset 25 | a = Amount("1 USD") 26 | b = Amount(1, "USD") 27 | c = Amount("20", Asset("USD")) 28 | a + b 29 | a * 2 30 | a += b 31 | a /= 2.0 32 | 33 | Way to obtain a proper instance: 34 | 35 | * ``args`` can be a string, e.g.: "1 USD" 36 | * ``args`` can be a dictionary containing ``amount`` and ``asset_id`` 37 | * ``args`` can be a dictionary containing ``amount`` and ``asset`` 38 | * ``args`` can be a list of a ``float`` and ``str`` (symbol) 39 | * ``args`` can be a list of a ``float`` and a :class:`peerplays.asset.Asset` 40 | * ``amount`` and ``asset`` are defined manually 41 | 42 | An instance is a dictionary and comes with the following keys: 43 | 44 | * ``amount`` (float) 45 | * ``symbol`` (str) 46 | * ``asset`` (instance of :class:`peerplays.asset.Asset`) 47 | 48 | Instances of this class can be used in regular mathematical expressions 49 | (``+-*/%``) such as: 50 | 51 | .. code-block:: python 52 | 53 | Amount("1 USD") * 2 54 | Amount("15 GOLD") + Amount("0.5 GOLD") 55 | """ 56 | 57 | def define_classes(self): 58 | from graphenecommon.price import Price 59 | 60 | self.asset_class = Asset 61 | self.price_class = Price 62 | -------------------------------------------------------------------------------- /peerplaysbase/chains.py: -------------------------------------------------------------------------------- 1 | known_chains = { 2 | "ALICE": { 3 | "chain_id": "6b6b5f0ce7a36d323768e534f3edb41c6d6332a541a95725b98e28d140850134", 4 | "core_symbol": "PPY", 5 | "prefix": "PPY", 6 | }, 7 | "ALPHA": { 8 | "chain_id": "80df3c34bff8469bdaf47e55a2fb6fb6f1eb4a49d6375aaa5689d3ca9e51bff5", 9 | "core_symbol": "PPY", 10 | "prefix": "PPY", 11 | }, 12 | "BEATRICE": { 13 | "chain_id": "b3f7fe1e5ad0d2deca40a626a4404524f78e65c3a48137551c33ea4e7c365672", 14 | "core_symbol": "TEST", 15 | "prefix": "TEST", 16 | }, 17 | "CHARLIE": { 18 | "chain_id": "3de67447ef7e7390371cdf8f2c775b4a6a9b8692ab4376942746dea73f5a37dd", 19 | "core_symbol": "TEST", 20 | "prefix": "TEST", 21 | }, 22 | "DICK": { 23 | "chain_id": "318cea7d376eb81a0beba3623e12af66ab76b736860ae50739d0aaf5cecb5a91", 24 | "core_symbol": "TEST", 25 | "prefix": "TEST", 26 | }, 27 | "ELIZABETH": { 28 | "chain_id": "1d76e264e0a6f27be89d80bbff66c53ea87068bbdd73fc912cbcbcb7be53ec83", 29 | "core_symbol": "TEST", 30 | "prefix": "TEST", 31 | }, 32 | "FRED": { 33 | "chain_id": "ef688290dea16ee55ae83bbe30384c24a8ad06268897dd144fb3a677a88306fe", 34 | "core_symbol": "TEST", 35 | "prefix": "TEST", 36 | }, 37 | "HERCULES": { 38 | "chain_id": "9e770aee9cb04b11f62391b89a38e1ee28981c94e2b8e7047e0902862568d285", 39 | "core_symbol": "TEST", 40 | "prefix": "TEST"}, 41 | "FRED": { 42 | "chain_id": "6d8462f65dc97688de4cd1e295ecbda8f34ca3def0dab96272faee251761cc08", 43 | "core_symbol": "TEST", 44 | "prefix": "TEST"}, 45 | "IRONA": { 46 | "chain_id": "bec1b83fc4752ad319dfc4e9f1fac37d8fb06c77382ad74438a827a4b16f2e9e", 47 | "core_symbol": "TEST", 48 | "prefix": "TEST"}, 49 | "QAENV": { 50 | "chain_id": "7c1c72eb738b3ff1870350f85daca27e2d0f5dd25af27df7475fbd92815e421e", 51 | "core_symbol": "TEST", 52 | "prefix": "TEST"}, 53 | "MINT": { 54 | "chain_id": "2c25aae5835fd54020329d4f150b04867e72cbd8f7f7b900a7c3da8a329a6014", 55 | "core_symbol": "TEST", 56 | "prefix": "TEST"}, 57 | } 58 | 59 | # old fred chain_id in the code 60 | # "chain_id": "735f88594e5b7521047864f7cca7367ff27e95ce7bd40482b85c978d1337f881", 61 | # "chain_id": "09f618610b6449f230f133addd388dc6dc15072e80e39f2c0d76088550413d47", 62 | -------------------------------------------------------------------------------- /peerplaysapi/node.py: -------------------------------------------------------------------------------- 1 | import re 2 | from grapheneapi.api import Api as Original_Api 3 | from peerplaysbase.chains import known_chains 4 | from . import exceptions 5 | 6 | 7 | class Api(Original_Api): 8 | def post_process_exception(self, e): 9 | msg = exceptions.decodeRPCErrorMsg(e).strip() 10 | if msg == "missing required active authority": 11 | raise exceptions.MissingRequiredActiveAuthority 12 | elif re.match("^no method with name.*", msg): 13 | raise exceptions.NoMethodWithName(msg) 14 | elif msg == "Proposed operation is already pending for approval.": 15 | raise exceptions.OperationInProposalExistsException(msg) 16 | elif msg: 17 | raise exceptions.UnhandledRPCError(msg) 18 | else: 19 | raise e 20 | 21 | 22 | class PeerPlaysNodeRPC(Api): 23 | 24 | def register_apis(self): 25 | self.login("", "", api_id=1) 26 | self.api_id["database"] = self.database(api_id=1) 27 | self.api_id["history"] = self.history(api_id=1) 28 | self.api_id["network_broadcast"] = self.network_broadcast(api_id=1) 29 | 30 | def get_account(self, name, **kwargs): 31 | """ Get full account details from account name or id 32 | 33 | :param str name: Account name or account id 34 | """ 35 | if len(name.split(".")) == 3: 36 | return self.get_objects([name])[0] 37 | else: 38 | return self.get_account_by_name(name, **kwargs) 39 | 40 | def get_asset(self, name, **kwargs): 41 | """ Get full asset from name of id 42 | 43 | :param str name: Symbol name or asset id (e.g. 1.3.0) 44 | """ 45 | if len(name.split(".")) == 3: 46 | return self.get_objects([name], **kwargs)[0] 47 | else: 48 | return self.lookup_asset_symbols([name], **kwargs)[0] 49 | 50 | def get_object(self, o, **kwargs): 51 | """ Get object with id ``o`` 52 | 53 | :param str o: Full object id 54 | """ 55 | return self.get_objects([o], **kwargs)[0] 56 | 57 | def get_network(self): 58 | """ Identify the connected network. This call returns a 59 | dictionary with keys chain_id, core_symbol and prefix 60 | """ 61 | props = self.get_chain_properties() 62 | chain_id = props["chain_id"] 63 | for k, v in known_chains.items(): 64 | if v["chain_id"] == chain_id: 65 | return v 66 | raise Exception("Connecting to unknown network!") 67 | -------------------------------------------------------------------------------- /peerplays/account.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from .amount import Amount 3 | from .instance import BlockchainInstance 4 | from graphenecommon.account import ( 5 | Account as GrapheneAccount, 6 | AccountUpdate as GrapheneAccountUpdate, 7 | ) 8 | from peerplaysbase import operations 9 | 10 | 11 | @BlockchainInstance.inject 12 | class Account(GrapheneAccount): 13 | """ This class allows to easily access Account data 14 | 15 | :param str account_name: Name of the account 16 | :param peerplays.peerplays.peerplays blockchain_instance: peerplays 17 | instance 18 | :param bool full: Obtain all account data including orders, positions, etc. 19 | :param bool lazy: Use lazy loading 20 | :param bool full: Obtain all account data including orders, positions, 21 | etc. 22 | :returns: Account data 23 | :rtype: dictionary 24 | :raises peerplays.exceptions.AccountDoesNotExistsException: if account 25 | does not exist 26 | 27 | Instances of this class are dictionaries that come with additional 28 | methods (see below) that allow dealing with an account and it's 29 | corresponding functions. 30 | 31 | .. code-block:: python 32 | 33 | from peerplays.account import Account 34 | account = Account("init0") 35 | print(account) 36 | 37 | .. note:: This class comes with its own caching function to reduce the 38 | load on the API server. Instances of this class can be 39 | refreshed with ``Account.refresh()``. 40 | 41 | """ 42 | 43 | def define_classes(self): 44 | self.type_id = 2 45 | self.amount_class = Amount 46 | self.operations = operations 47 | 48 | 49 | @BlockchainInstance.inject 50 | class AccountUpdate(GrapheneAccountUpdate): 51 | """ This purpose of this class is to keep track of account updates 52 | as they are pushed through by :class:`peerplays.notify.Notify`. 53 | 54 | Instances of this class are dictionaries and take the following 55 | form: 56 | 57 | ... code-block: js 58 | 59 | {'id': '2.6.29', 60 | 'lifetime_fees_paid': '44261516129', 61 | 'most_recent_op': '2.9.0', 62 | 'owner': '1.2.29', 63 | 'pending_fees': 0, 64 | 'pending_vested_fees': 16310, 65 | 'total_core_in_orders': '6788845277634', 66 | 'total_ops': 0} 67 | 68 | """ 69 | 70 | account_class = Account 71 | -------------------------------------------------------------------------------- /docs/rpc.rst: -------------------------------------------------------------------------------- 1 | ********************** 2 | Remote Procedure Calls 3 | ********************** 4 | 5 | Prerequisits 6 | ############ 7 | 8 | This page assumes that you either have a full node or a wallet running and 9 | listening to port ``8090``, locally. 10 | 11 | .. note:: The set of available commands depends on application you connect to. 12 | 13 | Call Format 14 | ########### 15 | 16 | In Graphene, RPC calls are state-less and accessible via regular JSON formated 17 | RPC-HTTP-calls. The correct structure of the JSON call is 18 | 19 | .. code-block:: js 20 | 21 | { 22 | "jsonrpc": "2.0", 23 | "id": 1 24 | "method": "get_accounts", 25 | "params": [["1.2.0", "1.2.1"]], 26 | } 27 | 28 | The ``get_accounts`` call is available in the Full Node's ``database`` API and 29 | takes only one argument which is an array of account ids (here: ``["1.2.0", "1.2.1"]``). 30 | 31 | Example Call with `curl` 32 | ------------------------ 33 | 34 | Such as call can be submitted via ``curl``: 35 | 36 | .. code-block:: sh 37 | 38 | curl --data '{"jsonrpc":"2.0","method":"call", "params":[0, "get_accounts", [["1.2.0", "1.2.1"]]],"id":0}' https://ppy-node.bitshares.eu 39 | 40 | 41 | Successful Calls 42 | ---------------- 43 | 44 | The API will return a properly JSON formated response carrying the same ``id`` 45 | as the request to distinguish subsequent calls. 46 | 47 | .. code-block:: js 48 | 49 | { 50 | "id":1, 51 | "result": ..data.. 52 | } 53 | 54 | Errors 55 | ------ 56 | 57 | In case of an error, the resulting answer will carry an ``error`` attribute and 58 | a detailed description: 59 | 60 | .. code-block:: js 61 | 62 | { 63 | "id": 0 64 | "error": { 65 | "data": { 66 | "code": error-code, 67 | "name": " .. name of exception .." 68 | "message": " .. message of exception ..", 69 | "stack": [ .. stack trace .. ], 70 | }, 71 | "code": 1, 72 | }, 73 | } 74 | 75 | Remarks 76 | ####### 77 | 78 | Wallet specific commands, such as ``transfer`` and market orders, are only 79 | available if connecting to ``cli_wallet`` because only the wallet has the 80 | private keys and signing capabilities and some calls will only execute of the 81 | wallet is unlocked. 82 | 83 | The full node offers a set of API(s), of which only the ``database`` calls are 84 | avaiable via RPC. Calls that are restricted by default (i.e. 85 | ``network_node_api``) or have been restricted by configuration are not 86 | accessible via RPC because a statefull protocol (websocket) is required for 87 | login. 88 | -------------------------------------------------------------------------------- /peerplays/cli/ui.py: -------------------------------------------------------------------------------- 1 | import json 2 | from peerplays.account import Account 3 | from prettytable import PrettyTable, ALL as allBorders 4 | import pkg_resources 5 | import click 6 | import logging 7 | log = logging.getLogger(__name__) 8 | 9 | 10 | def print_version(ctx, param, value): 11 | if not value or ctx.resilient_parsing: 12 | return 13 | click.echo('{prog} {version}'.format( 14 | prog=pkg_resources.require("peerplays")[0].project_name, 15 | version=pkg_resources.require("peerplays")[0].version 16 | )) 17 | ctx.exit() 18 | 19 | 20 | def print_permissions(account): 21 | t = PrettyTable( 22 | ["Permission", "Threshold", "Key/Account"], 23 | hrules=allBorders 24 | ) 25 | t.align = "r" 26 | for permission in ["owner", "active"]: 27 | auths = [] 28 | # account auths: 29 | for authority in account[permission]["account_auths"]: 30 | auths.append( 31 | "%s (%d)" % (Account(authority[0])["name"], 32 | authority[1])) 33 | # key auths: 34 | for authority in account[permission]["key_auths"]: 35 | auths.append( 36 | "%s (%d)" % (authority[0], authority[1])) 37 | t.add_row([ 38 | permission, 39 | account[permission]["weight_threshold"], 40 | "\n".join(auths), 41 | ]) 42 | print(t) 43 | 44 | 45 | def get_terminal(text="Password", confirm=False, allowedempty=False): 46 | import getpass 47 | while True: 48 | pw = getpass.getpass(text) 49 | if not pw and not allowedempty: 50 | print("Cannot be empty!") 51 | continue 52 | else: 53 | if not confirm: 54 | break 55 | pwck = getpass.getpass( 56 | "Confirm " + text 57 | ) 58 | if (pw == pwck): 59 | break 60 | else: 61 | print("Not matching!") 62 | return pw 63 | 64 | 65 | def pprintOperation(op): 66 | return json.dumps(op["op"][1], indent=4) 67 | 68 | 69 | def pretty_print(o, *args, **kwargs): 70 | t = PrettyTable( 71 | o[0].keys(), 72 | ) 73 | for items in o: 74 | r = list() 75 | for item in items.values(): 76 | if isinstance(item, list): 77 | r.append( 78 | "\n".join(["{}: {}".format(v[0], v[1]) for v in item]) 79 | ) 80 | else: 81 | r.append(item) 82 | t.add_row(r) 83 | t.align = "l" 84 | return str(t) 85 | 86 | 87 | def maplist2dict(dlist): 88 | """ Convert a list of tuples into a dictionary 89 | """ 90 | return {k[0]: k[1] for k in dlist} 91 | -------------------------------------------------------------------------------- /peerplays/cli/proposal.py: -------------------------------------------------------------------------------- 1 | import json 2 | import click 3 | from prettytable import PrettyTable 4 | from pprint import pprint 5 | from peerplays.proposal import Proposals 6 | from peerplays.account import Account 7 | from .decorators import onlineChain, unlockWallet 8 | from .main import main 9 | 10 | 11 | @main.command() 12 | @click.pass_context 13 | @onlineChain 14 | @click.argument("proposal", nargs=-1) 15 | @click.option("--account", help="Account that takes this action", type=str) 16 | @unlockWallet 17 | def disapproveproposal(ctx, proposal, account): 18 | """ Disapprove a proposal 19 | """ 20 | pprint(ctx.peerplays.disapproveproposal(proposal, account=account)) 21 | 22 | 23 | @main.command() 24 | @click.pass_context 25 | @onlineChain 26 | @click.argument("proposal", nargs=-1) 27 | @click.option("--account", help="Account that takes this action", type=str) 28 | @unlockWallet 29 | def approveproposal(ctx, proposal, account): 30 | """ Approve a proposal 31 | """ 32 | pprint(ctx.peerplays.approveproposal(proposal, account=account)) 33 | 34 | 35 | @main.command() 36 | @click.pass_context 37 | @onlineChain 38 | @click.argument("account", type=str, required=False) 39 | def proposals(ctx, account): 40 | """ List proposals 41 | """ 42 | proposals = Proposals(account) 43 | t = PrettyTable( 44 | [ 45 | "id", 46 | "expiration", 47 | "proposer", 48 | "required approvals", 49 | "available approvals", 50 | "review period time", 51 | "proposal", 52 | ] 53 | ) 54 | t.align = "l" 55 | for proposal in proposals: 56 | if proposal.proposer: 57 | proposer = Account(proposal.proposer, peerplays_instance=ctx.peerplays)[ 58 | "name" 59 | ] 60 | else: 61 | proposer = "n/a" 62 | 63 | t.add_row( 64 | [ 65 | proposal["id"], 66 | proposal["expiration_time"], 67 | proposer, 68 | [ 69 | Account(x)["name"] 70 | for x in ( 71 | proposal["required_active_approvals"] 72 | + proposal["required_owner_approvals"] 73 | ) 74 | ], 75 | json.dumps( 76 | [Account(x)["name"] for x in proposal["available_active_approvals"]] 77 | + proposal["available_key_approvals"] 78 | + proposal["available_owner_approvals"], 79 | indent=1, 80 | ), 81 | proposal.get("review_period_time", None), 82 | json.dumps(proposal["proposed_transaction"], indent=4), 83 | ] 84 | ) 85 | 86 | click.echo(str(t)) 87 | -------------------------------------------------------------------------------- /peerplays/bettingmarketgroup.py: -------------------------------------------------------------------------------- 1 | from peerplays.instance import BlockchainInstance 2 | from .exceptions import BettingMarketGroupDoesNotExistException 3 | from .blockchainobject import BlockchainObject, BlockchainObjects 4 | from .utils import map2dict 5 | 6 | HANDICAP_MARKET_LABELS = ["hc", "1x2_hc"] 7 | OVERUNDER_MARKET_LABELS = ["ou"] 8 | 9 | 10 | class BettingMarketGroup(BlockchainObject): 11 | """ Read data about a Betting Market Group on the chain 12 | 13 | :param str identifier: Identifier 14 | :param peerplays blockchain_instance: PeerPlays() instance to use when 15 | accesing a RPC 16 | 17 | """ 18 | 19 | type_id = 24 20 | 21 | def refresh(self): 22 | data = self.blockchain.rpc.get_object(self.identifier) 23 | if not data: 24 | raise BettingMarketGroupDoesNotExistException(self.identifier) 25 | super(BettingMarketGroup, self).__init__(data) 26 | self.cached = True 27 | 28 | @property 29 | def event(self): 30 | from .event import Event 31 | 32 | return Event(self["event_id"]) 33 | 34 | @property 35 | def bettingmarkets(self): 36 | from .bettingmarket import BettingMarkets 37 | 38 | return BettingMarkets(self["id"]) 39 | 40 | def resolve(self, results, **kwargs): 41 | return self.blockchain.betting_market_resolve(self["id"], results, **kwargs) 42 | 43 | def get_dynamic_type(self): 44 | assert self.is_dynamic() 45 | description = map2dict(self["description"]) 46 | return description.get("_dynamic") 47 | 48 | def is_dynamic(self): 49 | description = map2dict(self["description"]) 50 | return bool(description.get("_dynamic", False)) 51 | 52 | def is_dynamic_type(self, other_type): 53 | our_type = self.get_dynamic_type() 54 | if our_type in HANDICAP_MARKET_LABELS: 55 | return other_type in HANDICAP_MARKET_LABELS 56 | else: 57 | return other_type in OVERUNDER_MARKET_LABELS 58 | 59 | 60 | class BettingMarketGroups(BlockchainObjects, BlockchainInstance): 61 | """ List of all available BettingMarketGroups 62 | 63 | :param strevent_id: Event ID (``1.22.xxx``) 64 | """ 65 | 66 | def __init__(self, event_id, *args, **kwargs): 67 | self.event_id = event_id 68 | BlockchainInstance.__init__(self, *args, **kwargs) 69 | BlockchainObjects.__init__(self, event_id, *args, **kwargs) 70 | 71 | def refresh(self, *args, **kwargs): 72 | self.bettingmarketgroups = self.blockchain.rpc.list_betting_market_groups( 73 | self.event_id 74 | ) 75 | self.store( 76 | [ 77 | BettingMarketGroup(x, lazy=False, blockchain_instance=self.blockchain) 78 | for x in self.bettingmarketgroups 79 | ], 80 | self.event_id, 81 | ) 82 | -------------------------------------------------------------------------------- /docs/cli.rst: -------------------------------------------------------------------------------- 1 | ***************************** 2 | "peerplays" command line tool 3 | ***************************** 4 | 5 | The ``peerplays`` command line tool comes with the following features: 6 | 7 | .. code-block:: console 8 | 9 | $ peerplays --help 10 | Usage: peerplays [OPTIONS] COMMAND [ARGS]... 11 | 12 | Options: 13 | --debug / --no-debug Enable/Disable Debugging (no-broadcasting 14 | mode) 15 | --node TEXT Websocket URL for public Peerplays API 16 | (default: "wss://t.b.d./") 17 | --rpcuser TEXT Websocket user if authentication is required 18 | --rpcpassword TEXT Websocket password if authentication is 19 | required 20 | -d, --nobroadcast / --broadcast 21 | Do not broadcast anything 22 | -x, --unsigned / --signed Do not try to sign the transaction 23 | -e, --expires INTEGER Expiration time in seconds (defaults to 30) 24 | -v, --verbose INTEGER Verbosity (0-15) 25 | --version Show version 26 | --help Show this message and exit. 27 | 28 | Commands: 29 | addkey Add a private key to the wallet 30 | allow Add a key/account to an account's permission 31 | approvecommittee Approve committee member(s) 32 | approveproposal Approve a proposal 33 | approvewitness Approve witness(es) 34 | balance Show Account balances 35 | broadcast Broadcast a json-formatted transaction 36 | changewalletpassphrase Change the wallet passphrase 37 | configuration Show configuration variables 38 | delkey Delete a private key from the wallet 39 | disallow Remove a key/account from an account's... 40 | disapprovecommittee Disapprove committee member(s) 41 | disapproveproposal Disapprove a proposal 42 | disapprovewitness Disapprove witness(es) 43 | getkey Obtain private key in WIF format 44 | history Show history of an account 45 | info Obtain all kinds of information 46 | listaccounts List accounts (for the connected network) 47 | listkeys List all keys (for all networks) 48 | newaccount Create a new account 49 | permissions Show permissions of an account 50 | randomwif Obtain a random private/public key pair 51 | set Set configuration key/value pair 52 | sign Sign a json-formatted transaction 53 | transfer Transfer assets 54 | upgrade Upgrade Account 55 | 56 | Further help can be obtained via: 57 | 58 | .. code-block:: console 59 | 60 | $ peerplays --help 61 | -------------------------------------------------------------------------------- /peerplays/cli/cli.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | import logging 5 | import ast 6 | 7 | try: 8 | import click 9 | except ImportError: 10 | print("Please install python-click") 11 | sys.exit(1) 12 | 13 | from pprint import pprint 14 | from peerplaysbase.account import PrivateKey 15 | from peerplays.transactionbuilder import TransactionBuilder 16 | from prettytable import PrettyTable 17 | from .ui import print_permissions, print_version 18 | from .decorators import onlineChain, offlineChain, unlockWallet 19 | from .main import main 20 | from . import ( 21 | account, 22 | info, 23 | proposal, 24 | wallet, 25 | witness, 26 | committee, 27 | bookie, 28 | message, 29 | rpc, 30 | asset, 31 | bos, 32 | ) 33 | 34 | log = logging.getLogger(__name__) 35 | 36 | 37 | @main.command(help="Set configuration key/value pair") 38 | @click.pass_context 39 | @offlineChain 40 | @click.argument("key", type=str) 41 | @click.argument("value", type=str) 42 | def set(ctx, key, value): 43 | """ Set configuration parameters 44 | """ 45 | if key == "default_account" and value[0] == "@": 46 | value = value[1:] 47 | ctx.blockchain.config[key] = value 48 | 49 | 50 | @main.command(help="Show configuration variables") 51 | @click.pass_context 52 | @offlineChain 53 | def configuration(ctx): 54 | t = PrettyTable(["Key", "Value"]) 55 | t.align = "l" 56 | for key in ctx.blockchain.config: 57 | if key not in ["encrypted_master_password"]: 58 | t.add_row([key, ctx.blockchain.config[key]]) 59 | click.echo(t) 60 | 61 | 62 | @main.command(help="Sign a json-formatted transaction") 63 | @click.pass_context 64 | @offlineChain 65 | @click.argument("filename", required=False, type=click.File("r")) 66 | @unlockWallet 67 | def sign(ctx, filename): 68 | if filename: 69 | tx = filename.read() 70 | else: 71 | tx = sys.stdin.read() 72 | tx = TransactionBuilder(ast.literal_eval(tx), peerplays_instance=ctx.peerplays) 73 | tx.appendMissingSignatures() 74 | tx.sign() 75 | pprint(tx.json()) 76 | 77 | 78 | @main.command(help="Broadcast a json-formatted transaction") 79 | @click.pass_context 80 | @onlineChain 81 | @click.argument("filename", required=False, type=click.File("r")) 82 | def broadcast(ctx, filename): 83 | if filename: 84 | tx = filename.read() 85 | else: 86 | tx = sys.stdin.read() 87 | tx = TransactionBuilder(ast.literal_eval(tx), peerplays_instance=ctx.peerplays) 88 | tx.broadcast() 89 | pprint(tx.json()) 90 | 91 | 92 | @main.command(help="Obtain a random private/public key pair") 93 | @click.option("--prefix", type=str, default="PPY", help="The refix to use") 94 | @click.option("--num", type=int, default=1, help="The number of keys to derive") 95 | def randomwif(prefix, num): 96 | t = PrettyTable(["wif", "pubkey"]) 97 | for n in range(0, num): 98 | wif = PrivateKey() 99 | t.add_row([str(wif), format(wif.pubkey, prefix)]) 100 | click.echo(str(t)) 101 | -------------------------------------------------------------------------------- /peerplays/exceptions.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from graphenestorage.exceptions import WrongMasterPasswordException 3 | from graphenecommon.exceptions import ( 4 | AccountDoesNotExistsException, 5 | AssetDoesNotExistsException, 6 | BlockDoesNotExistsException, 7 | CommitteeMemberDoesNotExistsException, 8 | InvalidAssetException, 9 | InvalidMemoKeyException, 10 | InvalidMessageSignature, 11 | InvalidWifError, 12 | KeyAlreadyInStoreException, 13 | KeyNotFound, 14 | MissingKeyError, 15 | NoWalletException, 16 | OfflineHasNoRPCException, 17 | ProposalDoesNotExistException, 18 | VestingBalanceDoesNotExistsException, 19 | WalletExists, 20 | WalletLocked, 21 | WitnessDoesNotExistsException, 22 | WorkerDoesNotExistsException, 23 | WrongMemoKey, 24 | GenesisBalanceDoesNotExistsException, 25 | ) 26 | 27 | 28 | class RPCConnectionRequired(Exception): 29 | """ An RPC connection is required 30 | """ 31 | 32 | pass 33 | 34 | 35 | class AccountExistsException(Exception): 36 | """ The requested account already exists 37 | """ 38 | 39 | pass 40 | 41 | 42 | class ObjectNotInProposalBuffer(Exception): 43 | """ Object was not found in proposal 44 | """ 45 | 46 | pass 47 | 48 | 49 | class RPCConnectionRequired(Exception): 50 | """ An RPC connection is required 51 | """ 52 | 53 | pass 54 | 55 | 56 | class AccountExistsException(Exception): 57 | """ The requested account already exists 58 | """ 59 | 60 | pass 61 | 62 | 63 | class InsufficientAuthorityError(Exception): 64 | """ The transaction requires signature of a higher authority 65 | """ 66 | 67 | pass 68 | 69 | 70 | class WrongMasterPasswordException(Exception): 71 | """ The password provided could not properly unlock the wallet 72 | """ 73 | 74 | pass 75 | 76 | 77 | class BetDoesNotExistException(Exception): 78 | """ This bet does not exist 79 | """ 80 | 81 | pass 82 | 83 | 84 | class BettingMarketDoesNotExistException(Exception): 85 | """ Betting market does not exist 86 | """ 87 | 88 | pass 89 | 90 | 91 | class BettingMarketGroupDoesNotExistException(Exception): 92 | """ Betting Market Group does not exist 93 | """ 94 | 95 | pass 96 | 97 | 98 | class EventDoesNotExistException(Exception): 99 | """ This event does not exist 100 | """ 101 | 102 | pass 103 | 104 | 105 | class EventGroupDoesNotExistException(Exception): 106 | """ This event group does not exist 107 | """ 108 | 109 | pass 110 | 111 | 112 | class SportDoesNotExistException(Exception): 113 | """ Sport does not exist 114 | """ 115 | 116 | pass 117 | 118 | 119 | class RuleDoesNotExistException(Exception): 120 | """ Rule does not exist 121 | """ 122 | 123 | pass 124 | 125 | 126 | class ObjectNotInProposalBuffer(Exception): 127 | """ Object was not found in proposal 128 | """ 129 | 130 | pass 131 | 132 | 133 | class GenesisBalanceDoesNotExistsException(Exception): 134 | """ The provided genesis balance id does not exist 135 | """ 136 | 137 | pass 138 | -------------------------------------------------------------------------------- /tests/test_nft.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from pprint import pprint 3 | from peerplays import PeerPlays 4 | from peerplaysbase.operationids import getOperationNameForId 5 | from peerplays.instance import set_shared_peerplays_instance 6 | from .fixtures import fixture_data, peerplays, core_unit 7 | import random 8 | import string 9 | 10 | 11 | def get_random_string(length): 12 | letters = string.ascii_lowercase 13 | result_str = ''.join(random.choice(letters) for i in range(length)) 14 | return result_str 15 | 16 | 17 | class Testcases(unittest.TestCase): 18 | 19 | def setUp(self): 20 | # fixture_data() 21 | self.nameMetadata = get_random_string(5) 22 | self.nameNft = get_random_string(5) 23 | # self.nameMetaData = "testmeta56" 24 | # self.nameNft = "testnft56" 25 | 26 | def test_nft(self): 27 | print("============nft test begin=========") 28 | self.setUp() 29 | # peerplays.blocking = True 30 | self.res = peerplays.nft_metadata_create("1.2.7", 31 | self.nameMetadata, 32 | self.nameMetadata, 33 | self.nameMetadata, 34 | revenue_partner="1.2.8", 35 | revenue_split=300, 36 | is_sellable=False, 37 | is_transferable=False) 38 | # peerplays.blocking = False 39 | print("nft_metadata_create Success!") 40 | 41 | self.metadataId = self.res["operation_results"][0][1] 42 | print("metadataId:", self.metadataId) 43 | 44 | peerplays.nft_metadata_update("1.2.7", 45 | self.metadataId, 46 | self.nameMetadata + "m", 47 | self.nameMetadata + "m", 48 | self.nameMetadata + "m", 49 | "1.2.9", 50 | 400, 51 | True, 52 | True) 53 | print("nft_metadata_update Success!") 54 | 55 | self.res = peerplays.nft_mint("1.2.7", 56 | self.metadataId, 57 | "1.2.7", 58 | "1.2.7", 59 | "1.2.7", 60 | self.nameNft) 61 | print("nft_mint Success!") 62 | 63 | self.tokenId = self.res["operation_results"][0][1] 64 | 65 | peerplays.nft_safe_transfer_from("1.2.7", 66 | "1.2.7", 67 | "1.2.9", 68 | self.tokenId, 69 | "whatever") 70 | print("nft_safe_transfer_from Success!") 71 | 72 | peerplays.nft_approve("1.2.9", "1.2.8", self.tokenId) 73 | print("nft_approve Success!") 74 | 75 | peerplays.nft_set_approval_for_all("1.2.7", "1.2.10", True) 76 | print("nft_set_approval_for_all Success!") 77 | 78 | print("All tests successful!") 79 | 80 | if __name__ == "__main__": 81 | self = Testcases() 82 | s = self 83 | -------------------------------------------------------------------------------- /tests/test_account.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import mock 3 | from pprint import pprint 4 | from peerplays import PeerPlays 5 | from peerplays.account import Account 6 | from peerplays.amount import Amount 7 | from peerplays.asset import Asset 8 | from peerplays.instance import set_shared_peerplays_instance 9 | from peerplaysbase.operationids import getOperationNameForId 10 | from .fixtures import fixture_data, peerplays 11 | import string 12 | import random 13 | 14 | 15 | class Testcases(unittest.TestCase): 16 | 17 | def setUp(self): 18 | # fixture_data() 19 | peerplays.nobroadcast = False 20 | peerplays.blocking = True 21 | pass 22 | 23 | def test_account(self): 24 | Account("witness-account") 25 | Account("1.2.3") 26 | asset = Asset("1.3.0") 27 | symbol = asset["symbol"] 28 | account = Account("witness-account", full=True) 29 | self.assertEqual(account.name, "witness-account") 30 | self.assertEqual(account["name"], account.name) 31 | self.assertEqual(account["id"], "1.2.1") 32 | self.assertIsInstance(account.balance("1.3.0"), Amount) 33 | self.assertIsInstance(account.balance({"symbol": symbol}), Amount) 34 | self.assertIsInstance(account.balances, list) 35 | for h in account.history(limit=1): 36 | pass 37 | 38 | # BlockchainObjects method 39 | account.cached = False 40 | self.assertTrue(account.items()) 41 | account.cached = False 42 | self.assertIn("id", account) 43 | account.cached = False 44 | self.assertEqual(account["id"], "1.2.1") 45 | # self.assertEqual(str(account), "") 46 | self.assertIsInstance(Account(account), Account) 47 | 48 | def test_account_creation(self): 49 | peerplays.blocking = True 50 | account_name = "".join(random.choices(string.ascii_lowercase, k=10)) 51 | account_name = account_name + "".join(random.choices(string.digits, k=10)) 52 | print("account_name:", account_name) 53 | peerplays.blocking = True 54 | print("peerplays blocking:", peerplays.blocking) 55 | op_res = peerplays.create_account( 56 | account_name, 57 | referrer="1.2.7", 58 | password=account_name, 59 | blocking=True 60 | ) 61 | print("op_res_keys:", op_res.keys()) 62 | print("op_res:", op_res) 63 | print("op_res_keys:", op_res.keys()) 64 | self.assertTrue(op_res["operation_results"][0][0]) 65 | account_id = op_res["operation_results"][0][1] 66 | account_name_from_chain = peerplays.rpc.get_object(account_id)["name"] 67 | self.assertEqual(account_name, account_name_from_chain) 68 | peerplays.transfer(account_name, 10000, "TEST", account="nathan") 69 | tx = peerplays.upgrade_account(account_name) 70 | account = Account(account_name) 71 | # tx = account.upgrade() 72 | ops = tx["operations"] 73 | op = ops[0][1] 74 | self.assertEqual(len(ops), 1) 75 | self.assertEqual( 76 | getOperationNameForId(ops[0][0]), 77 | "account_upgrade" 78 | ) 79 | self.assertTrue( 80 | op["upgrade_to_lifetime_member"] 81 | ) 82 | self.assertEqual( 83 | op["account_to_upgrade"], 84 | account["id"], 85 | ) 86 | -------------------------------------------------------------------------------- /peerplaysbase/operationids.py: -------------------------------------------------------------------------------- 1 | ops = [ 2 | "transfer", 3 | "limit_order_create", 4 | "limit_order_cancel", 5 | "call_order_update", 6 | "fill_order", 7 | "account_create", 8 | "account_update", 9 | "account_whitelist", 10 | "account_upgrade", 11 | "account_transfer", 12 | "asset_create", 13 | "asset_update", 14 | "asset_update_bitasset", 15 | "asset_update_feed_producers", 16 | "asset_issue", 17 | "asset_reserve", 18 | "asset_fund_fee_pool", 19 | "asset_settle", 20 | "asset_global_settle", 21 | "asset_publish_feed", 22 | "witness_create", 23 | "witness_update", 24 | "proposal_create", 25 | "proposal_update", 26 | "proposal_delete", 27 | "withdraw_permission_create", 28 | "withdraw_permission_update", 29 | "withdraw_permission_claim", 30 | "withdraw_permission_delete", 31 | "committee_member_create", 32 | "committee_member_update", 33 | "committee_member_update_global_parameters", 34 | "vesting_balance_create", 35 | "vesting_balance_withdraw", 36 | "worker_create", 37 | "custom", 38 | "assert", 39 | "balance_claim", 40 | "override_transfer", 41 | "transfer_to_blind", 42 | "blind_transfer", 43 | "transfer_from_blind", 44 | "asset_settle_cancel", 45 | "asset_claim_fees", 46 | "fba_distribute", 47 | "tournament_create", 48 | "tournament_join", 49 | "game_move", 50 | "asset_update_dividend", 51 | "asset_dividend_distribution", 52 | "tournament_payout", 53 | "tournament_leave", 54 | "sport_create", 55 | "sport_update", 56 | "event_group_create", 57 | "event_group_update", 58 | "event_create", 59 | "event_update", 60 | "betting_market_rules_create", 61 | "betting_market_rules_update", 62 | "betting_market_group_create", 63 | "betting_market_create", 64 | "bet_place", 65 | "betting_market_group_resolve", 66 | "betting_market_group_resolved", 67 | "bet_adjusted", 68 | "betting_market_group_cancel_unmatched_bets", 69 | "bet_matched", 70 | "bet_cancel", 71 | "bet_canceled", 72 | "betting_market_group_update", 73 | "betting_market_update", 74 | "event_update_status", 75 | "sport_delete", 76 | "event_group_delete", 77 | "affiliate_payout", 78 | "affiliate_referral_payout", 79 | "lottery_asset_create", 80 | "ticket_purchase", 81 | "lottery_reward", 82 | "lottery_end", 83 | "sweeps_vesting_claim", 84 | "custom_permission_create", 85 | "custom_permission_update", 86 | "custom_permission_delete", 87 | "custom_account_authority_create", 88 | "custom_account_authority_update", 89 | "custom_account_authority_delete", 90 | "offer", 91 | "bid", 92 | "cancel_offer", 93 | "finalize_offer_operation", 94 | "nft_metadata_create", 95 | "nft_metadata_update", 96 | "nft_mint", 97 | "nft_safe_transfer_from", 98 | "nft_approve", 99 | "nft_set_approval_for_all" 100 | ] 101 | operations = {o: ops.index(o) for o in ops} 102 | 103 | 104 | def getOperationNameForId(i): 105 | """ Convert an operation id into the corresponding string 106 | """ 107 | for key in operations: 108 | if int(operations[key]) is int(i): 109 | return key 110 | return "Unknown Operation ID %d" % i 111 | -------------------------------------------------------------------------------- /tests/archived/test_son.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import mock 3 | from pprint import pprint 4 | from peerplays import PeerPlays 5 | from peerplays.account import Account 6 | from peerplays.amount import Amount 7 | from peerplays.asset import Asset 8 | from peerplays.instance import set_shared_peerplays_instance 9 | from peerplaysbase.operationids import getOperationNameForId 10 | from .fixtures import fixture_data, peerplays 11 | import string 12 | import random 13 | 14 | from peerplays.son import Son 15 | 16 | # Initializing son objet 17 | urlWitness = "http://10.11.12.101:8092" 18 | son = Son(urlWitness = urlWitness) 19 | 20 | 21 | class Testcases(unittest.TestCase): 22 | 23 | def setUp(self): 24 | # fixture_data() 25 | peerplays.nobroadcast = False 26 | peerplays.blocking = True 27 | pass 28 | 29 | def test_a(self): 30 | r = son.create_son("sonaccount01", "http://sonaddreess01.com", [["bitcoin", "03456772301e221026269d3095ab5cb623fc239835b583ae4632f99a15107ef275"], ["ethereum", "5fbbb31be52608d2f52247e8400b7fcaa9e0bc12"], ["hive", "sonaccount01"], ["peerplays", "TEST8TCQFzyYDp3DPgWZ24261fMPSCzXxVyoF3miWeTj6JTi2DZdrL"]]) 31 | 32 | 33 | def test_account_creation(self): 34 | peerplays.blocking = True 35 | account_name = "".join(random.choices(string.ascii_lowercase, k=10)) 36 | account_name = account_name + "".join(random.choices(string.digits, k=10)) 37 | print("account_name:", account_name) 38 | peerplays.blocking = True 39 | print("peerplays blocking:", peerplays.blocking) 40 | 41 | op_res = peerplays.create_account( 42 | account_name, 43 | referrer="1.2.7", 44 | password=account_name, 45 | blocking=True 46 | ) 47 | print("op_res_keys:", op_res.keys()) 48 | print("op_res:", op_res) 49 | print("op_res_keys:", op_res.keys()) 50 | self.assertTrue(op_res["operation_results"][0][0]) 51 | account_id = op_res["operation_results"][0][1] 52 | account_name_from_chain = peerplays.rpc.get_object(account_id)["name"] 53 | self.assertEqual(account_name, account_name_from_chain) 54 | peerplays.transfer(account_name, 10000, "TEST", account="nathan") 55 | tx = peerplays.upgrade_account(account_name) 56 | account = Account(account_name) 57 | print("====================================", account, "============================") 58 | ops = tx["operations"] 59 | op = ops[0][1] 60 | self.assertEqual(len(ops), 1) 61 | self.assertEqual( 62 | getOperationNameForId(ops[0][0]), 63 | "account_upgrade" 64 | ) 65 | self.assertTrue( 66 | op["upgrade_to_lifetime_member"] 67 | ) 68 | self.assertEqual( 69 | op["account_to_upgrade"], 70 | account["id"], 71 | ) 72 | account_name = "test1234" 73 | r = son.create_son("sonaccount01", "http://sonaddreess01.com", [["bitcoin", "03456772301e221026269d3095ab5cb623fc239835b583ae4632f99a15107ef275"], ["ethereum", "5fbbb31be52608d2f52247e8400b7fcaa9e0bc12"], ["hive", "sonaccount01"], ["peerplays", "TEST8TCQFzyYDp3DPgWZ24261fMPSCzXxVyoF3miWeTj6JTi2DZdrL"]]) 74 | print ("tested ========================") 75 | print(r) 76 | 77 | if __name__ == "__main__": 78 | r = son.delete_sidechain_address("sonaccount01", "hive") 79 | r = son.request_son_maintenance("sonaccount01") 80 | print(r) 81 | 82 | -------------------------------------------------------------------------------- /tests/test_market_place.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from pprint import pprint 3 | from peerplays import PeerPlays 4 | from peerplaysbase.operationids import getOperationNameForId 5 | from peerplays.instance import set_shared_peerplays_instance 6 | from .fixtures import fixture_data, peerplays, core_unit 7 | import random 8 | import string 9 | 10 | 11 | def get_random_string(length): 12 | letters = string.ascii_lowercase 13 | result_str = ''.join(random.choice(letters) for i in range(length)) 14 | return result_str 15 | 16 | 17 | class Testcases(unittest.TestCase): 18 | 19 | def setUp(self): 20 | fixture_data() 21 | self.nameMetadata = get_random_string(10) 22 | self.nameNft = get_random_string(10) 23 | 24 | def test_nft(self): 25 | 26 | self.setUp() 27 | 28 | self.res = peerplays.nft_metadata_create("1.2.9", 29 | self.nameMetadata, 30 | self.nameMetadata, 31 | self.nameMetadata, 32 | revenue_partner="1.2.8", 33 | revenue_split=300, 34 | is_sellable=False, 35 | is_transferable=False) 36 | # peerplays.blocking = False 37 | print("nft_metadata_create Success!") 38 | 39 | self.metadataId = self.res["operation_results"][0][1] 40 | print("metadataId:", self.metadataId) 41 | 42 | peerplays.nft_metadata_update("1.2.9", 43 | self.metadataId, 44 | self.nameMetadata + "m", 45 | self.nameMetadata + "m", 46 | self.nameMetadata + "m", 47 | "1.2.9", 48 | 400, 49 | True, 50 | True) 51 | print("nft_metadata_update Success!") 52 | 53 | self.res = peerplays.nft_mint("1.2.9", 54 | self.metadataId, 55 | "1.2.9", 56 | "1.2.9", 57 | "1.2.9", 58 | self.nameNft) 59 | print("nft_mint Success!") 60 | 61 | self.tokenId = self.res["operation_results"][0][1] 62 | 63 | # offers = peerplays.rpc.get_offers_by_item("1.29.0", "1.31.5", 1) 64 | offers = peerplays.rpc.get_offers_by_item("1.29.0", self.tokenId, 1) 65 | if len(offers) >= 1: 66 | offer = offers[0] 67 | peerplays.cancel_offer("1.2.9", offer["id"]) 68 | 69 | # peerplays.create_offer(["1.31.5"], "1.2.9", {"amount":5,"asset_id":"1.3.0"}, {"amount":15,"asset_id":"1.3.0"}, False, "2030-09-18T11:05:39", "") 70 | peerplays.create_offer([self.tokenId], "1.2.9", {"amount":5,"asset_id":"1.3.0"}, {"amount":15,"asset_id":"1.3.0"}, False, "2030-09-18T11:05:39", "") 71 | print("create_off Success!") 72 | 73 | # offers = peerplays.rpc.get_offers_by_item("1.29.0", "1.31.5", 1) 74 | offers = peerplays.rpc.get_offers_by_item("1.29.0", self.tokenId, 1) 75 | offer = offers[0] 76 | peerplays.create_bid("1.2.10", {"amount":8,"asset_id":"1.3.0"}, offer["id"]) 77 | print("create_bid Success!") 78 | 79 | peerplays.cancel_offer("1.2.9", offer["id"]) 80 | print("cancel_offer Success!") 81 | 82 | print("All tests successful!") 83 | -------------------------------------------------------------------------------- /peerplays/notify.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from events import Events 3 | from peerplaysapi.websocket import PeerPlaysWebsocket 4 | from peerplays.instance import shared_peerplays_instance 5 | from peerplays.account import Account, AccountUpdate 6 | log = logging.getLogger(__name__) 7 | # logging.basicConfig(level=logging.DEBUG) 8 | 9 | 10 | class Notify(Events): 11 | """ Notifications on Blockchain events. 12 | 13 | :param list accounts: Account names/ids to be notified about when changing 14 | :param list objects: Object ids to be notified about when changed 15 | :param fnt on_tx: Callback that will be called for each transaction received 16 | :param fnt on_block: Callback that will be called for each block received 17 | :param fnt on_account: Callback that will be called for changes of the listed accounts 18 | :param peerplays.peerplays.PeerPlays peerplays_instance: PeerPlays instance 19 | 20 | **Example** 21 | 22 | .. code-block:: python 23 | 24 | from pprint import pprint 25 | from peerplays.notify import Notify 26 | 27 | notify = Notify( 28 | accounts=["xeroc"], 29 | on_account=print, 30 | on_block=print, 31 | on_tx=print 32 | ) 33 | notify.listen() 34 | 35 | 36 | """ 37 | 38 | __events__ = [ 39 | 'on_tx', 40 | 'on_object', 41 | 'on_block', 42 | 'on_account', 43 | ] 44 | 45 | def __init__( 46 | self, 47 | accounts=[], 48 | objects=[], 49 | on_tx=None, 50 | on_object=None, 51 | on_block=None, 52 | on_account=None, 53 | peerplays_instance=None, 54 | ): 55 | # Events 56 | super(Notify, self).__init__() 57 | self.events = Events() 58 | 59 | # PeerPlays instance 60 | self.peerplays = peerplays_instance or shared_peerplays_instance() 61 | 62 | # Accounts 63 | account_ids = [] 64 | for account_name in accounts: 65 | account = Account( 66 | account_name, 67 | peerplays_instance=self.peerplays 68 | ) 69 | account_ids.append(account["id"]) 70 | 71 | # Callbacks 72 | if on_tx: 73 | self.on_tx += on_tx 74 | if on_object: 75 | self.on_object += on_object 76 | if on_block: 77 | self.on_block += on_block 78 | if on_account: 79 | self.on_account += on_account 80 | 81 | # Open the websocket 82 | self.websocket = PeerPlaysWebsocket( 83 | urls=self.peerplays.rpc.urls, 84 | user=self.peerplays.rpc.user, 85 | password=self.peerplays.rpc.password, 86 | accounts=account_ids, 87 | objects=objects, 88 | on_tx=on_tx, 89 | on_object=on_object, 90 | on_block=on_block, 91 | on_account=self.process_account, 92 | ) 93 | 94 | def process_account(self, message): 95 | """ This is used for processing of account Updates. It will 96 | return instances of :class:peerplays.account.AccountUpdate` 97 | """ 98 | self.on_account(AccountUpdate( 99 | message, 100 | blockchain_instance=self.blockchain 101 | )) 102 | 103 | def listen(self): 104 | """ This call initiates the listening/notification process. It 105 | behaves similar to ``run_forever()``. 106 | """ 107 | self.websocket.run_forever() 108 | -------------------------------------------------------------------------------- /peerplaysbase/account.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | import sys 3 | from binascii import hexlify 4 | from graphenebase.account import ( 5 | PasswordKey as GPHPasswordKey, 6 | BrainKey as GPHBrainKey, 7 | Address as GPHAddress, 8 | PublicKey as GPHPublicKey, 9 | PrivateKey as GPHPrivateKey, 10 | ) 11 | 12 | default_prefix = "PPY" 13 | 14 | 15 | class PasswordKey(GPHPasswordKey): 16 | """ This class derives a private key given the account name, the 17 | role and a password. It leverages the technology of Brainkeys 18 | and allows people to have a secure private key by providing a 19 | passphrase only. 20 | """ 21 | 22 | prefix = default_prefix 23 | 24 | 25 | class BrainKey(GPHBrainKey): 26 | """Brainkey implementation similar to the graphene-ui web-wallet. 27 | 28 | :param str brainkey: Brain Key 29 | :param int sequence: Sequence number for consecutive keys 30 | 31 | Keys in Graphene are derived from a seed brain key which is a string of 32 | 16 words out of a predefined dictionary with 49744 words. It is a 33 | simple single-chain key derivation scheme that is not compatible with 34 | BIP44 but easy to use. 35 | 36 | Given the brain key, a private key is derived as:: 37 | 38 | privkey = SHA256(SHA512(brainkey + " " + sequence)) 39 | 40 | Incrementing the sequence number yields a new key that can be 41 | regenerated given the brain key. 42 | """ 43 | 44 | prefix = default_prefix 45 | 46 | 47 | class Address(GPHAddress): 48 | """ Address class 49 | 50 | This class serves as an address representation for Public Keys. 51 | 52 | :param str address: Base58 encoded address (defaults to ``None``) 53 | :param str pubkey: Base58 encoded pubkey (defaults to ``None``) 54 | :param str prefix: Network prefix (defaults to ``PPY``) 55 | 56 | Example:: 57 | 58 | Address("PPYFN9r6VYzBK8EKtMewfNbfiGCr56pHDBFi") 59 | 60 | """ 61 | 62 | prefix = default_prefix 63 | 64 | 65 | class PublicKey(GPHPublicKey): 66 | """ This class deals with Public Keys and inherits ``Address``. 67 | 68 | :param str pk: Base58 encoded public key 69 | :param str prefix: Network prefix (defaults to ``PPY``) 70 | 71 | Example::: 72 | 73 | PublicKey("PPY6UtYWWs3rkZGV8JA86qrgkG6tyFksgECefKE1MiH4HkLD8PFGL") 74 | 75 | .. note:: By default, graphene-based networks deal with **compressed** 76 | public keys. If an **uncompressed** key is required, the 77 | method ``unCompressed`` can be used:: 78 | 79 | PublicKey("xxxxx").unCompressed() 80 | 81 | """ 82 | 83 | prefix = default_prefix 84 | 85 | 86 | class PrivateKey(GPHPrivateKey): 87 | """ Derives the compressed and uncompressed public keys and 88 | constructs two instances of ``PublicKey``: 89 | 90 | :param str wif: Base58check-encoded wif key 91 | :param str prefix: Network prefix (defaults to ``PPY``) 92 | 93 | Example::: 94 | 95 | PrivateKey("5HqUkGuo62BfcJU5vNhTXKJRXuUi9QSE6jp8C3uBJ2BVHtB8WSd") 96 | 97 | Compressed vs. Uncompressed: 98 | 99 | * ``PrivateKey("w-i-f").pubkey``: 100 | Instance of ``PublicKey`` using compressed key. 101 | * ``PrivateKey("w-i-f").pubkey.address``: 102 | Instance of ``Address`` using compressed key. 103 | * ``PrivateKey("w-i-f").uncompressed``: 104 | Instance of ``PublicKey`` using uncompressed key. 105 | * ``PrivateKey("w-i-f").uncompressed.address``: 106 | Instance of ``Address`` using uncompressed key. 107 | 108 | """ 109 | 110 | prefix = default_prefix 111 | -------------------------------------------------------------------------------- /tests/test_object_creation_parents.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import unittest 3 | from peerplays import PeerPlays 4 | from peerplays.utils import parse_time 5 | from peerplays.exceptions import ObjectNotInProposalBuffer 6 | from peerplaysbase.operationids import getOperationNameForId 7 | from peerplays.instance import set_shared_peerplays_instance 8 | 9 | wif = "5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3" 10 | 11 | 12 | class Testcases(unittest.TestCase): 13 | 14 | def __init__(self, *args, **kwargs): 15 | super().__init__(*args, **kwargs) 16 | 17 | self.ppy = PeerPlays( 18 | nobroadcast=True, 19 | # Overwrite wallet to use this list of wifs only 20 | wif=[wif] 21 | ) 22 | set_shared_peerplays_instance(self.ppy) 23 | self.ppy.set_default_account("init0") 24 | 25 | def test_event_create(self): 26 | ev = [["de", "1. Bundesliga"], ["en", "First Country League"]] 27 | desc = [["de", "Bundesliga"], ["en", "Germany Scoccer Championship"]] 28 | season = [["de", "Januar 2016"], ["en", "January 2016"]] 29 | start = datetime.datetime(2016, 1, 1, 0, 0, 0) 30 | rule_name = [["en", "NHL Rules v1.0"]] 31 | rule = [["en", "The winner will be the team with the most points ..."]] 32 | bmg_name = [["de", "Meine Market Group"], ["en", "My betting market group"]] 33 | bm_name = [["de", "Nuernberg gewinnt"], ["en", "Nuremberg wins"]] 34 | cond = [["de", "Description: Fuerth gewinnt"], 35 | ["en", "Description: Fuerth wins"]] 36 | 37 | with self.assertRaises(ObjectNotInProposalBuffer): 38 | self.ppy.event_group_create(ev, sport_id="0.0.0") 39 | with self.assertRaises(ObjectNotInProposalBuffer): 40 | self.ppy.event_create(desc, season, start, event_group_id="0.0.0") 41 | with self.assertRaises(ObjectNotInProposalBuffer): 42 | self.ppy.betting_market_group_create(bmg_name, event_id="0.0.2", rules_id="0.0.3") 43 | with self.assertRaises(ObjectNotInProposalBuffer): 44 | self.ppy.betting_market_group_create(bmg_name, event_id="0.0.3", rules_id="0.0.4") 45 | 46 | proposal = self.ppy.proposal() 47 | 48 | # Sport (0) 49 | self.ppy.sport_create(["en", "testsport"], append_to=proposal) 50 | 51 | # Eventgroup (1) 52 | self.ppy.event_group_create(ev, sport_id="0.0.0", append_to=proposal) 53 | with self.assertRaises(ObjectNotInProposalBuffer): 54 | self.ppy.event_group_create(ev, sport_id="0.0.1", append_to=proposal) 55 | 56 | # Event (2) 57 | self.ppy.event_create(desc, season, start, event_group_id="0.0.1", append_to=proposal) 58 | with self.assertRaises(ObjectNotInProposalBuffer): 59 | self.ppy.event_create(desc, season, start, event_group_id="0.0.2") 60 | self.ppy.event_create(desc, season, start, event_group_id="0.0.0") 61 | 62 | # Rule (3) 63 | self.ppy.betting_market_rules_create(rule_name, rule, append_to=proposal) 64 | 65 | # BMG (4) 66 | self.ppy.betting_market_group_create(bmg_name, event_id="0.0.2", rules_id="0.0.3", append_to=proposal) 67 | with self.assertRaises(ObjectNotInProposalBuffer): 68 | self.ppy.betting_market_group_create(bmg_name, event_id="0.0.3", rules_id="0.0.4", append_to=proposal) 69 | self.ppy.betting_market_group_create(bmg_name, event_id="0.0.1", rules_id="0.0.4", append_to=proposal) 70 | 71 | # BM (5) 72 | self.ppy.betting_market_create(cond, bm_name, group_id="0.0.4", append_to=proposal) 73 | with self.assertRaises(ObjectNotInProposalBuffer): 74 | self.ppy.betting_market_create(cond, bm_name, group_id="0.0.3", append_to=proposal) 75 | self.ppy.betting_market_create(cond, bm_name, group_id="0.0.5", append_to=proposal) 76 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. python-peerplays documentation master file, created by 2 | sphinx-quickstart on Fri Jun 5 14:06:38 2015. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | .. http://sphinx-doc.org/rest.html 7 | http://sphinx-doc.org/markup/index.html 8 | http://sphinx-doc.org/markup/para.html 9 | http://openalea.gforge.inria.fr/doc/openalea/doc/_build/html/source/sphinx/rest_syntax.html 10 | http://rest-sphinx-memo.readthedocs.org/en/latest/ReST.html 11 | 12 | Welcome to pypeerplays's documentation! 13 | =============================================== 14 | 15 | PeerPlays is a **blockchain-based autonomous company** (i.e. a DAC) that 16 | offers gaming and tournaments on a blockchain. 17 | 18 | It is based on *Graphene* (tm), a blockchain technology stack (i.e. 19 | software) that allows for fast transactions and a scalable blockchain 20 | solution. In case of PeerPlays, it comes with decentralized gaming 21 | engine and allows setting up and running tournaments of any kind. 22 | 23 | About this Library 24 | ------------------ 25 | 26 | The purpose of *pypeerplays* is to simplify development of products and 27 | services that use the PeerPlays blockchain. It comes with 28 | 29 | * it's own (bip32-encrypted) wallet 30 | * RPC interface for the Blockchain backend 31 | * JSON-based blockchain objects (accounts, blocks, events, etc) 32 | * a simple to use yet powerful API 33 | * transaction construction and signing 34 | * push notification API 35 | * *and more* 36 | 37 | Quickstart 38 | ---------- 39 | 40 | .. note:: All methods that construct and sign a transaction can be given 41 | the ``account=`` parameter to identify the user that is going 42 | to affected by this transaction, e.g.: 43 | 44 | * the source account in a transfer 45 | * the accout that buys/sells an asset in the exchange 46 | * the account whos collateral will be modified 47 | 48 | **Important**, If no ``account`` is given, then the 49 | ``default_account`` according to the settings in ``config`` is 50 | used instead. 51 | 52 | .. code-block:: python 53 | 54 | from peerplays import PeerPlays 55 | peerplays = PeerPlays() 56 | peerplays.wallet.unlock("wallet-passphrase") 57 | peerplays.transfer("", "", "", [""], account="") 58 | 59 | .. code-block:: python 60 | 61 | from peerplays.blockchain import Blockchain 62 | blockchain = Blockchain() 63 | for op in Blockchain.ops(): 64 | print(op) 65 | 66 | .. code-block:: python 67 | 68 | from peerplays.block import Block 69 | print(Block(1)) 70 | 71 | .. code-block:: python 72 | 73 | from peerplays.account import Account 74 | account = Account("init0") 75 | print(account.balances) 76 | print(account.openorders) 77 | for h in account.history(): 78 | print(h) 79 | 80 | 81 | General 82 | ------------------------- 83 | .. toctree:: 84 | :maxdepth: 1 85 | 86 | installation 87 | quickstart 88 | tutorials 89 | configuration 90 | contribute 91 | support 92 | stati 93 | 94 | Command Line Tool 95 | ----------------- 96 | 97 | .. toctree:: 98 | :maxdepth: 1 99 | 100 | cli 101 | 102 | Packages 103 | -------- 104 | 105 | peerplays 106 | ~~~~~~~~~ 107 | 108 | .. toctree:: 109 | :maxdepth: 3 110 | 111 | peerplays 112 | 113 | peerplaysbase 114 | ~~~~~~~~~~~~~ 115 | 116 | .. toctree:: 117 | :maxdepth: 3 118 | 119 | peerplaysbase 120 | 121 | Tutorials 122 | --------- 123 | 124 | .. toctree:: 125 | :maxdepth: 2 126 | 127 | tutorials/index 128 | 129 | Indices and tables 130 | ================== 131 | 132 | * :ref:`genindex` 133 | * :ref:`modindex` 134 | * :ref:`search` 135 | -------------------------------------------------------------------------------- /tests/fixtures_hrp.py: -------------------------------------------------------------------------------- 1 | import os 2 | import yaml 3 | 4 | from peerplays import PeerPlays 5 | from peerplays.account import Account 6 | from peerplays.instance import set_shared_peerplays_instance 7 | from peerplays.sport import Sports, Sport 8 | from peerplays.bet import Bet 9 | from peerplays.event import Events, Event 10 | from peerplays.rule import Rules, Rule 11 | from peerplays.proposal import Proposals, Proposal 12 | from peerplays.eventgroup import EventGroups, EventGroup 13 | from peerplays.bettingmarketgroup import BettingMarketGroups, BettingMarketGroup 14 | from peerplays.bettingmarket import BettingMarkets, BettingMarket 15 | from peerplays.witness import Witnesses, Witness 16 | from peerplaysbase.operationids import operations 17 | 18 | # default wifs key for testing 19 | wifs = [ 20 | "5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3", 21 | "5KCBDTcyDqzsqehcb52tW5nU6pXife6V2rX9Yf7c3saYSzbDZ5W", 22 | ] 23 | wif = wifs[0] 24 | core_unit = "TEST" 25 | 26 | # peerplays instance 27 | peerplays = PeerPlays( 28 | # "wss://api.ppy-beatrice.blckchnd.com", keys=wifs, nobroadcast=True, num_retries=1 29 | # "wss://irona.peerplays.download/api", keys=wifs, nobroadcast=True, num_retries=1 30 | "wss://fred.peerplays.download/api", keys=wifs, nobroadcast=True, num_retries=1 31 | ) 32 | # Set defaults 33 | # peerplays.set_default_account("init0") 34 | set_shared_peerplays_instance(peerplays) 35 | 36 | # Ensure we are not going to transaction anythin on chain! 37 | assert peerplays.nobroadcast 38 | 39 | 40 | def fixture_data(): 41 | peerplays.clear() 42 | BettingMarkets.clear_cache() 43 | Rules.clear_cache() 44 | BettingMarketGroups.clear_cache() 45 | Proposals.clear_cache() 46 | Witnesses.clear_cache() 47 | Events.clear_cache() 48 | EventGroups.clear_cache() 49 | Sports.clear_cache() 50 | 51 | with open(os.path.join(os.path.dirname(__file__), "fixtures.yaml")) as fid: 52 | data = yaml.safe_load(fid) 53 | 54 | [Account(x) for x in data.get("accounts", [])] 55 | [Account(x).store(x, "name") for x in data.get("accounts", [])] 56 | Witnesses.cache_objects([Witness(x) for x in data.get("witnesses", [])]) 57 | Sports.cache_objects([Sport(x) for x in data.get("sports", [])]) 58 | EventGroups.cache_objects([EventGroup(x) for x in data.get("eventgroups", [])]) 59 | Events.cache_objects([Event(x) for x in data.get("events", [])]) 60 | BettingMarketGroups.cache_objects( 61 | [BettingMarketGroup(x) for x in data.get("bettingmarketgroups", [])] 62 | ) 63 | BettingMarkets.cache_objects( 64 | [BettingMarket(x) for x in data.get("bettingmarkets", [])] 65 | ) 66 | Rules.cache_objects([Rule(x) for x in data.get("rules", [])]) 67 | [Bet(x) for x in data.get("bets", [])] 68 | 69 | proposals = [] 70 | for proposal in data.get("proposals", []): 71 | ops = list() 72 | for _op in proposal["operations"]: 73 | for opName, op in _op.items(): 74 | ops.append([operations[opName], op]) 75 | # Proposal! 76 | proposal_id = proposal["proposal_id"] 77 | proposal_data = { 78 | "available_active_approvals": [], 79 | "available_key_approvals": [], 80 | "available_owner_approvals": [], 81 | "expiration_time": "2018-05-29T10:23:13", 82 | "id": proposal_id, 83 | "proposed_transaction": { 84 | "expiration": "2018-05-29T10:23:13", 85 | "extensions": [], 86 | "operations": ops, 87 | "ref_block_num": 0, 88 | "ref_block_prefix": 0, 89 | }, 90 | "proposer": "1.2.7", 91 | "required_active_approvals": ["1.2.1"], 92 | "required_owner_approvals": [], 93 | } 94 | proposals.append(Proposal(proposal_data)) 95 | 96 | Proposals.cache_objects(proposals, "1.2.1") 97 | Proposals.cache_objects(proposals, "witness-account") 98 | -------------------------------------------------------------------------------- /docs/tutorials/howto-monitor-blocks.rst: -------------------------------------------------------------------------------- 1 | *************************************************** 2 | Howto Monitor the blockchain for certain operations 3 | *************************************************** 4 | 5 | Block Structure 6 | =============== 7 | 8 | A block takes the following form: 9 | 10 | .. code-block:: js 11 | 12 | {'extensions': [], 13 | 'previous': '000583428a021b14c02f0faaff12a4c686e475e3', 14 | 'timestamp': '2017-04-21T08:38:35', 15 | 'transaction_merkle_root': '328be3287f89aa4d21c69cb617c4fcc372465493', 16 | 'transactions': [{'expiration': '2017-04-21T08:39:03', 17 | 'extensions': [], 18 | 'operation_results': [[0, {}]], 19 | 'operations': [ 20 | [0, 21 | {'amount': {'amount': 100000, 22 | 'asset_id': '1.3.0'}, 23 | 'extensions': [], 24 | 'fee': {'amount': 2089843, 25 | 'asset_id': '1.3.0'}, 26 | 'from': '1.2.18', 27 | 'memo': {'from': 'PPY1894jUspGi6fZwnUmaeCPDZpke6m4T9bHtKrd966M7qYz665xjr', 28 | 'message': '5d09c06c4794f9bcdef9d269774209be', 29 | 'nonce': '7364013452905740719', 30 | 'to': 'PPY16MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV'}, 31 | 'to': '1.2.6'}] 32 | ], 33 | 'ref_block_num': 33602, 34 | 'ref_block_prefix': 337314442, 35 | 'signatures': ['1f3755deaa7f9........']}], 36 | 'witness': '1.6.4', 37 | 'witness_signature': '2052571f091c4542...........'} 38 | 39 | Please note that a block can **carry multiple transactions** while each 40 | transaction **carries multiple operations**. Each operation could be a 41 | **transfer**, or any other type of operation from a list of available 42 | operations. Technically, an operation could be seen as a smart contract 43 | that comes with operation-specific side-information and results in some 44 | changes in the blockchain database. 45 | 46 | In the example above, the operation type is identified by the ``0``, 47 | which makes it a ``transfer`` and the structure afterwards carries the 48 | transfer-specific side information, e.g. ``from``, ``to`` accounts, 49 | ``fee`` aswell as the ``memo``. 50 | 51 | 52 | Polling Approach 53 | ================ 54 | 55 | Blocks can be polled with as little code as this: 56 | 57 | .. code-block:: python 58 | 59 | from peerplays.blockchain import Blockchain 60 | chain = Blockchain() 61 | for block in chain.blocks(start=START_BLOCK): 62 | print(block) 63 | 64 | .. note:: ``chain.blocks()`` is a blocking call that will wait for new 65 | blocks and yield them to the for loop when they arrive. 66 | 67 | Alternatively, one can construct a loop that only yields the operations 68 | on the blockchain and does not show the block structure: 69 | 70 | .. code-block:: python 71 | 72 | from peerplays.blockchain import Blockchain 73 | chain = Blockchain() 74 | for op in chain.ops(start=START_BLOCK): # Note the `ops` 75 | print(op) 76 | 77 | If you are only interested in transfers, you may want to use this 78 | instead: 79 | 80 | .. code-block:: python 81 | 82 | from peerplays.blockchain import Blockchain 83 | chain = Blockchain() 84 | for transfer in chain.stream(opNames=["transfer"], start=START_BLOCK): # Note the `ops` 85 | print(transfer) 86 | 87 | 88 | .. warning:: By default, the ``Blockchain()`` instance will only look at 89 | **irrversible** blocks, this means that blocks are only 90 | considered if they are approved/signed by a majority of the 91 | witnesses and this lacks behind the head block by a short 92 | period of time (in the seconds to low minutes). 93 | 94 | Notification Approach 95 | ===================== 96 | 97 | *under construction* 98 | -------------------------------------------------------------------------------- /docs/tutorials.rst: -------------------------------------------------------------------------------- 1 | ********* 2 | Tutorials 3 | ********* 4 | 5 | Bundle Many Operations 6 | ---------------------- 7 | 8 | With PeerPlays, you can bundle multiple operations into a single 9 | transactions. This can be used to do a multi-send (one sender, multiple 10 | receivers), but it also allows to use any other kind of operation. The 11 | advantage here is that the user can be sure that the operations are 12 | executed in the same order as they are added to the transaction. 13 | 14 | .. code-block:: python 15 | 16 | from pprint import pprint 17 | from peerplays import PeerPlays 18 | 19 | testnet = PeerPlays( 20 | "wss://node.testnet.peerplays.eu", 21 | nobroadcast=True, 22 | bundle=True, 23 | ) 24 | 25 | testnet.wallet.unlock("supersecret") 26 | 27 | testnet.transfer("init0", 1, "TEST", account="xeroc") 28 | testnet.transfer("init1", 1, "TEST", account="xeroc") 29 | testnet.transfer("init2", 1, "TEST", account="xeroc") 30 | testnet.transfer("init3", 1, "TEST", account="xeroc") 31 | 32 | pprint(testnet.broadcast()) 33 | 34 | 35 | Proposing a Transaction 36 | ----------------------- 37 | 38 | In PeerPlays, you can propose a transactions to any account. This is 39 | used to facilitate on-chain multisig transactions. With 40 | python-peerplays, you can do this simply by using the ``proposer`` 41 | attribute: 42 | 43 | .. code-block:: python 44 | 45 | from pprint import pprint 46 | from peerplays import PeerPlays 47 | 48 | testnet = PeerPlays( 49 | "wss://node.testnet.peerplays.eu", 50 | proposer="xeroc" 51 | ) 52 | testnet.wallet.unlock("supersecret") 53 | pprint(testnet.transfer("init0", 1, "TEST", account="xeroc")) 54 | 55 | Simple Sell Script 56 | ------------------ 57 | 58 | .. code-block:: python 59 | 60 | from peerplays import PeerPlays 61 | from peerplays.market import Market 62 | from peerplays.price import Price 63 | from peerplays.amount import Amount 64 | 65 | # 66 | # Instanciate PeerPlays (pick network via API node) 67 | # 68 | peerplays = PeerPlays( 69 | "wss://node.testnet.peerplays.eu", 70 | nobroadcast=True # <<--- set this to False when you want to fire! 71 | ) 72 | 73 | # 74 | # Unlock the Wallet 75 | # 76 | peerplays.wallet.unlock("") 77 | 78 | # 79 | # This defines the market we are looking at. 80 | # The first asset in the first argument is the *quote* 81 | # Sell and buy calls always refer to the *quote* 82 | # 83 | market = Market( 84 | "GOLD:USD", 85 | peerplays_instance=peerplays 86 | ) 87 | 88 | # 89 | # Sell an asset for a price with amount (quote) 90 | # 91 | print(market.sell( 92 | Price(100.0, "USD/GOLD"), 93 | Amount("0.01 GOLD") 94 | )) 95 | 96 | 97 | Sell at a timely rate 98 | --------------------- 99 | 100 | .. code-block:: python 101 | 102 | import threading 103 | from peerplays import PeerPlays 104 | from peerplays.market import Market 105 | from peerplays.price import Price 106 | from peerplays.amount import Amount 107 | 108 | 109 | def sell(): 110 | """ Sell an asset for a price with amount (quote) 111 | """ 112 | print(market.sell( 113 | Price(100.0, "USD/GOLD"), 114 | Amount("0.01 GOLD") 115 | )) 116 | 117 | threading.Timer(60, sell).start() 118 | 119 | 120 | if __name__ == "__main__": 121 | # 122 | # Instanciate PeerPlays (pick network via API node) 123 | # 124 | peerplays = PeerPlays( 125 | "wss://node.testnet.peerplays.eu", 126 | nobroadcast=True # <<--- set this to False when you want to fire! 127 | ) 128 | 129 | # 130 | # Unlock the Wallet 131 | # 132 | peerplays.wallet.unlock("") 133 | 134 | # 135 | # This defines the market we are looking at. 136 | # The first asset in the first argument is the *quote* 137 | # Sell and buy calls always refer to the *quote* 138 | # 139 | market = Market( 140 | "GOLD:USD", 141 | peerplays_instance=peerplays 142 | ) 143 | 144 | sell() 145 | -------------------------------------------------------------------------------- /tests/test_txbuffers.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from pprint import pprint 3 | from peerplays import PeerPlays 4 | from peerplaysbase import operations 5 | from peerplays.instance import set_shared_peerplays_instance 6 | 7 | wif = "5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3" 8 | 9 | 10 | class Testcases(unittest.TestCase): 11 | def __init__(self, *args, **kwargs): 12 | super().__init__(*args, **kwargs) 13 | 14 | self.ppy = PeerPlays( 15 | nobroadcast=True, 16 | wif={ 17 | # Force signing with this key 18 | "active": wif 19 | }, 20 | ) 21 | set_shared_peerplays_instance(self.ppy) 22 | self.ppy.set_default_account("init0") 23 | 24 | def test_add_one_proposal_one_op(self): 25 | ppy = self.ppy 26 | tx1 = ppy.new_tx() 27 | proposal1 = ppy.new_proposal(tx1, proposer="init0") 28 | op = operations.Transfer( 29 | **{ 30 | "fee": {"amount": 0, "asset_id": "1.3.0"}, 31 | "from": "1.2.0", 32 | "to": "1.2.0", 33 | "amount": {"amount": 0, "asset_id": "1.3.0"}, 34 | "prefix": "PPY", 35 | } 36 | ) 37 | proposal1.appendOps(op) 38 | tx = tx1.json() 39 | self.assertEqual(tx["operations"][0][0], 22) 40 | self.assertEqual(len(tx["operations"]), 1) 41 | ps = tx["operations"][0][1] 42 | self.assertEqual(len(ps["proposed_ops"]), 1) 43 | self.assertEqual(ps["proposed_ops"][0]["op"][0], 0) 44 | 45 | def test_add_one_proposal_two_ops(self): 46 | ppy = self.ppy 47 | tx1 = ppy.new_tx() 48 | proposal1 = ppy.new_proposal(tx1, proposer="init0") 49 | op = operations.Transfer( 50 | **{ 51 | "fee": {"amount": 0, "asset_id": "1.3.0"}, 52 | "from": "1.2.0", 53 | "to": "1.2.0", 54 | "amount": {"amount": 0, "asset_id": "1.3.0"}, 55 | "prefix": "PPY", 56 | } 57 | ) 58 | proposal1.appendOps(op) 59 | proposal1.appendOps(op) 60 | tx = tx1.json() 61 | self.assertEqual(tx["operations"][0][0], 22) 62 | self.assertEqual(len(tx["operations"]), 1) 63 | ps = tx["operations"][0][1] 64 | self.assertEqual(len(ps["proposed_ops"]), 2) 65 | self.assertEqual(ps["proposed_ops"][0]["op"][0], 0) 66 | self.assertEqual(ps["proposed_ops"][1]["op"][0], 0) 67 | 68 | def test_have_two_proposals(self): 69 | ppy = self.ppy 70 | tx1 = ppy.new_tx() 71 | 72 | # Proposal 1 73 | proposal1 = ppy.new_proposal(tx1, proposer="init0") 74 | op = operations.Transfer( 75 | **{ 76 | "fee": {"amount": 0, "asset_id": "1.3.0"}, 77 | "from": "1.2.0", 78 | "to": "1.2.0", 79 | "amount": {"amount": 0, "asset_id": "1.3.0"}, 80 | "prefix": "PPY", 81 | } 82 | ) 83 | for i in range(0, 3): 84 | proposal1.appendOps(op) 85 | 86 | # Proposal 1 87 | proposal2 = ppy.new_proposal(tx1, proposer="init0") 88 | op = operations.Transfer( 89 | **{ 90 | "fee": {"amount": 0, "asset_id": "1.3.0"}, 91 | "from": "1.2.0", 92 | "to": "1.2.0", 93 | "amount": {"amount": 5555555, "asset_id": "1.3.0"}, 94 | "prefix": "PPY", 95 | } 96 | ) 97 | for i in range(0, 2): 98 | proposal2.appendOps(op) 99 | tx = tx1.json() 100 | 101 | self.assertEqual(len(tx["operations"]), 2) # 2 proposals 102 | 103 | # Test proposal 1 104 | prop = tx["operations"][0] 105 | self.assertEqual(prop[0], 22) 106 | ps = prop[1] 107 | self.assertEqual(len(ps["proposed_ops"]), 3) 108 | for i in range(0, 3): 109 | self.assertEqual(ps["proposed_ops"][i]["op"][0], 0) 110 | 111 | # Test proposal 2 112 | prop = tx["operations"][1] 113 | self.assertEqual(prop[0], 22) 114 | ps = prop[1] 115 | self.assertEqual(len(ps["proposed_ops"]), 2) 116 | for i in range(0, 2): 117 | self.assertEqual(ps["proposed_ops"][i]["op"][0], 0) 118 | -------------------------------------------------------------------------------- /tests/test_proposals.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from pprint import pprint 3 | from peerplays import PeerPlays 4 | from peerplaysbase.operationids import getOperationNameForId 5 | from peerplays.instance import set_shared_peerplays_instance 6 | from .fixtures import fixture_data, peerplays, core_unit 7 | 8 | 9 | class Testcases(unittest.TestCase): 10 | 11 | def setUp(self): 12 | fixture_data() 13 | 14 | def test_finalizeOps_proposal(self): 15 | # proposal = peerplays.new_proposal(peerplays.tx()) 16 | proposal = peerplays.proposal() 17 | peerplays.transfer("init1", 1, core_unit, append_to=proposal) 18 | tx = peerplays.tx().json() # default tx buffer 19 | ops = tx["operations"] 20 | self.assertEqual(len(ops), 1) 21 | self.assertEqual( 22 | getOperationNameForId(ops[0][0]), 23 | "proposal_create") 24 | prop = ops[0][1] 25 | self.assertEqual(len(prop["proposed_ops"]), 1) 26 | self.assertEqual( 27 | getOperationNameForId(prop["proposed_ops"][0]["op"][0]), 28 | "transfer") 29 | 30 | def test_finalizeOps_proposal2(self): 31 | proposal = peerplays.new_proposal() 32 | # proposal = peerplays.proposal() 33 | peerplays.transfer("init1", 1, core_unit, append_to=proposal) 34 | tx = peerplays.tx().json() # default tx buffer 35 | ops = tx["operations"] 36 | self.assertEqual(len(ops), 1) 37 | self.assertEqual( 38 | getOperationNameForId(ops[0][0]), 39 | "proposal_create") 40 | prop = ops[0][1] 41 | self.assertEqual(len(prop["proposed_ops"]), 1) 42 | self.assertEqual( 43 | getOperationNameForId(prop["proposed_ops"][0]["op"][0]), 44 | "transfer") 45 | 46 | def test_finalizeOps_combined_proposal(self): 47 | parent = peerplays.new_tx() 48 | proposal = peerplays.new_proposal(parent) 49 | peerplays.transfer("init1", 1, core_unit, append_to=proposal) 50 | peerplays.transfer("init1", 1, core_unit, append_to=parent) 51 | tx = parent.json() 52 | ops = tx["operations"] 53 | self.assertEqual(len(ops), 2) 54 | self.assertEqual( 55 | getOperationNameForId(ops[0][0]), 56 | "proposal_create") 57 | self.assertEqual( 58 | getOperationNameForId(ops[1][0]), 59 | "transfer") 60 | prop = ops[0][1] 61 | self.assertEqual(len(prop["proposed_ops"]), 1) 62 | self.assertEqual( 63 | getOperationNameForId(prop["proposed_ops"][0]["op"][0]), 64 | "transfer") 65 | 66 | def test_finalizeOps_changeproposer_new(self): 67 | proposal = peerplays.proposal(proposer="init5") 68 | peerplays.transfer("init1", 1, core_unit, append_to=proposal) 69 | tx = peerplays.tx().json() 70 | ops = tx["operations"] 71 | self.assertEqual(len(ops), 1) 72 | self.assertEqual( 73 | getOperationNameForId(ops[0][0]), 74 | "proposal_create") 75 | prop = ops[0][1] 76 | self.assertEqual(len(prop["proposed_ops"]), 1) 77 | self.assertEqual(prop["fee_paying_account"], "1.2.12") 78 | self.assertEqual( 79 | getOperationNameForId(prop["proposed_ops"][0]["op"][0]), 80 | "transfer") 81 | 82 | def test_finalizeOps_changeproposer_legacy(self): 83 | peerplays.proposer = "init5" 84 | tx = peerplays.transfer("init1", 1, core_unit) 85 | ops = tx["operations"] 86 | self.assertEqual(len(ops), 1) 87 | self.assertEqual( 88 | getOperationNameForId(ops[0][0]), 89 | "proposal_create") 90 | prop = ops[0][1] 91 | self.assertEqual(len(prop["proposed_ops"]), 1) 92 | self.assertEqual(prop["fee_paying_account"], "1.2.12") 93 | self.assertEqual( 94 | getOperationNameForId(prop["proposed_ops"][0]["op"][0]), 95 | "transfer") 96 | 97 | def test_new_proposals(self): 98 | p1 = peerplays.new_proposal() 99 | p2 = peerplays.new_proposal() 100 | self.assertIsNotNone(id(p1), id(p2)) 101 | 102 | def test_new_txs(self): 103 | p1 = peerplays.new_tx() 104 | p2 = peerplays.new_tx() 105 | self.assertIsNotNone(id(p1), id(p2)) 106 | 107 | 108 | if __name__ == "__main__": 109 | unittest.main() 110 | -------------------------------------------------------------------------------- /docs/tutorials/howto-trusted-node.rst: -------------------------------------------------------------------------------- 1 | ***************************************** 2 | Trusted Network and Client Configuration 3 | ***************************************** 4 | 5 | Introduction 6 | ____________________ 7 | 8 | Similar to other crypto currencies, it is recommended to wait for several 9 | confirmations of a transcation. Even though the consensus scheme of Graphene is 10 | alot more secure than regular proof-of-work or other proof-of-stake schemes, we 11 | still support exchanges that require more confirmations for deposits. 12 | 13 | We provide a so called *delayed* full node which accepts two additional 14 | parameters for the configuration besides those already available with the 15 | standard daemon. 16 | 17 | * `trusted-node` RPC endpoint of a trusted validating node (required) 18 | 19 | The trusted-node is a regular full node directly connected to the P2P 20 | network that works as a proxy. The delay between the trusted node and 21 | the delayed node is chosen automatically in a way that ensures that 22 | blocks that are available in the delayed node are guarenteed to be 23 | **irreversible**. Thus, the delayed full node will be behind the real 24 | blockchain by a few seconds up to only a few minutes. 25 | 26 | .. note:: **Irrversibility**: On DPOS chains, blocks are irreversible if 27 | it has been approved/confirmed by at least 2/3 of all block 28 | validators (i.e. witnesses) 29 | 30 | Overview of the Setup 31 | ------------------------------- 32 | 33 | In the following, we will setup and use the following network::: 34 | 35 | P2P network <-> Trusted Full Node <-> Delayed Full Node <-> API 36 | 37 | * P2P network: 38 | The PeerPlays client uses a peer-to-peer network to connect and broadcasts 39 | transactions there. A block producing full node will eventually catch your 40 | transcaction and validate it by adding it into a new block. 41 | * Trusted Full Node: 42 | We will use a Full node to connect to the network directly. We call it 43 | *trusted* since it is supposed to be under our control. 44 | * Delayed Full Node: 45 | The delayed full node node will provide us with a delayed and several times 46 | confirmed and verified blockchain. Even though DPOS is more resistant against 47 | forks than most other blockchain consensus schemes, we delay the blockchain 48 | here to reduces the risk of forks even more. In the end, the delayed full 49 | node is supposed to never enter an invalid fork. 50 | * API: 51 | Since we have a delayed full node that we can fully trust, we will interface 52 | with this node to query the blockchain and receive notifications from it once 53 | balance changes. 54 | 55 | The delayed full node should be in the same *local* network as the trusted full 56 | node, however only the trusted full node requires public internet access. Hence we will work with 57 | the following IPs: 58 | 59 | * Trusted Full Node: 60 | * extern: *internet access* 61 | * intern: `192.168.0.100` 62 | 63 | * Delayed Full Node: 64 | * extern: *no* internet access required 65 | * intern: `192.168.0.101` 66 | 67 | Let's go into more detail on how to set these up. 68 | 69 | Trusted Full Node 70 | _________________ 71 | 72 | For the trusted full node, the default settings can be used. Later, we 73 | will need to open the RPC port and listen to an IP address to connect the 74 | delayed full node to:: 75 | 76 | ./programs/witness_node/witness_node --rpc-endpoint="192.168.0.100:8090" 77 | 78 | .. note:: A *witness* node is identical to a full node if no authorized 79 | block-signing private key is provided. 80 | 81 | Delayed Full Node 82 | _________________ 83 | 84 | The delayed full node will need the IP address and port of the p2p-endpoint 85 | from the trusted full node and the number of blocks that should be delayed. We 86 | also need to open the RPC/Websocket port (to the local network!) so that we can 87 | interface using RPC-JSON calls. 88 | 89 | For our example and for 10 blocks delayed (i.e. 30 seconds for 3 second block 90 | intervals), we need::: 91 | 92 | ./programs/delayed_node/delayed_node --trusted-node="192.168.0.100:8090" --rpc-endpoint="192.168.0.101:8090" 93 | 94 | We can now connect via RPC: 95 | 96 | * `192.168.0.100:8090` : The trusted full node exposed to the internet 97 | * `192.168.0.101:8090` : The delayed full node not exposed to the internet 98 | 99 | .. note:: For security reasons, an exchange should only interface with the delayed 100 | full node. 101 | 102 | For obvious reasons, the trusted full node is should be running before 103 | attempting to start the delayed full node. 104 | -------------------------------------------------------------------------------- /peerplays/cli/info.py: -------------------------------------------------------------------------------- 1 | import re 2 | import json 3 | import click 4 | from pprint import pprint 5 | from prettytable import PrettyTable 6 | from peerplays.block import Block 7 | from peerplays.amount import Amount 8 | from peerplays.blockchain import Blockchain 9 | from peerplays.account import Account 10 | from peerplays.asset import Asset 11 | from .decorators import onlineChain, unlockWallet 12 | from .main import main 13 | 14 | 15 | @main.command() 16 | @click.pass_context 17 | @onlineChain 18 | @click.argument("objects", type=str, nargs=-1) 19 | def info(ctx, objects): 20 | """ Obtain all kinds of information 21 | """ 22 | if not objects: 23 | t = PrettyTable(["Key", "Value"]) 24 | t.align = "l" 25 | info = ctx.peerplays.rpc.get_dynamic_global_properties() 26 | for key in info: 27 | t.add_row([key, info[key]]) 28 | click.echo(t.get_string(sortby="Key")) 29 | 30 | for obj in objects: 31 | # Block 32 | if re.match("^[0-9]*$", obj): 33 | block = Block(obj, peerplays_instance=ctx.peerplays) 34 | if block: 35 | t = PrettyTable(["Key", "Value"]) 36 | t.align = "l" 37 | for key in sorted(block): 38 | value = block[key] 39 | if key == "transactions": 40 | value = json.dumps(value, indent=4) 41 | t.add_row([key, value]) 42 | click.echo(t) 43 | else: 44 | click.echo("Block number %s unknown" % obj) 45 | # Object Id 46 | elif len(obj.split(".")) == 3: 47 | data = ctx.peerplays.rpc.get_object(obj) 48 | if data: 49 | t = PrettyTable(["Key", "Value"]) 50 | t.align = "l" 51 | for key in sorted(data): 52 | value = data[key] 53 | if isinstance(value, dict) or isinstance(value, list): 54 | value = json.dumps(value, indent=4) 55 | t.add_row([key, value]) 56 | click.echo(t) 57 | else: 58 | click.echo("Object %s unknown" % obj) 59 | 60 | # Asset 61 | elif obj.upper() == obj: 62 | data = Asset(obj) 63 | t = PrettyTable(["Key", "Value"]) 64 | t.align = "l" 65 | for key in sorted(data): 66 | value = data[key] 67 | if isinstance(value, dict): 68 | value = json.dumps(value, indent=4) 69 | t.add_row([key, value]) 70 | click.echo(t) 71 | 72 | # Public Key 73 | elif re.match("^PPY.{48,55}$", obj): 74 | account = ctx.peerplays.wallet.getAccountFromPublicKey(obj) 75 | if account: 76 | t = PrettyTable(["Account"]) 77 | t.align = "l" 78 | t.add_row([account]) 79 | click.echo(t) 80 | else: 81 | click.echo("Public Key not known" % obj) 82 | 83 | # Account name 84 | elif re.match("^[a-zA-Z0-9\-\._]{2,64}$", obj): 85 | account = Account(obj, full=True) 86 | if account: 87 | t = PrettyTable(["Key", "Value"]) 88 | t.align = "l" 89 | for key in sorted(account): 90 | value = account[key] 91 | if isinstance(value, dict) or isinstance(value, list): 92 | value = json.dumps(value, indent=4) 93 | t.add_row([key, value]) 94 | click.echo(t) 95 | else: 96 | click.echo("Account %s unknown" % obj) 97 | else: 98 | click.echo("Couldn't identify object to read") 99 | 100 | 101 | @main.command() 102 | @click.pass_context 103 | @onlineChain 104 | def fees(ctx): 105 | """ List fees 106 | """ 107 | from peerplaysbase.operationids import getOperationNameForId 108 | 109 | chain = Blockchain(peerplays_instance=ctx.peerplays) 110 | feesObj = chain.chainParameters().get("current_fees") 111 | fees = feesObj["parameters"] 112 | 113 | t = PrettyTable(["Operation", "Type", "Fee"]) 114 | t.align = "l" 115 | t.align["Fee"] = "r" 116 | 117 | for fee in fees: 118 | for f in fee[1]: 119 | t.add_row( 120 | [ 121 | getOperationNameForId(fee[0]), 122 | f, 123 | str(Amount({"amount": fee[1].get(f, 0), "asset_id": "1.3.0"})), 124 | ] 125 | ) 126 | click.echo(t) 127 | -------------------------------------------------------------------------------- /tests/fixtures.py: -------------------------------------------------------------------------------- 1 | """ Setup script for the test cases """ 2 | import os 3 | import yaml 4 | 5 | from peerplays.peerplays2 import PeerPlays as PeerPlays2 6 | from peerplays import PeerPlays 7 | from peerplays.account import Account 8 | from peerplays.instance import set_shared_peerplays_instance 9 | from peerplays.sport import Sports, Sport 10 | from peerplays.bet import Bet 11 | from peerplays.event import Events, Event 12 | from peerplays.rule import Rules, Rule 13 | from peerplays.proposal import Proposals, Proposal 14 | from peerplays.eventgroup import EventGroups, EventGroup 15 | from peerplays.bettingmarketgroup import BettingMarketGroups, BettingMarketGroup 16 | from peerplays.bettingmarket import BettingMarkets, BettingMarket 17 | from peerplays.witness import Witnesses, Witness 18 | from peerplaysbase.operationids import operations 19 | 20 | # default wifs key for testing 21 | wifs = [ 22 | "5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3", 23 | "5KCBDTcyDqzsqehcb52tW5nU6pXife6V2rX9Yf7c3saYSzbDZ5W", 24 | ] 25 | wif = wifs[0] 26 | publicKey = "TEST6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV" 27 | core_unit = "TEST" 28 | 29 | # urlWalletServer = "http://10.11.12.101:8091" 30 | # peerplays2 = PeerPlays2(urlWalletServer=urlWalletServer) 31 | # peerplays2.unlock("password") 32 | 33 | # peerplays instance 34 | peerplays = PeerPlays( 35 | # "ws://10.11.12.101:8090", keys=wifs, nobroadcast=False, num_retries=1, blocking=True 36 | # "wss://api.ppy-beatrice.blckchnd.com", keys=wifs, nobroadcast=True, num_retries=1 37 | "wss://mint.peerplays.download/api", keys=wifs, nobroadcast=False, num_retries=1, blocking=True 38 | # "wss://irona.peerplays.download/api", keys=wifs, nobroadcast=False, num_retries=1, blocking=True 39 | # "wss://fred.peerplays.download/api", keys=wifs, nobroadcast=False, num_retries=1, blocking=True 40 | # "wss://hercules.peerplays.download/api", keys=wifs, nobroadcast=False, num_retries=1, blocking=True 41 | ) 42 | # Set defaults 43 | # peerplays.set_default_account("init0") 44 | set_shared_peerplays_instance(peerplays) 45 | 46 | # Ensure we are not going to transaction anythin on chain! 47 | # assert peerplays.nobroadcast 48 | 49 | 50 | def fixture_data(): 51 | peerplays.clear() 52 | BettingMarkets.clear_cache() 53 | Rules.clear_cache() 54 | BettingMarketGroups.clear_cache() 55 | Proposals.clear_cache() 56 | Witnesses.clear_cache() 57 | Events.clear_cache() 58 | EventGroups.clear_cache() 59 | Sports.clear_cache() 60 | 61 | with open(os.path.join(os.path.dirname(__file__), "fixtures.yaml")) as fid: 62 | data = yaml.safe_load(fid) 63 | 64 | [Account(x) for x in data.get("accounts", [])] 65 | [Account(x).store(x, "name") for x in data.get("accounts", [])] 66 | Witnesses.cache_objects([Witness(x) for x in data.get("witnesses", [])]) 67 | Sports.cache_objects([Sport(x) for x in data.get("sports", [])]) 68 | EventGroups.cache_objects([EventGroup(x) for x in data.get("eventgroups", [])]) 69 | Events.cache_objects([Event(x) for x in data.get("events", [])]) 70 | BettingMarketGroups.cache_objects( 71 | [BettingMarketGroup(x) for x in data.get("bettingmarketgroups", [])] 72 | ) 73 | BettingMarkets.cache_objects( 74 | [BettingMarket(x) for x in data.get("bettingmarkets", [])] 75 | ) 76 | Rules.cache_objects([Rule(x) for x in data.get("rules", [])]) 77 | [Bet(x) for x in data.get("bets", [])] 78 | 79 | proposals = [] 80 | for proposal in data.get("proposals", []): 81 | ops = list() 82 | for _op in proposal["operations"]: 83 | for opName, op in _op.items(): 84 | ops.append([operations[opName], op]) 85 | # Proposal! 86 | proposal_id = proposal["proposal_id"] 87 | proposal_data = { 88 | "available_active_approvals": [], 89 | "available_key_approvals": [], 90 | "available_owner_approvals": [], 91 | "expiration_time": "2018-05-29T10:23:13", 92 | "id": proposal_id, 93 | "proposed_transaction": { 94 | "expiration": "2018-05-29T10:23:13", 95 | "extensions": [], 96 | "operations": ops, 97 | "ref_block_num": 0, 98 | "ref_block_prefix": 0, 99 | }, 100 | "proposer": "1.2.7", 101 | "required_active_approvals": ["1.2.1"], 102 | "required_owner_approvals": [], 103 | } 104 | proposals.append(Proposal(proposal_data)) 105 | 106 | Proposals.cache_objects(proposals, "1.2.1") 107 | Proposals.cache_objects(proposals, "witness-account") 108 | -------------------------------------------------------------------------------- /docs/tutorials/howto-blockproducer.rst: -------------------------------------------------------------------------------- 1 | ***************************************** 2 | Setup a witness and block producing node 3 | ***************************************** 4 | 5 | After :doc:`having setup a node `, we can setup a 6 | witness and block producing node. We will need: 7 | 8 | * A compiled ``witness_node`` 9 | * A compiled ``cli_wallet`` 10 | * A registered account 11 | * The active private key to that account 12 | * Some little funds to pay for witness registration in your account 13 | 14 | Lunching the cli_wallet 15 | ======================= 16 | 17 | We first need to launch the cli_wallet and setup a local wallet with it::: 18 | 19 | ./programs/cli_wallet/cli_wallet --server-rpc-endpoint wss://node-to-some-public-api-node 20 | 21 | First thing to do is setting up a password for the newly created wallet prior to 22 | importing any private keys::: 23 | 24 | >>> set_password 25 | null 26 | >>> unlock 27 | null 28 | >>> 29 | 30 | Basic Account Management 31 | ======================== 32 | 33 | We can import your account with:: 34 | 35 | >>> import_key 36 | true 37 | >>> list_my_accounts 38 | [{ 39 | "id": "1.2.15", 40 | ... 41 | "name": , 42 | ... 43 | ] 44 | >>> list_account_balances 45 | XXXXXXX PPY 46 | 47 | Registering a Witness 48 | ===================== 49 | 50 | To become a witness and be able to produce blocks, you first need to create a 51 | witness object that can be voted in. 52 | 53 | We create a new witness by issuing::: 54 | 55 | >>> create_witness "http://" true 56 | { 57 | "ref_block_num": 139, 58 | "ref_block_prefix": 3692461913, 59 | "relative_expiration": 3, 60 | "operations": [[ 61 | 21,{ 62 | "fee": { 63 | "amount": 0, 64 | "asset_id": "1.3.0" 65 | }, 66 | "witness_account": "1.2.16", 67 | "url": "url-to-proposal", 68 | "block_signing_key": "", 69 | "initial_secret": "00000000000000000000000000000000000000000000000000000000" 70 | } 71 | ] 72 | ], 73 | "signatures": [ 74 | "1f2ad5597af2ac4bf7a50f1eef2db49c9c0f7616718776624c2c09a2dd72a0c53a26e8c2bc928f783624c4632924330fc03f08345c8f40b9790efa2e4157184a37" 75 | ] 76 | } 77 | 78 | The cli_wallet will create a new public key for signing ````. We now need to obtain the private key for that::: 80 | 81 | get_private_key 82 | 83 | 84 | Configuration of the Witness Node 85 | ================================= 86 | 87 | Get the witness object using:: 88 | 89 | get_witness 90 | 91 | and take note of two things. The ``id`` is displayed in ``get_global_properties`` 92 | when the witness is voted in, and we will need it on the ``witness_node`` command 93 | line to produce blocks. We'll also need the public ``signing_key`` so we can 94 | look up the correspoinding private key. 95 | 96 | .. code-block:: sh 97 | 98 | >>> get_witness 99 | { 100 | [...] 101 | "id": "1.6.10", 102 | "signing_key": "GPH7vQ7GmRSJfDHxKdBmWMeDMFENpmHWKn99J457BNApiX1T5TNM8", 103 | [...] 104 | } 105 | 106 | The ``id`` and the ``signing_key`` are the two important parameters, here. Let's get 107 | the private key for that signing key with::: 108 | 109 | get_private_key 110 | 111 | Now we need to start the witness, so shut down the wallet (ctrl-d), and shut 112 | down the witness (ctrl-c). Re-launch the witness, now mentioning the new 113 | witness 1.6.10 and its keypair::: 114 | 115 | ./witness_node --rpc-endpoint=127.0.0.1:8090 \ 116 | --witness-id '"1.6.10"' \ 117 | --private-key '["GPH7vQ7GmRSJfDHxKdBmWMeDMFENpmHWKn99J457BNApiX1T5TNM8", "5JGi7DM7J8fSTizZ4D9roNgd8dUc5pirUe9taxYCUUsnvQ4zCaQ"]' 118 | 119 | Alternatively, you can also add this line into yout config.ini::: 120 | 121 | witness-id = "1.6.10" 122 | private-key = ["GPH7vQ7GmRSJfDHxKdBmWMeDMFENpmHWKn99J457BNApiX1T5TNM8","5JGi7DM7J8fSTizZ4D9roNgd8dUc5pirUe9taxYCUUsnvQ4zCaQ"] 123 | 124 | .. note:: Make sure to use YOUR public/private keys instead of the once given 125 | above! 126 | 127 | Verifying Block Production 128 | ########################## 129 | 130 | If you monitor the output of the `witness_node`, you should see it generate 131 | blocks signed by your witness::: 132 | 133 | Witness 1.6.10 production slot has arrived; generating a block now... 134 | Generated block #367 with timestamp 2015-07-05T20:46:30 at time 2015-07-05T20:46:30 135 | -------------------------------------------------------------------------------- /tests/performance/performance_son.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import multiprocessing 5 | import time 6 | import random 7 | 8 | 9 | from binascii import hexlify 10 | from pprint import pprint 11 | 12 | from peerplaysbase import memo, account, operations, objects 13 | from peerplaysbase.account import PrivateKey, PublicKey 14 | from peerplaysbase.objects import Operation 15 | from peerplaysbase.signedtransactions import Signed_Transaction 16 | from peerplays import PeerPlays 17 | 18 | TEST_AGAINST_CLI_WALLET = False 19 | 20 | prefix = "TEST" 21 | wif = "5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3" 22 | ref_block_num = 34294 23 | ref_block_prefix = 3707022213 24 | expiration = "2016-04-06T08:29:27" 25 | GRAPHENE_BETTING_ODDS_PRECISION = 10000 26 | 27 | wifs = [ 28 | "5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3", 29 | "5KCBDTcyDqzsqehcb52tW5nU6pXife6V2rX9Yf7c3saYSzbDZ5W", 30 | ] 31 | wif = wifs[0] 32 | core_unit = "TEST" 33 | 34 | class Testcases(): 35 | def doit(self, printWire=False): 36 | ops = [Operation(self.op)] 37 | tx = Signed_Transaction( 38 | ref_block_num=ref_block_num, 39 | ref_block_prefix=ref_block_prefix, 40 | expiration=expiration, 41 | operations=ops, 42 | ) 43 | tx = tx.sign([wif], chain=prefix) 44 | tx.verify([PrivateKey(wif).pubkey], prefix) 45 | txWire = hexlify(bytes(tx)).decode("ascii") 46 | if printWire: 47 | print() 48 | print(txWire) 49 | print() 50 | self.assertEqual(self.cm[:-130], txWire[:-130]) 51 | 52 | if TEST_AGAINST_CLI_WALLET: 53 | from grapheneapi.grapheneapi import GrapheneAPI 54 | 55 | rpc = GrapheneAPI("localhost", 8092) 56 | self.cm = rpc.serialize_transaction(tx.json()) 57 | self.assertEqual(self.cm[:-130], txWire[:-130]) 58 | 59 | # Test against Bitshares backened 60 | live = peerplays.rpc.get_transaction_hex(tx.json()) 61 | 62 | # Compare expected result with online result 63 | self.assertEqual(live[:-130], txWire[:-130]) 64 | 65 | # Compare expected result with online backend 66 | self.assertEqual(live[:-130], self.cm[:-130]) 67 | 68 | def test_Transfer(self): 69 | pub = format(account.PrivateKey(wif).pubkey, prefix) 70 | from_account_id = "1.2.0" 71 | to_account_id = "1.2.1" 72 | amount = 1000000 73 | asset_id = "1.3.4" 74 | message = "abcdefgABCDEFG0123456789" 75 | nonce = "5862723643998573708" 76 | 77 | fee = objects.Asset(amount=0, asset_id="1.3.0") 78 | amount = objects.Asset(amount=int(amount), asset_id=asset_id) 79 | encrypted_memo = memo.encode_memo( 80 | account.PrivateKey(wif), 81 | account.PublicKey(pub, prefix=prefix), 82 | nonce, 83 | message, 84 | ) 85 | self.op = operations.Transfer( 86 | **{ 87 | "fee": fee, 88 | "from": from_account_id, 89 | "to": to_account_id, 90 | "amount": amount, 91 | "memo": { 92 | "from": pub, 93 | "to": pub, 94 | "nonce": nonce, 95 | "message": encrypted_memo, 96 | }, 97 | "prefix": prefix, 98 | } 99 | ) 100 | self.cm = ( 101 | "f68585abf4dce7c804570100000000000000000000000140420" 102 | "f0000000000040102c0ded2bc1f1305fb0faac5e6c03ee3a192" 103 | "4234985427b6167ca569d13df435cf02c0ded2bc1f1305fb0fa" 104 | "ac5e6c03ee3a1924234985427b6167ca569d13df435cf8c94d1" 105 | "9817945c5120fa5b6e83079a878e499e2e52a76a7739e9de409" 106 | "86a8e3bd8a68ce316cee50b210000011f39e3fa7071b795491e" 107 | "3b6851d61e7c959be92cc7deb5d8491cf1c3c8c99a1eb44553c" 108 | "348fb8f5001a78b18233ac66727e32fc776d48e92d9639d64f6" 109 | "8e641948" 110 | ) 111 | self.doit() 112 | 113 | def transfer_On_Chain(self): 114 | pp = PeerPlays( 115 | "ws://10.11.12.101:8090", keys=wifs, nobroadcast=False, num_retries=1, blocking=False 116 | ) 117 | pp.transfer ("1.2.9", 1, "TEST", memo="", account="1.2.8") #, blocking=False) 118 | print("transfer done") 119 | 120 | testcases = Testcases() 121 | 122 | def task(x): 123 | print(" ") 124 | rand = x 125 | print("-------------", rand, "------------------", "Started") 126 | testcases.transfer_On_Chain() 127 | print(rand, "Done") 128 | print("------------------------------", rand, "---------------", "Ended") 129 | print (" ") 130 | return x 131 | 132 | if __name__ == "__main__": 133 | countTask = 2 134 | p = multiprocessing.Pool(countTask) 135 | tic = time.time() 136 | p.map(task, range(countTask)) 137 | toc = time.time() 138 | 139 | timePerTask = (toc - tic) / countTask 140 | 141 | print("timePerTask:", timePerTask) 142 | 143 | -------------------------------------------------------------------------------- /tests/performance/performance_fund_transfer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import multiprocessing 5 | import time 6 | import random 7 | 8 | 9 | from binascii import hexlify 10 | from pprint import pprint 11 | 12 | from peerplaysbase import memo, account, operations, objects 13 | from peerplaysbase.account import PrivateKey, PublicKey 14 | from peerplaysbase.objects import Operation 15 | from peerplaysbase.signedtransactions import Signed_Transaction 16 | from peerplays import PeerPlays 17 | 18 | TEST_AGAINST_CLI_WALLET = False 19 | 20 | prefix = "TEST" 21 | wif = "5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3" 22 | ref_block_num = 34294 23 | ref_block_prefix = 3707022213 24 | expiration = "2016-04-06T08:29:27" 25 | GRAPHENE_BETTING_ODDS_PRECISION = 10000 26 | 27 | wifs = [ 28 | "5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3", 29 | "5KCBDTcyDqzsqehcb52tW5nU6pXife6V2rX9Yf7c3saYSzbDZ5W", 30 | ] 31 | wif = wifs[0] 32 | core_unit = "TEST" 33 | 34 | class Testcases(): 35 | def doit(self, printWire=False): 36 | ops = [Operation(self.op)] 37 | tx = Signed_Transaction( 38 | ref_block_num=ref_block_num, 39 | ref_block_prefix=ref_block_prefix, 40 | expiration=expiration, 41 | operations=ops, 42 | ) 43 | tx = tx.sign([wif], chain=prefix) 44 | tx.verify([PrivateKey(wif).pubkey], prefix) 45 | txWire = hexlify(bytes(tx)).decode("ascii") 46 | if printWire: 47 | print() 48 | print(txWire) 49 | print() 50 | self.assertEqual(self.cm[:-130], txWire[:-130]) 51 | 52 | if TEST_AGAINST_CLI_WALLET: 53 | from grapheneapi.grapheneapi import GrapheneAPI 54 | 55 | rpc = GrapheneAPI("localhost", 8092) 56 | self.cm = rpc.serialize_transaction(tx.json()) 57 | self.assertEqual(self.cm[:-130], txWire[:-130]) 58 | 59 | # Test against Bitshares backened 60 | live = peerplays.rpc.get_transaction_hex(tx.json()) 61 | 62 | # Compare expected result with online result 63 | self.assertEqual(live[:-130], txWire[:-130]) 64 | 65 | # Compare expected result with online backend 66 | self.assertEqual(live[:-130], self.cm[:-130]) 67 | 68 | def test_Transfer(self): 69 | pub = format(account.PrivateKey(wif).pubkey, prefix) 70 | from_account_id = "1.2.0" 71 | to_account_id = "1.2.1" 72 | amount = 1000000 73 | asset_id = "1.3.4" 74 | message = "abcdefgABCDEFG0123456789" 75 | nonce = "5862723643998573708" 76 | 77 | fee = objects.Asset(amount=0, asset_id="1.3.0") 78 | amount = objects.Asset(amount=int(amount), asset_id=asset_id) 79 | encrypted_memo = memo.encode_memo( 80 | account.PrivateKey(wif), 81 | account.PublicKey(pub, prefix=prefix), 82 | nonce, 83 | message, 84 | ) 85 | self.op = operations.Transfer( 86 | **{ 87 | "fee": fee, 88 | "from": from_account_id, 89 | "to": to_account_id, 90 | "amount": amount, 91 | "memo": { 92 | "from": pub, 93 | "to": pub, 94 | "nonce": nonce, 95 | "message": encrypted_memo, 96 | }, 97 | "prefix": prefix, 98 | } 99 | ) 100 | self.cm = ( 101 | "f68585abf4dce7c804570100000000000000000000000140420" 102 | "f0000000000040102c0ded2bc1f1305fb0faac5e6c03ee3a192" 103 | "4234985427b6167ca569d13df435cf02c0ded2bc1f1305fb0fa" 104 | "ac5e6c03ee3a1924234985427b6167ca569d13df435cf8c94d1" 105 | "9817945c5120fa5b6e83079a878e499e2e52a76a7739e9de409" 106 | "86a8e3bd8a68ce316cee50b210000011f39e3fa7071b795491e" 107 | "3b6851d61e7c959be92cc7deb5d8491cf1c3c8c99a1eb44553c" 108 | "348fb8f5001a78b18233ac66727e32fc776d48e92d9639d64f6" 109 | "8e641948" 110 | ) 111 | self.doit() 112 | 113 | def transfer_On_Chain(self): 114 | pp = PeerPlays( 115 | "ws://10.11.12.101:8090", keys=wifs, nobroadcast=False, num_retries=1, blocking=False 116 | ) 117 | pp.transfer ("1.2.9", 1, "TEST", memo="", account="1.2.8") #, blocking=False) 118 | print("transfer done") 119 | 120 | testcases = Testcases() 121 | 122 | def task(x): 123 | print(" ") 124 | rand = x 125 | print("-------------", rand, "------------------", "Started") 126 | testcases.transfer_On_Chain() 127 | print(rand, "Done") 128 | print("------------------------------", rand, "---------------", "Ended") 129 | print (" ") 130 | return x 131 | 132 | if __name__ == "__main__": 133 | countTask = 2 134 | p = multiprocessing.Pool(countTask) 135 | tic = time.time() 136 | p.map(task, range(countTask)) 137 | toc = time.time() 138 | 139 | timePerTask = (toc - tic) / countTask 140 | 141 | print("timePerTask:", timePerTask) 142 | 143 | --------------------------------------------------------------------------------