├── .flake8 ├── .gitignore ├── LICENSE ├── README.md ├── how_tos ├── crypto │ ├── how_to_apply_a_checksum.py │ ├── how_to_create_key_pairs.py │ ├── how_to_hash_data.py │ └── how_to_sign_data.py ├── deploys │ ├── how_to_delegate.py │ ├── how_to_stake.py │ ├── how_to_transfer.py │ ├── how_to_undelegate.py │ └── how_to_unstake.py ├── node_apis │ ├── how_to_use_rest_client.py │ ├── how_to_use_rpc_client.py │ ├── how_to_use_speculative_rpc_client.py │ └── how_to_use_sse_client.py ├── other │ └── how_to_consume_events_and_write_to_fs.py └── smart_contracts │ ├── how_to_install.py │ ├── how_to_invoke.py │ ├── how_to_query.py │ └── how_to_query_dictionary.py ├── pan.txt ├── poetry.lock ├── pycspr ├── __init__.py ├── api │ ├── __init__.py │ ├── constants.py │ ├── rest │ │ ├── __init__.py │ │ ├── client.py │ │ ├── connection.py │ │ └── proxy.py │ ├── rpc │ │ ├── __init__.py │ │ ├── client.py │ │ ├── connection.py │ │ ├── params.py │ │ └── proxy.py │ ├── rpc_speculative │ │ ├── __init__.py │ │ ├── client.py │ │ ├── connection.py │ │ └── proxy.py │ └── sse │ │ ├── __init__.py │ │ ├── client.py │ │ ├── connection.py │ │ └── proxy.py ├── crypto │ ├── __init__.py │ ├── checksummer.py │ ├── cl_operations.py │ ├── ecc.py │ ├── ecc_ed25519.py │ ├── ecc_secp256k1.py │ ├── hashifier.py │ ├── hashifier_blake2b.py │ └── hashifier_blake3.py ├── factory │ ├── __init__.py │ ├── accounts.py │ ├── deploys.py │ └── digests.py ├── serializer │ ├── __init__.py │ ├── binary │ │ ├── __init__.py │ │ ├── decoder.py │ │ ├── decoder_clt.py │ │ ├── decoder_clv.py │ │ ├── decoder_node.py │ │ ├── encoder.py │ │ ├── encoder_clt.py │ │ ├── encoder_clv.py │ │ └── encoder_node.py │ ├── json │ │ ├── __init__.py │ │ ├── decoder.py │ │ ├── decoder_clt.py │ │ ├── decoder_clv.py │ │ ├── decoder_crypto.py │ │ ├── decoder_node.py │ │ ├── decoder_primitives.py │ │ ├── encoder.py │ │ ├── encoder_clt.py │ │ ├── encoder_clv.py │ │ ├── encoder_crypto.py │ │ ├── encoder_node.py │ │ └── encoder_primitives.py │ └── utils │ │ ├── __init__.py │ │ ├── clv_to_clt.py │ │ └── clv_to_parsed.py ├── types │ ├── __init__.py │ ├── cl.py │ ├── crypto.py │ └── node.py ├── utils │ ├── __init__.py │ ├── constants.py │ ├── convertor.py │ ├── io.py │ └── validation.py └── verifier │ ├── __init__.py │ ├── of_block.py │ └── of_deploy.py ├── pyproject.toml ├── pytest.ini └── tests ├── __init__.py ├── _test_sc_lifecycle_2.py ├── _test_sc_lifecycle_3.py ├── assets ├── accounts │ ├── account-1 │ │ ├── public_key.pem │ │ ├── public_key_hex │ │ └── secret_key.pem │ └── account-2 │ │ ├── public_key.pem │ │ ├── public_key_hex │ │ └── secret_key.pem └── vectors │ ├── cl-types.json │ ├── cl-values.json │ ├── crypto-checksums.json │ ├── crypto-hashes.json │ ├── crypto-key-pairs.json │ ├── crypto-misc.json │ ├── crypto-signatures.json │ └── deploys-1.json ├── conftest.py ├── fixtures ├── __init__.py ├── accounts.py ├── chain.py ├── contracts.py ├── deploys.py ├── iterator_cl_types.py ├── iterator_cl_values.py ├── iterator_deploy_entities.py ├── node.py └── vectors.py ├── test_api_rest.py ├── test_api_rpc_1.py ├── test_api_rpc_2.py ├── test_api_rpc_3.py ├── test_api_rpc_4.py ├── test_api_rpc_5.py ├── test_api_rpc_6.py ├── test_api_rpc_7.py ├── test_api_rpc_8.py ├── test_api_speculative_rpc.py ├── test_api_sse.py ├── test_crypto_1.py ├── test_crypto_2.py ├── test_crypto_3.py ├── test_crypto_4.py ├── test_deploy_lifecycle.py ├── test_digests.py ├── test_factory_1.py ├── test_factory_2.py ├── test_factory_3.py ├── test_interface.py ├── test_sc_lifecycle_1.py ├── test_serializer_1.py ├── test_serializer_2.py ├── test_serializer_3.py ├── test_serializer_4.py ├── test_serializer_5.py ├── test_utils_conversion.py └── utils ├── __init__.py └── cctl.py /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 97 3 | per-file-ignores = __init__.py:F401 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mypy 121 | .mypy_cache/ 122 | .dmypy.json 123 | dmypy.json 124 | 125 | # Pyre type checker 126 | .pyre/ 127 | .pytest_cache/ 128 | 129 | # temporary files 130 | tmp/ 131 | tmp.* -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Casper Python SDK 2 | 3 | Python library for interacting with a CSPR node. 4 | 5 | ## Installation 6 | 7 | ``` 8 | pip3 install pycspr 9 | ``` 10 | 11 | ## Usage 12 | 13 | ### Cryptography 14 | 15 | * [How To: Apply a checksum ?](how_tos/crypto/how_to_apply_a_checksum.py) 16 | 17 | * [How To: Create Key Pairs ?](how_tos/crypto/how_to_create_key_pairs.py) 18 | 19 | * [How To: Hash data ?](how_tos/crypto/how_to_hash_data.py) 20 | 21 | * [How To: Sign data ?](how_tos/crypto/how_to_sign_data.py) 22 | 23 | ### Deploys 24 | 25 | * [How To: Transfer funds between 2 accounts ?](how_tos/deploys/how_to_transfer.py) 26 | 27 | * [How To: Delegate funds to a validator ?](how_tos/deploys/how_to_delegate.py) 28 | 29 | * [How To: Undelegate funds from a validator ?](how_tos/deploys/how_to_undelegate.py) 30 | 31 | * [How To: Stake funds as a validator ?](how_tos/deploys/how_to_stake.py) 32 | 33 | * [How To: Unstake funds as a validator ?](how_tos/deploys/how_to_unstake.py) 34 | 35 | ### Smart Contracts 36 | 37 | * [How To: Install a smart contract ?](how_tos/smart_contracts/how_to_install.py) 38 | 39 | * [How To: Invoke a smart contract ?](how_tos/smart_contracts/how_to_invoke.py) 40 | 41 | * [How To: Query a smart contract ?](how_tos/smart_contracts/how_to_query.py) 42 | 43 | ### Node APIs 44 | 45 | * [How To: Use REST API ?](how_tos/node_apis/how_to_use_rest_client.py) 46 | 47 | * [How To: Use RPC API ?](how_tos/node_apis/how_to_use_rpc_client.py) 48 | 49 | * [How To: Use Speculative RPC API ?](how_tos/node_apis/how_to_use_speculative_rpc_client.py) 50 | 51 | * [How To: Use SSE API ?](how_tos/node_apis/how_to_use_sse_client.py) 52 | 53 | ## Development 54 | 55 | ### Pre-Requisites 56 | 57 | [1. Setup Local CCTL Network](https://github.com/casper-network/cctl). 58 | 59 | [2. Install poetry](https://python-poetry.org). 60 | 61 | ### Install SDK 62 | 63 | ``` 64 | cd YOUR_WORKING_DIRECTORY 65 | git clone https://github.com/casper-network/casper-python-sdk.git 66 | cd casper-python-sdk 67 | poetry install 68 | ```` 69 | 70 | ### Testing 71 | 72 | #### Important Environment Variables 73 | 74 | * Mandatory 75 | 76 | * CCTL 77 | 78 | * path to local clone of CCTL repo 79 | 80 | * Optional 81 | 82 | * PYCSPR_TEST_NODE_HOST 83 | 84 | * host of a test node 85 | * default = localhost 86 | 87 | * PYCSPR_TEST_NODE_PORT_REST 88 | 89 | * port of rest server exposed by test node 90 | * default = 14101 91 | 92 | * PYCSPR_TEST_NODE_PORT_RPC 93 | 94 | * port of json-rpc server exposed by test node 95 | * default = 11101 96 | 97 | * PYCSPR_TEST_NODE_PORT_SSE 98 | 99 | * port of sse server exposed by test node 100 | * default = 18101 101 | 102 | * PYCSPR_TEST_NODE_PORT_RPC_SPECULATIVE 103 | 104 | * port of speculative execution server exposed by test node 105 | * default = 25101 106 | 107 | #### Running Tests 108 | 109 | ``` 110 | cd YOUR_WORKING_DIRECTORY/casper-python-sdk 111 | poetry shell 112 | pytest ./tests [TEST-FILTER] 113 | ``` 114 | -------------------------------------------------------------------------------- /how_tos/crypto/how_to_apply_a_checksum.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import typing 3 | 4 | import pycspr 5 | 6 | 7 | # CLI argument parser. 8 | _ARGS = argparse.ArgumentParser("Illustrates usage of pycspr.checksummer module.") 9 | 10 | 11 | def _main(args: argparse.Namespace): 12 | """Main entry point. 13 | 14 | :param args: Parsed command line arguments. 15 | 16 | """ 17 | print("-" * 74) 18 | print("PYCSPR :: How To Apply A Checksum") 19 | print("") 20 | print("Illustrates usage of pycspr.checksummer module.") 21 | print("-" * 74) 22 | 23 | # Create new key pair & destructure raw public key. 24 | key_pair: typing.Tuple[bytes, bytes] = \ 25 | pycspr.crypto.get_key_pair(algo=pycspr.DEFAULT_KEY_ALGO) 26 | pbk: bytes = key_pair[1] 27 | 28 | # Map raw public key -> raw account key. 29 | account_key: bytes = pycspr.get_account_key(pycspr.DEFAULT_KEY_ALGO, pbk) 30 | 31 | # Use checksummer to checksum account key. 32 | checksum: str = pycspr.checksummer.encode_account_key(account_key) 33 | 34 | print("Account Key:") 35 | print(f" ... raw: {account_key.hex()}") 36 | print(f" ... checksummed: {checksum}") 37 | print("-" * 74) 38 | 39 | 40 | # Entry point. 41 | if __name__ == "__main__": 42 | _main(_ARGS.parse_args()) 43 | -------------------------------------------------------------------------------- /how_tos/crypto/how_to_create_key_pairs.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import base64 3 | import typing 4 | 5 | import pycspr 6 | 7 | 8 | # CLI argument parser. 9 | _ARGS = argparse.ArgumentParser("Demo illustrating how to create key pairs using pycspr.") 10 | 11 | # CLI argument: key algorithm. 12 | _ARGS.add_argument( 13 | "--algo", 14 | default=pycspr.DEFAULT_KEY_ALGO.name, 15 | dest="algo", 16 | help=f"Key algorithm to be used - defaults to {pycspr.DEFAULT_KEY_ALGO.name}.", 17 | type=str, 18 | choices=[i.name for i in pycspr.KeyAlgorithm], 19 | ) 20 | 21 | 22 | def _main(args: argparse.Namespace): 23 | """Main entry point. 24 | 25 | :param args: Parsed command line arguments. 26 | 27 | """ 28 | print("-" * 74) 29 | print("PYCSPR :: How To Create Key Pairs") 30 | print("") 31 | print("Illustrates usage of get_key_pair & get_key_pair_from_* functions.") 32 | print("-" * 74) 33 | 34 | # Parse args. 35 | algo = pycspr.KeyAlgorithm[args.algo] 36 | 37 | # Create new key pair. 38 | key_pair: typing.Union[bytes, bytes] = pycspr.get_key_pair(algo=algo) 39 | assert len(key_pair) == 2 40 | print("... new key pair") 41 | 42 | # Destructure key pair. 43 | pvk, pbk = key_pair 44 | assert isinstance(pvk, bytes) and isinstance(pbk, bytes) 45 | print("... key pair from private bytes") 46 | 47 | # Create key pair from private key as bytes. 48 | key_pair_1 = pycspr.get_key_pair_from_bytes(pvk, algo=algo) 49 | assert key_pair_1 == key_pair 50 | 51 | # Create key pair from private key as hex. 52 | pvk_as_hex = pvk.hex() 53 | key_pair_2 = pycspr.get_key_pair_from_hex_string(pvk_as_hex, algo=algo) 54 | assert key_pair_2 == key_pair 55 | print("... key pair from private hex") 56 | 57 | # Create key pair from private key as base64. 58 | pvk_as_base64 = base64.b64encode(pvk) 59 | key_pair_3 = pycspr.get_key_pair_from_base64(pvk_as_base64, algo=algo) 60 | assert key_pair_3 == key_pair 61 | print("... key pair from private base64") 62 | 63 | # Create key pair from private key as pem file. 64 | pvk_as_pem_file = pycspr.get_pvk_pem_file_from_bytes(pvk, algo) 65 | key_pair_4 = pycspr.get_key_pair_from_pem_file(pvk_as_pem_file, algo=algo) 66 | assert key_pair_4 == key_pair 67 | print("... key pair from private pem") 68 | 69 | print("-" * 74) 70 | 71 | 72 | # Entry point. 73 | if __name__ == "__main__": 74 | _main(_ARGS.parse_args()) 75 | -------------------------------------------------------------------------------- /how_tos/crypto/how_to_hash_data.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | 3 | import pycspr 4 | 5 | 6 | # CLI argument parser. 7 | _ARGS = argparse.ArgumentParser("Demo illustrating how to hash data using pycspr.") 8 | 9 | # CLI argument: data to be hashed. 10 | _ARGS.add_argument( 11 | "--data", 12 | default="أبو يوسف يعقوب بن إسحاق الصبّاح الكندي".encode("utf-8"), 13 | dest="data", 14 | help="Data to be hashed as a hexadecimal string.", 15 | type=str, 16 | ) 17 | 18 | 19 | def _main(args: argparse.Namespace): 20 | """Main entry point. 21 | 22 | :param args: Parsed command line arguments. 23 | 24 | """ 25 | print("-" * 74) 26 | print("PYCSPR :: How To Get Cryptographic Hash") 27 | print("") 28 | print("Illustrates usage of pycspr.get_hash function.") 29 | print("-" * 74) 30 | print(f"Data to be hashed: {args.data.decode()}") 31 | print("-" * 74) 32 | 33 | # Create a digest - algo = default, default size = 32. 34 | digest: bytes = pycspr.get_hash(args.data) 35 | assert isinstance(digest, bytes) and len(digest) == 32 36 | 37 | # Iterate supported algos: 38 | for algo in pycspr.HashAlgorithm: 39 | # Create a digest - default size = 32. 40 | digest: bytes = pycspr.get_hash(args.data, algo=algo, size=32) 41 | assert isinstance(digest, bytes) and len(digest) == 32 42 | print("Hash of data") 43 | print(f" Algo: {algo.name}") 44 | print(f" Hash -> 32 bytes :: {algo.name} = {digest.hex()}") 45 | 46 | # Create a digest - size = 64. 47 | digest: bytes = pycspr.get_hash(args.data, algo=algo, size=64) 48 | assert isinstance(digest, bytes) and len(digest) == 64 49 | print(f" Hash -> 64 bytes :: {algo.name} = {digest.hex()}") 50 | 51 | 52 | # Entry point. 53 | if __name__ == "__main__": 54 | _main(_ARGS.parse_args()) 55 | -------------------------------------------------------------------------------- /how_tos/crypto/how_to_sign_data.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | 3 | import pycspr 4 | 5 | 6 | # CLI argument parser. 7 | _ARGS = argparse.ArgumentParser("Demo illustrating how to sign data using pycspr.") 8 | 9 | # CLI argument: data to be signed. 10 | _ARGS.add_argument( 11 | "--data", 12 | default="أبو يوسف يعقوب بن إسحاق الصبّاح الكندي".encode("utf-8"), 13 | dest="data", 14 | help="Data to be signed.", 15 | type=str, 16 | ) 17 | 18 | 19 | def _main(args: argparse.Namespace): 20 | """Main entry point. 21 | 22 | :param args: Parsed command line arguments. 23 | 24 | """ 25 | print("-" * 74) 26 | print("PYCSPR :: How To Sign Data") 27 | print("") 28 | print("Illustrates usage of pycspr.get_signature function.") 29 | print("-" * 74) 30 | print(f"Data to be signed: {args.data.decode()}") 31 | print("-" * 74) 32 | 33 | # Create a digest - algo = default, size = 32 = default. 34 | digest: bytes = pycspr.get_hash(args.data) 35 | print("Hash of data") 36 | print(f" Algo: {pycspr.DEFAULT_HASH_ALGO.name}") 37 | print(f" Hash: {digest.hex()}") 38 | 39 | # Iterate supported algos: 40 | for algo in pycspr.KeyAlgorithm: 41 | # Sign over digest with a private key. 42 | pvk, _ = pycspr.get_key_pair(algo) 43 | sig: bytes = pycspr.get_signature(digest, algo, pvk) 44 | 45 | print("Signature over hash of data") 46 | print(f" Algo: {algo.name}") 47 | print(f" Sig : {sig.hex()}") 48 | 49 | 50 | # Entry point. 51 | if __name__ == "__main__": 52 | _main(_ARGS.parse_args()) 53 | -------------------------------------------------------------------------------- /how_tos/node_apis/how_to_use_rest_client.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import asyncio 3 | import typing 4 | 5 | from pycspr import NodeRestClient as NodeClient 6 | from pycspr import NodeRestConnectionInfo as NodeConnectionInfo 7 | from pycspr.types.node import NodeStatus 8 | from pycspr.types.node import ValidatorChanges 9 | 10 | 11 | # CLI argument parser. 12 | _ARGS = argparse.ArgumentParser("Demo illustrating how to invoke a node's REST API.") 13 | 14 | # CLI argument: host address of target node - defaults to CCTL node 1. 15 | _ARGS.add_argument( 16 | "--node-host", 17 | default="localhost", 18 | dest="node_host", 19 | help="Host address of target node.", 20 | type=str, 21 | ) 22 | 23 | # CLI argument: Node API REST port - defaults to 14101 @ CCTL node 1. 24 | _ARGS.add_argument( 25 | "--node-port-rest", 26 | default=14101, 27 | dest="node_port_rest", 28 | help="Node API REST port. Typically 8888 on most nodes.", 29 | type=int, 30 | ) 31 | 32 | 33 | class _Context(): 34 | def __init__(self, args: argparse.Namespace): 35 | self.client = NodeClient(NodeConnectionInfo(args.node_host, args.node_port_rest)) 36 | 37 | 38 | async def _main(args: argparse.Namespace): 39 | """Main entry point. 40 | 41 | :param args: Parsed command line arguments. 42 | 43 | """ 44 | print("-" * 74) 45 | print("PYCSPR :: How To Invoke A Node's REST API") 46 | print(f"PYCSPR :: API @ http://{args.node_host}:{args.node_port_rest}") 47 | print("-" * 74) 48 | 49 | ctx = _Context(args) 50 | for func in [ 51 | _get_chainspec, 52 | _get_node_metrics, 53 | _get_node_metric, 54 | _get_node_status_1, 55 | _get_node_status_2, 56 | _get_validator_changes_1, 57 | _get_validator_changes_2, 58 | ]: 59 | await func(ctx) 60 | 61 | print("-" * 74) 62 | 63 | 64 | async def _get_chainspec(ctx: _Context): 65 | data: dict = await ctx.client.get_chainspec() 66 | assert isinstance(data, dict) 67 | print("SUCCESS :: get_chainspec") 68 | 69 | 70 | async def _get_node_metrics(ctx: _Context): 71 | data: typing.List[str] = await ctx.client.get_node_metrics() 72 | assert isinstance(data, list) 73 | print("SUCCESS :: get_node_metrics") 74 | 75 | 76 | async def _get_node_metric(ctx: _Context): 77 | data: typing.List[str] = await ctx.client.get_node_metric("mem_deploy_gossiper") 78 | assert isinstance(data, list) 79 | print("SUCCESS :: get_node_metric") 80 | 81 | 82 | async def _get_node_status_1(ctx: _Context): 83 | data: dict = await ctx.client.get_node_status(decode=False) 84 | assert isinstance(data, dict) 85 | print("SUCCESS :: get_node_status_1") 86 | 87 | 88 | async def _get_node_status_2(ctx: _Context): 89 | data: NodeStatus = await ctx.client.get_node_status() 90 | assert isinstance(data, NodeStatus) 91 | print("SUCCESS :: get_node_status_2") 92 | 93 | 94 | async def _get_validator_changes_1(ctx: _Context): 95 | data: dict = await ctx.client.get_validator_changes(decode=False) 96 | assert isinstance(data, list) 97 | for item in data: 98 | assert isinstance(item, dict) 99 | print("SUCCESS :: get_validator_changes_1") 100 | 101 | 102 | async def _get_validator_changes_2(ctx: _Context): 103 | data: dict = await ctx.client.get_validator_changes() 104 | assert isinstance(data, list) 105 | for item in data: 106 | assert isinstance(item, ValidatorChanges) 107 | print("SUCCESS :: get_validator_changes_2") 108 | 109 | 110 | # Entry point. 111 | if __name__ == "__main__": 112 | asyncio.run(_main(_ARGS.parse_args())) 113 | -------------------------------------------------------------------------------- /how_tos/node_apis/how_to_use_sse_client.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import asyncio 3 | import json 4 | 5 | from pycspr import NodeEventChannel 6 | from pycspr import NodeEventType 7 | from pycspr import NodeEventInfo 8 | from pycspr import NodeSseClient as NodeClient 9 | from pycspr import NodeSseConnectionInfo as NodeConnectionInfo 10 | 11 | 12 | # CLI argument parser. 13 | _ARGS = argparse.ArgumentParser("How to consume node SSE events demo.") 14 | 15 | # CLI argument: host address of target node - defaults to CCTL node 1. 16 | _ARGS.add_argument( 17 | "--node-host", 18 | default="localhost", 19 | dest="node_host", 20 | help="Host address of target node.", 21 | type=str, 22 | ) 23 | 24 | # CLI argument: Node API JSON-RPC port - defaults to 11101 @ CCTL node 1. 25 | _ARGS.add_argument( 26 | "--node-port-rpc", 27 | default=11101, 28 | dest="node_port_rpc", 29 | help="Node API JSON-RPC port. Typically 7777 on most nodes.", 30 | type=int, 31 | ) 32 | 33 | # CLI argument: Node API SSE port - defaults to 18101 @ CCTL node 1. 34 | _ARGS.add_argument( 35 | "--node-port-sse", 36 | default=18101, 37 | dest="node_port_sse", 38 | help="Node API SSE port. Typically 9999 on most nodes.", 39 | type=int, 40 | ) 41 | 42 | # CLI argument: SSE channel type - defaults to main. 43 | _ARGS.add_argument( 44 | "--channel", 45 | default=NodeEventChannel.main.name, 46 | dest="channel", 47 | help="Node event channel to which to bind - defaults to main.", 48 | type=str, 49 | choices=[i.name for i in NodeEventChannel], 50 | ) 51 | 52 | # CLI argument: SSE event type - defaults to all. 53 | _ARGS.add_argument( 54 | "--event", 55 | default="all", 56 | dest="event", 57 | help="Type of event to which to listen to - defaults to all.", 58 | type=str, 59 | choices=["all"] + [i.name for i in NodeEventType], 60 | ) 61 | 62 | 63 | async def main(args: argparse.Namespace): 64 | """Main entry point. 65 | 66 | :param args: Parsed command line arguments. 67 | 68 | """ 69 | print("-" * 74) 70 | print("PYCSPR :: How To Apply Await Events") 71 | print("") 72 | print("Illustrates usage of pycspr.NodeClient.await_* functions.") 73 | print("-" * 74) 74 | 75 | # Set node client. 76 | client: NodeClient = _get_client(args) 77 | 78 | # Await until 2 blocks have been added to linear chain. 79 | print("awaiting 2 blocks ...") 80 | block_height = await client.rpc.get_block_height() 81 | await client.await_n_blocks(2) 82 | assert await client.rpc.get_block_height() == block_height + 2 83 | 84 | # Await until 1 consensus era has elapsed. 85 | print("awaiting 1 era ...") 86 | era_height = await client.rpc.get_era_height() 87 | await client.await_n_eras(1) 88 | assert await client.rpc.get_era_height() == era_height + 1 89 | 90 | # Await until a block in the future. 91 | future_block_height = await client.rpc.get_block_height() + 2 92 | print(f"awaiting until block {future_block_height} ...") 93 | await client.await_until_block_n(future_block_height) 94 | assert await client.rpc.get_block_height() == future_block_height 95 | 96 | # Await until a consensus era in the future. 97 | future_era_height = await client.rpc.get_era_height() + 1 98 | print(f"awaiting until era {future_era_height} ...") 99 | await client.await_until_era_n(future_era_height) 100 | assert future_era_height == await client.rpc.get_era_height() 101 | 102 | # Listen to node events. 103 | client.get_events( 104 | ecallback=_on_event_callback, 105 | echannel=NodeEventChannel[args.channel], 106 | etype=None if args.event == "all" else NodeEventType[args.event], 107 | eid=0 108 | ) 109 | 110 | print("-" * 74) 111 | 112 | 113 | def _get_client(args: argparse.Namespace) -> NodeClient: 114 | """Returns a pycspr client instance. 115 | 116 | """ 117 | return NodeClient(NodeConnectionInfo(args.node_host, args.node_port_sse, args.node_port_rpc)) 118 | 119 | 120 | def _on_event_callback(event_info: NodeEventInfo): 121 | """Event callback handler. 122 | 123 | """ 124 | print("-" * 74) 125 | print(f"Event #{event_info.idx or 0} :: {event_info.channel} :: {event_info.typeof}") 126 | print("-" * 74) 127 | print(json.dumps(event_info.payload, indent=4)) 128 | print("-" * 74) 129 | 130 | 131 | # Entry point. 132 | if __name__ == "__main__": 133 | asyncio.run(main(_ARGS.parse_args())) 134 | -------------------------------------------------------------------------------- /how_tos/other/how_to_consume_events_and_write_to_fs.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import asyncio 3 | import json 4 | 5 | from pycspr import NodeEventChannel 6 | from pycspr import NodeEventInfo 7 | from pycspr import NodeEventType 8 | from pycspr import NodeSseClient as NodeClient 9 | from pycspr import NodeSseConnectionInfo as NodeConnectionInfo 10 | 11 | 12 | # CLI argument parser. 13 | _ARGS = argparse.ArgumentParser("How to consume node SSE events demo.") 14 | 15 | # CLI argument: host address of target node - defaults to CCTL node 1. 16 | _ARGS.add_argument( 17 | "--node-host", 18 | default="localhost", 19 | dest="node_host", 20 | help="Host address of target node.", 21 | type=str, 22 | ) 23 | 24 | # CLI argument: Node API SSE port - defaults to 18101 @ CCTL node 1. 25 | _ARGS.add_argument( 26 | "--node-port-sse", 27 | default=18101, 28 | dest="node_port_sse", 29 | help="Node API SSE port. Typically 9999 on most nodes.", 30 | type=int, 31 | ) 32 | 33 | # CLI argument: SSE channel type - defaults to main. 34 | _ARGS.add_argument( 35 | "--channel", 36 | default=NodeEventChannel.main.name, 37 | dest="channel", 38 | help="Node event channel to which to bind - defaults to main.", 39 | type=str, 40 | choices=[i.name for i in NodeEventChannel], 41 | ) 42 | 43 | # CLI argument: SSE event type - defaults to all. 44 | _ARGS.add_argument( 45 | "--event", 46 | default="all", 47 | dest="event", 48 | help="Type of event to which to listen to - defaults to all.", 49 | type=str, 50 | choices=["all"] + [i.name for i in NodeEventType], 51 | ) 52 | 53 | # CLI argument: Path to output file. 54 | _ARGS.add_argument( 55 | "--output", 56 | dest="path_to_output", 57 | help="Path to output file.", 58 | type=str, 59 | ) 60 | 61 | 62 | async def _main(args: argparse.Namespace): 63 | """Main entry point. 64 | 65 | :param args: Parsed command line arguments. 66 | 67 | """ 68 | print("-" * 74) 69 | print("PYCSPR :: How To Consume Events And Write To File System") 70 | print("") 71 | print("Illustrates usage of pycspr.NodeClient.get_events function.") 72 | print("-" * 74) 73 | 74 | # Set node client. 75 | client = _get_client(args) 76 | 77 | # Write event stream to a file. 78 | with open(args.path_to_output, 'w') as fhandle: 79 | fhandle.write("[\n") 80 | fhandle.flush() 81 | try: 82 | client.get_events( 83 | ecallback=lambda x: _on_event(x, fhandle), 84 | echannel=NodeEventChannel[args.channel], 85 | etype=None if args.event == "all" else NodeEventType[args.event], 86 | eid=0 87 | ) 88 | except KeyboardInterrupt: 89 | pass 90 | finally: 91 | fhandle.write("\n]") 92 | fhandle.flush() 93 | 94 | 95 | def _get_client(args: argparse.Namespace) -> NodeClient: 96 | """Returns a pycspr client instance. 97 | 98 | """ 99 | return NodeClient(NodeConnectionInfo(args.node_host, args.node_port_sse)) 100 | 101 | 102 | def _on_event(event_info: NodeEventInfo, fhandle): 103 | """Event callback handler. 104 | 105 | """ 106 | if event_info.idx is not None: 107 | fhandle.write(",\n") 108 | 109 | fhandle.write(json.dumps({ 110 | "id": event_info.idx, 111 | "channel": event_info.channel.name, 112 | "payload": event_info.payload 113 | }, indent=4, sort_keys=True)) 114 | 115 | fhandle.flush() 116 | 117 | 118 | # Entry point. 119 | if __name__ == "__main__": 120 | asyncio.run(_main(_ARGS.parse_args())) 121 | -------------------------------------------------------------------------------- /how_tos/smart_contracts/how_to_query.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import asyncio 3 | import os 4 | import pathlib 5 | 6 | import pycspr 7 | from pycspr import NodeRpcClient as NodeClient 8 | from pycspr import NodeRpcConnectionInfo as NodeConnectionInfo 9 | from pycspr.types.crypto import PrivateKey 10 | from pycspr.types.crypto import PublicKey 11 | from pycspr.types.cl import CLV_Key 12 | 13 | 14 | # Path to CCTL assets. 15 | _PATH_TO_CCTL_ASSETS = pathlib.Path(os.getenv("CCTL")) / "assets" 16 | 17 | # CLI argument parser. 18 | _ARGS = argparse.ArgumentParser("Demo illustrating how to qeury an ERC-20 smart contract.") 19 | 20 | # CLI argument: path to contract operator public key - defaults to CCTL faucet. 21 | _ARGS.add_argument( 22 | "--operator-public-key-path", 23 | default=_PATH_TO_CCTL_ASSETS / "faucet" / "public_key_hex", 24 | dest="path_to_operator_public_key", 25 | help="Path to operator's public_key_hex file.", 26 | type=str, 27 | ) 28 | 29 | # CLI argument: name of target chain - defaults to CCTL chain. 30 | _ARGS.add_argument( 31 | "--chain", 32 | default="cspr-dev-cctl", 33 | dest="chain_name", 34 | help="Name of target chain.", 35 | type=str, 36 | ) 37 | 38 | # CLI argument: host address of target node - defaults to CCTL node 1. 39 | _ARGS.add_argument( 40 | "--node-host", 41 | default="localhost", 42 | dest="node_host", 43 | help="Host address of target node.", 44 | type=str, 45 | ) 46 | 47 | # CLI argument: Node API JSON-RPC port - defaults to 11101 @ CCTL node 1. 48 | _ARGS.add_argument( 49 | "--node-port-rpc", 50 | default=11101, 51 | dest="node_port_rpc", 52 | help="Node API JSON-RPC port. Typically 7777 on most nodes.", 53 | type=int, 54 | ) 55 | 56 | 57 | async def _main(args: argparse.Namespace): 58 | """Main entry point. 59 | 60 | :param args: Parsed command line arguments. 61 | 62 | """ 63 | print("-" * 74) 64 | print("PYCSPR :: How To Query For A Smart Contract Named Key") 65 | print("-" * 74) 66 | 67 | # Set node client. 68 | client: NodeClient = _get_client(args) 69 | 70 | # Set contract operator key. 71 | operator = _get_operator_key(args) 72 | 73 | # Set contract hash. 74 | contract_hash: CLV_Key = await _get_contract_hash(client, operator) 75 | 76 | # Issue queries. 77 | token_decimals = await _get_contract_data(client, contract_hash, "decimals") 78 | token_name = await _get_contract_data(client, contract_hash, "name") 79 | token_symbol = await _get_contract_data(client, contract_hash, "symbol") 80 | token_supply = await _get_contract_data(client, contract_hash, "total_supply") 81 | 82 | print("-" * 72) 83 | print(f"Token Decimals: {token_decimals}") 84 | print(f"Token Name: {token_name}") 85 | print(f"Token Symbol: {token_symbol}") 86 | print(f"Token Supply: {token_supply}") 87 | print("-" * 72) 88 | 89 | 90 | def _get_client(args: argparse.Namespace) -> NodeClient: 91 | """Returns a pycspr client instance. 92 | 93 | """ 94 | return NodeClient(NodeConnectionInfo(args.node_host, args.node_port_rpc)) 95 | 96 | 97 | async def _get_contract_data(client: NodeClient, contract_hash: CLV_Key, key: str) -> bytes: 98 | """Queries chain for data associated with a contract. 99 | 100 | """ 101 | state_key = f"hash-{contract_hash.identifier.hex()}" 102 | value = await client.get_state_item(state_key, key) 103 | 104 | return value["CLValue"]["parsed"] 105 | 106 | 107 | async def _get_contract_hash(client: NodeClient, operator: PrivateKey) -> CLV_Key: 108 | """Returns on-chain contract identifier. 109 | 110 | """ 111 | named_key = await client.get_account_named_key(operator.account_key, "ERC20") 112 | if named_key is None: 113 | raise ValueError("ERC-20 uninstalled ... see how_tos/how_to_install_a_contract.py") 114 | 115 | return named_key 116 | 117 | 118 | def _get_operator_key(args: argparse.Namespace) -> PublicKey: 119 | """Returns the smart contract operator's public key. 120 | 121 | """ 122 | return pycspr.parse_public_key( 123 | args.path_to_operator_public_key, 124 | ) 125 | 126 | 127 | # Entry point. 128 | if __name__ == "__main__": 129 | asyncio.run(_main(_ARGS.parse_args())) 130 | -------------------------------------------------------------------------------- /how_tos/smart_contracts/how_to_query_dictionary.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import asyncio 3 | 4 | from pycspr import NodeRpcClient as NodeClient 5 | from pycspr import NodeRpcConnectionInfo as NodeConnectionInfo 6 | from pycspr.types.node import DictionaryID_ContractNamedKey 7 | 8 | 9 | # CLI argument parser. 10 | _ARGS = argparse.ArgumentParser("Demo illustrating how to query a dictionary item.") 11 | 12 | 13 | # CLI argument: name of target chain - defaults to CCTL chain. 14 | _ARGS.add_argument( 15 | "--chain", 16 | default="casper-test", 17 | dest="chain_name", 18 | help="Name of target chain.", 19 | type=str, 20 | ) 21 | 22 | # CLI argument: host address of target node - defaults to CCTL node 1. 23 | _ARGS.add_argument( 24 | "--node-host", 25 | default="3.208.91.63", 26 | dest="node_host", 27 | help="Host address of target node.", 28 | type=str, 29 | ) 30 | 31 | # CLI argument: Node API JSON-RPC port - defaults to 11101 @ CCTL node 1. 32 | _ARGS.add_argument( 33 | "--node-port-rpc", 34 | default=7777, 35 | dest="node_port_rpc", 36 | help="Node API JSON-RPC port. Typically 7777 on most nodes.", 37 | type=int, 38 | ) 39 | 40 | # CLI argument: On-chain address of target smart contract. 41 | _ARGS.add_argument( 42 | "--contract", 43 | default="75143aa704675b7dead20ac2ee06c1c3eccff4ffcf1eb9aebb8cce7c35648041", 44 | dest="contract_hash", 45 | help="On-chain address of target smart contract.", 46 | type=str, 47 | ) 48 | 49 | # CLI argument: The dictionary item key. 50 | _ARGS.add_argument( 51 | "--dict-item-key", 52 | default="c95da35f6a727f7af69da9e7cc0f37e5edd9aee89f7b2f2b1507c5f68aa81859", 53 | dest="dictionary_item_key", 54 | help="The dictionary item key.", 55 | type=str, 56 | ) 57 | 58 | # CLI argument: # The named key under which the dictionary seed URef is stored.. 59 | _ARGS.add_argument( 60 | "--dict-name", 61 | default="highscore_dictionary", 62 | dest="dictionary_name", 63 | help="# The named key under which the dictionary seed URef is stored..", 64 | type=str, 65 | ) 66 | 67 | 68 | async def _main(args: argparse.Namespace): 69 | """Main entry point. 70 | 71 | :param args: Parsed command line arguments. 72 | 73 | """ 74 | print("-" * 74) 75 | print("PYCSPR :: How To Query For A Smart Contract's Named Keys") 76 | print("-" * 74) 77 | 78 | # Set node client. 79 | client: NodeClient = _get_client(args) 80 | 81 | # Set dictionary item identifier. 82 | dictionary_id = DictionaryID_ContractNamedKey( 83 | dictionary_name=args.dictionary_name, 84 | dictionary_item_key=args.dictionary_item_key, 85 | contract_key=args.contract_hash 86 | ) 87 | 88 | # Set node JSON-RPC query response. 89 | response = await client.get_dictionary_item(dictionary_id) 90 | 91 | print(response) 92 | 93 | 94 | def _get_client(args: argparse.Namespace) -> NodeClient: 95 | """Returns a pycspr client instance. 96 | 97 | """ 98 | return NodeClient(NodeConnectionInfo(args.node_host, args.node_port_rpc)) 99 | 100 | 101 | # Entry point. 102 | if __name__ == "__main__": 103 | asyncio.run(_main(_ARGS.parse_args())) 104 | -------------------------------------------------------------------------------- /pan.txt: -------------------------------------------------------------------------------- 1 | Came the voice of Destiny, 2 | Calling o’er the Ionian Sea, 3 | ‘The Great God Pan is dead, is dead. 4 | Humbled is the horned head; 5 | Shut the door that hath no key – 6 | Waste the vales of Arcady.’ 7 | Shackled by the Iron Age, 8 | Lost the woodland heritage, 9 | Heavy goes the heart of man, 10 | Parted from the light-foot Pan; 11 | Wearily he wears the chain 12 | Till the Goat-god comes again. 13 | Half a man and half a beast, 14 | Pan is greatest, Pan is least, 15 | Pan is all, and all is Pan; 16 | Look for him in every-man; 17 | Goat-hoof swift and shaggy thigh – 18 | Follow him to Arcady. 19 | He shall wake the living dead – 20 | Cloven hoof and hornèd head, 21 | Human heart and human brain, 22 | Pan the goat-god comes again! 23 | Half a beast and half a man – 24 | Pan is all, and all is Pan. 25 | Come, O Goat-god, come again! -------------------------------------------------------------------------------- /pycspr/__init__.py: -------------------------------------------------------------------------------- 1 | # 8b,dPPYba, 8b d8 ,adPPYba, ,adPPYba, 8b,dPPYba, 8b,dPPYba, 2 | # 88P' "8a `8b d8' a8" "" I8[ "" 88P' "8a 88P' "Y8 3 | # 88 d8 `8b d8' 8b `"Y8ba, 88 d8 88 4 | # 88b, ,a8" `8b,d8' "8a, ,aa aa ]8I 88b, ,a8" 88 5 | # 88`YbbdP"' Y88' `"Ybbd8"' `"YbbdP"' 88`YbbdP"' 88 6 | # 88 d8' 88 7 | # 88 d8' 88 8 | 9 | __title__ = "pycspr" 10 | __version__ = "1.2.1" 11 | __author__ = "Mark A. Greenslade et al" 12 | __license__ = "Apache 2.0" 13 | 14 | from pycspr import types 15 | from pycspr import crypto 16 | from pycspr import factory 17 | from pycspr import verifier 18 | 19 | from pycspr.api import NodeRestClient 20 | from pycspr.api import NodeRestConnectionInfo 21 | from pycspr.api import NodeRpcClient 22 | from pycspr.api import NodeRpcConnectionInfo 23 | from pycspr.api import NodeRpcProxyError 24 | from pycspr.api import NodeSpeculativeRpcClient 25 | from pycspr.api import NodeSpeculativeRpcConnectionInfo 26 | from pycspr.api import NodeSseClient 27 | from pycspr.api import NodeSseConnectionInfo 28 | from pycspr.api import NodeEventChannel 29 | from pycspr.api import NodeEventInfo 30 | from pycspr.api import NodeEventType 31 | from pycspr.api import SSE_CHANNEL_TO_SSE_EVENT 32 | 33 | from pycspr.crypto import DEFAULT_HASH_ALGO 34 | from pycspr.crypto import DEFAULT_KEY_ALGO 35 | from pycspr.crypto import checksummer 36 | from pycspr.crypto import get_account_hash 37 | from pycspr.crypto import get_account_key 38 | from pycspr.crypto import get_hash 39 | from pycspr.crypto import get_key_pair 40 | from pycspr.crypto import get_key_pair_from_base64 41 | from pycspr.crypto import get_key_pair_from_bytes 42 | from pycspr.crypto import get_key_pair_from_hex_string 43 | from pycspr.crypto import get_key_pair_from_pem_file 44 | from pycspr.crypto import get_key_pair_from_seed 45 | from pycspr.crypto import get_pvk_pem_file_from_bytes 46 | from pycspr.crypto import get_pvk_pem_from_bytes 47 | from pycspr.crypto import get_signature 48 | from pycspr.crypto import get_signature_for_deploy_approval 49 | from pycspr.crypto import get_signature_from_pem_file 50 | from pycspr.crypto import is_signature_valid 51 | from pycspr.crypto import verify_deploy_approval_signature 52 | 53 | from pycspr.factory import create_deploy 54 | from pycspr.factory import create_deploy_approval 55 | from pycspr.factory import create_deploy_arguments 56 | from pycspr.factory import create_deploy_parameters 57 | from pycspr.factory import create_deploy_ttl 58 | from pycspr.factory import create_digest_of_block 59 | from pycspr.factory import create_digest_of_block_for_finality_signature 60 | from pycspr.factory import create_digest_of_deploy 61 | from pycspr.factory import create_digest_of_deploy_body 62 | from pycspr.factory import create_transfer 63 | from pycspr.factory import create_standard_payment 64 | from pycspr.factory import create_validator_auction_bid 65 | from pycspr.factory import create_validator_auction_bid_withdrawal 66 | from pycspr.factory import create_validator_delegation 67 | from pycspr.factory import create_validator_delegation_withdrawal 68 | from pycspr.factory import parse_private_key 69 | from pycspr.factory import parse_private_key_bytes 70 | from pycspr.factory import parse_public_key 71 | from pycspr.factory import parse_public_key_bytes 72 | 73 | from pycspr.serializer import to_bytes 74 | from pycspr.serializer import to_json 75 | from pycspr.serializer import from_bytes 76 | from pycspr.serializer import from_json 77 | 78 | from pycspr.types.crypto import HashAlgorithm 79 | from pycspr.types.crypto import KeyAlgorithm 80 | from pycspr.types.crypto import PublicKey 81 | from pycspr.types.crypto import PrivateKey 82 | 83 | from pycspr.utils import convertor 84 | 85 | from pycspr.utils.io import get_deploy_size_bytes 86 | from pycspr.utils.io import read_block 87 | from pycspr.utils.io import read_deploy 88 | from pycspr.utils.io import read_wasm 89 | from pycspr.utils.io import write_deploy 90 | 91 | from pycspr.utils.validation import validate_block 92 | from pycspr.utils.validation import validate_block_at_era_end 93 | from pycspr.utils.validation import validate_deploy 94 | from pycspr.utils.validation import InvalidBlockException 95 | from pycspr.utils.validation import InvalidDeployException 96 | -------------------------------------------------------------------------------- /pycspr/api/__init__.py: -------------------------------------------------------------------------------- 1 | from pycspr.api.rest import Client as NodeRestClient 2 | from pycspr.api.rest import ConnectionInfo as NodeRestConnectionInfo 3 | from pycspr.api.rpc import Client as NodeRpcClient 4 | from pycspr.api.rpc import ConnectionInfo as NodeRpcConnectionInfo 5 | from pycspr.api.rpc import ProxyError as NodeRpcProxyError 6 | from pycspr.api.rpc_speculative import Client as NodeSpeculativeRpcClient 7 | from pycspr.api.rpc_speculative import ConnectionInfo as NodeSpeculativeRpcConnectionInfo 8 | from pycspr.api.sse import Client as NodeSseClient 9 | from pycspr.api.sse import ConnectionInfo as NodeSseConnectionInfo 10 | from pycspr.types.node import NodeEventChannel 11 | from pycspr.types.node import NodeEventInfo 12 | from pycspr.types.node import NodeEventType 13 | from pycspr.types.node import SSE_CHANNEL_TO_SSE_EVENT 14 | -------------------------------------------------------------------------------- /pycspr/api/constants.py: -------------------------------------------------------------------------------- 1 | # Default node host. 2 | DEFAULT_HOST = "localhost" 3 | 4 | # Default node ports. 5 | DEFAULT_PORT_REST = 8888 6 | DEFAULT_PORT_RPC = 7777 7 | DEFAULT_PORT_SPECULATIVE_RPC = 7778 8 | DEFAULT_PORT_SSE = 9999 9 | 10 | # Node RPC endpoints. 11 | RPC_ACCOUNT_PUT_DEPLOY = "account_put_deploy" 12 | RPC_CHAIN_GET_BLOCK = "chain_get_block" 13 | RPC_CHAIN_GET_BLOCK_TRANSFERS = "chain_get_block_transfers" 14 | RPC_CHAIN_GET_ERA_INFO_BY_SWITCH_BLOCK = "chain_get_era_info_by_switch_block" 15 | RPC_CHAIN_GET_ERA_SUMMARY = "chain_get_era_summary" 16 | RPC_CHAIN_GET_STATE_ROOT_HASH = "chain_get_state_root_hash" 17 | RPC_DISCOVER = "rpc.discover" 18 | RPC_INFO_GET_CHAINSPEC = "info_get_chainspec" 19 | RPC_INFO_GET_DEPLOY = "info_get_deploy" 20 | RPC_INFO_GET_PEERS = "info_get_peers" 21 | RPC_INFO_GET_STATUS = "info_get_status" 22 | RPC_INFO_GET_VALIDATOR_CHANGES = "info_get_validator_changes" 23 | RPC_QUERY_BALANCE = "query_balance" 24 | RPC_QUERY_GLOBAL_STATE = "query_global_state" 25 | RPC_STATE_GET_ACCOUNT_INFO = "state_get_account_info" 26 | RPC_STATE_GET_AUCTION_INFO = "state_get_auction_info" 27 | RPC_STATE_GET_BALANCE = "state_get_balance" 28 | RPC_STATE_GET_DICTIONARY_ITEM = "state_get_dictionary_item" 29 | RPC_STATE_GET_ITEM = "state_get_item" 30 | RPC_STATE_GET_TRIE = "state_get_trie" 31 | 32 | RPC_ENDPOINTS: set = { 33 | RPC_ACCOUNT_PUT_DEPLOY, 34 | RPC_CHAIN_GET_BLOCK, 35 | RPC_CHAIN_GET_BLOCK_TRANSFERS, 36 | RPC_CHAIN_GET_ERA_INFO_BY_SWITCH_BLOCK, 37 | RPC_CHAIN_GET_ERA_SUMMARY, 38 | RPC_CHAIN_GET_STATE_ROOT_HASH, 39 | RPC_INFO_GET_CHAINSPEC, 40 | RPC_INFO_GET_DEPLOY, 41 | RPC_INFO_GET_PEERS, 42 | RPC_INFO_GET_STATUS, 43 | RPC_INFO_GET_VALIDATOR_CHANGES, 44 | RPC_QUERY_BALANCE, 45 | RPC_QUERY_GLOBAL_STATE, 46 | RPC_STATE_GET_ACCOUNT_INFO, 47 | RPC_STATE_GET_AUCTION_INFO, 48 | RPC_STATE_GET_BALANCE, 49 | RPC_STATE_GET_DICTIONARY_ITEM, 50 | RPC_STATE_GET_ITEM, 51 | } 52 | 53 | # Node REST endpoints. 54 | REST_GET_CHAINSPEC = "chainspec" 55 | REST_GET_METRICS = "metrics" 56 | REST_GET_RPC_SCHEMA = "rpc-schema" 57 | REST_GET_STATUS = "status" 58 | REST_GET_VALIDATOR_CHANGES = "validator-changes" 59 | 60 | # Node RPC endpoints. 61 | SPECULATIVE_RPC_EXEC_DEPLOY = "speculative_exec" 62 | -------------------------------------------------------------------------------- /pycspr/api/rest/__init__.py: -------------------------------------------------------------------------------- 1 | from pycspr.api.rest.client import Client 2 | from pycspr.api.rest.connection import ConnectionInfo 3 | -------------------------------------------------------------------------------- /pycspr/api/rest/client.py: -------------------------------------------------------------------------------- 1 | import typing 2 | 3 | from pycspr import serializer 4 | from pycspr.api.rest.connection import ConnectionInfo 5 | from pycspr.api.rest.proxy import Proxy 6 | from pycspr.types.node import NodeStatus 7 | from pycspr.types.node import ValidatorChanges 8 | 9 | 10 | class Client(): 11 | """Node REST server client. 12 | 13 | """ 14 | def __init__(self, connection_info: ConnectionInfo): 15 | """Instance constructor. 16 | 17 | :param connection_info: Information required to connect to a node. 18 | 19 | """ 20 | self.proxy = Proxy(connection_info) 21 | 22 | # Extension methods -> 2nd order functions. 23 | ext = ClientExtensions(self) 24 | self.get_node_metric = ext.get_node_metric 25 | 26 | async def get_chainspec(self) -> dict: 27 | """Returns network chainspec. 28 | 29 | :returns: Network chainspec. 30 | 31 | """ 32 | return await self.proxy.get_chainspec() 33 | 34 | async def get_node_metrics(self) -> list: 35 | """Returns set of node metrics. 36 | 37 | :returns: Node metrics information. 38 | 39 | """ 40 | return await self.proxy.get_node_metrics() 41 | 42 | async def get_node_status(self, decode: bool = True) -> typing.Union[dict, NodeStatus]: 43 | """Returns node status information. 44 | 45 | :param decode: Flag indicating whether to decode API response. 46 | :returns: Node status information. 47 | 48 | """ 49 | encoded: dict = await self.proxy.get_node_status() 50 | 51 | return encoded if decode is False else serializer.from_json(NodeStatus, encoded) 52 | 53 | async def get_node_rpc_schema(self) -> dict: 54 | """Returns node RPC API schema. 55 | 56 | :returns: Node RPC API schema. 57 | 58 | """ 59 | return await self.proxy.get_rpc_schema() 60 | 61 | async def get_validator_changes(self, decode: bool = True) -> list: 62 | """Returns validator change information. 63 | 64 | :returns: Validator change information. 65 | 66 | """ 67 | encoded: dict = await self.proxy.get_validator_changes() 68 | 69 | return \ 70 | encoded if decode is False else \ 71 | [serializer.from_json(ValidatorChanges, i) for i in encoded] 72 | 73 | 74 | class ClientExtensions(): 75 | """Node REST server client extensions, i.e. 2nd order functions. 76 | 77 | """ 78 | def __init__(self, client: Client): 79 | """Instance constructor. 80 | 81 | :param client: Node REST client. 82 | 83 | """ 84 | self.client = client 85 | 86 | async def get_node_metric(self, metric_id: str) -> list: 87 | """Returns node metrics information filtered by a particular metric. 88 | 89 | :param metric_id: Identifier of node metric. 90 | :returns: Node metrics information filtered by a particular metric. 91 | 92 | """ 93 | metrics: list = await self.client.get_node_metrics() 94 | 95 | return [i for i in metrics if i.lower().startswith(metric_id.lower())] 96 | -------------------------------------------------------------------------------- /pycspr/api/rest/connection.py: -------------------------------------------------------------------------------- 1 | import dataclasses 2 | 3 | from pycspr.api import constants 4 | 5 | 6 | @dataclasses.dataclass 7 | class ConnectionInfo: 8 | """Encapsulates information required to connect to a node's REST API. 9 | 10 | """ 11 | # Host address. 12 | host: str = constants.DEFAULT_HOST 13 | 14 | # Number of exposed REST port. 15 | port: int = constants.DEFAULT_PORT_REST 16 | -------------------------------------------------------------------------------- /pycspr/api/rest/proxy.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | import requests 4 | 5 | from pycspr.api import constants 6 | from pycspr.api.rest.connection import ConnectionInfo 7 | 8 | 9 | class Proxy: 10 | """Node REST server proxy. 11 | 12 | """ 13 | def __init__(self, connection_info: ConnectionInfo): 14 | """Instance constructor. 15 | 16 | :param connection_info: Information required to connect to a node. 17 | 18 | """ 19 | self.connection_info = connection_info 20 | 21 | @property 22 | def address(self) -> str: 23 | """A node's REST server base address.""" 24 | return f"http://{self.connection_info.host}:{self.connection_info.port}" 25 | 26 | def __str__(self): 27 | """Instance string representation.""" 28 | return self.address 29 | 30 | async def get_chainspec(self) -> dict: 31 | """Returns network chainspec. 32 | 33 | :returns: Network chainspec. 34 | 35 | """ 36 | response: str = await self._get_response(constants.REST_GET_CHAINSPEC) 37 | 38 | return json.loads(response)["chainspec_bytes"] 39 | 40 | async def get_node_metrics(self) -> list: 41 | """Returns set of node metrics. 42 | 43 | :returns: Node metrics information. 44 | 45 | """ 46 | response = await self._get_response(constants.REST_GET_METRICS) 47 | 48 | return sorted([i.strip() for i in response.split("\n") if not i.startswith("#")]) 49 | 50 | async def get_rpc_schema(self) -> dict: 51 | """Returns node RPC API schema. 52 | 53 | :returns: Node RPC API schema. 54 | 55 | """ 56 | response: str = await self._get_response(constants.REST_GET_RPC_SCHEMA) 57 | 58 | return json.loads(response) 59 | 60 | async def get_node_status(self) -> dict: 61 | """Returns node status information. 62 | 63 | :returns: Node status information. 64 | 65 | """ 66 | return json.loads(await self._get_response(constants.REST_GET_STATUS)) 67 | 68 | async def get_validator_changes(self) -> list: 69 | """Returns validator change information. 70 | 71 | :returns: Validator change information. 72 | 73 | """ 74 | response = await self._get_response(constants.REST_GET_VALIDATOR_CHANGES) 75 | 76 | return json.loads(response)["changes"] 77 | 78 | async def _get_response(self, endpoint: str) -> dict: 79 | """Invokes remote REST API and returns parsed response. 80 | 81 | :endpoint: Target endpoint to invoke. 82 | :returns: Parsed REST API response. 83 | 84 | """ 85 | return requests.get(f"{self.address}/{endpoint}").content.decode("utf-8") 86 | -------------------------------------------------------------------------------- /pycspr/api/rpc/__init__.py: -------------------------------------------------------------------------------- 1 | from pycspr.api.rpc.client import Client 2 | from pycspr.api.rpc.connection import ConnectionInfo 3 | from pycspr.api.rpc.proxy import ProxyError 4 | -------------------------------------------------------------------------------- /pycspr/api/rpc/connection.py: -------------------------------------------------------------------------------- 1 | import dataclasses 2 | 3 | from pycspr.api import constants 4 | 5 | 6 | @dataclasses.dataclass 7 | class ConnectionInfo: 8 | """Encapsulates information required to connect to a node's JSON-RPC API. 9 | 10 | """ 11 | # Host address. 12 | host: str = constants.DEFAULT_HOST 13 | 14 | # Number of exposed RPC port. 15 | port: int = constants.DEFAULT_PORT_RPC 16 | -------------------------------------------------------------------------------- /pycspr/api/rpc_speculative/__init__.py: -------------------------------------------------------------------------------- 1 | from pycspr.api.rpc_speculative.client import Client 2 | from pycspr.api.rpc_speculative.connection import ConnectionInfo 3 | -------------------------------------------------------------------------------- /pycspr/api/rpc_speculative/client.py: -------------------------------------------------------------------------------- 1 | from pycspr.api.rpc_speculative.connection import ConnectionInfo 2 | from pycspr.api.rpc_speculative.proxy import Proxy 3 | from pycspr.types.node import Deploy 4 | from pycspr.types.node import BlockID 5 | 6 | 7 | class Client(): 8 | """Node speculative RPC server client. 9 | 10 | """ 11 | def __init__(self, connection_info: ConnectionInfo) -> dict: 12 | """Instance constructor. 13 | 14 | :param connection_info: Information required to connect to a node. 15 | 16 | """ 17 | self.proxy = Proxy(connection_info) 18 | 19 | async def speculative_exec(self, deploy: Deploy, block_id: BlockID = None) -> dict: 20 | """Dispatches a deploy to a node for speculative execution. 21 | 22 | :param deploy: A deploy to be processed at a node. 23 | :param block_id: Identifier of a finalised block. 24 | :returns: Execution effects of virtual deploy processing. 25 | 26 | """ 27 | return await self.proxy.speculative_exec(deploy, block_id) 28 | -------------------------------------------------------------------------------- /pycspr/api/rpc_speculative/connection.py: -------------------------------------------------------------------------------- 1 | import dataclasses 2 | 3 | from pycspr.api import constants 4 | 5 | 6 | @dataclasses.dataclass 7 | class ConnectionInfo: 8 | """Encapsulates information required to connect to a node's speculative JSON-RPC API. 9 | 10 | """ 11 | # Host address. 12 | host: str = constants.DEFAULT_HOST 13 | 14 | # Number of exposed speculative RPC port. 15 | port: int = constants.DEFAULT_PORT_SPECULATIVE_RPC 16 | -------------------------------------------------------------------------------- /pycspr/api/rpc_speculative/proxy.py: -------------------------------------------------------------------------------- 1 | from pycspr import serializer 2 | from pycspr.api import constants 3 | from pycspr.api.rpc import params as param_utils 4 | from pycspr.api.rpc.proxy import get_response 5 | from pycspr.api.rpc_speculative.connection import ConnectionInfo 6 | from pycspr.types.node import Deploy 7 | from pycspr.types.node import BlockID 8 | 9 | 10 | class Proxy: 11 | """Node speculative JSON-RPC server proxy. 12 | 13 | """ 14 | def __init__(self, connection_info: ConnectionInfo): 15 | """Instance constructor. 16 | 17 | :param connection_info: Information required to connect to a node. 18 | 19 | """ 20 | self.connection_info = connection_info 21 | 22 | @property 23 | def address(self) -> str: 24 | """A node's speculative RPC server base address.""" 25 | return f"http://{self.connection_info.host}:{self.connection_info.port}/rpc" 26 | 27 | def __str__(self): 28 | """Instance string representation.""" 29 | return self.address 30 | 31 | async def speculative_exec(self, deploy: Deploy, block_id: BlockID = None) -> dict: 32 | """Dispatches a deploy to a node for speculative execution. 33 | 34 | :param deploy: A deploy to be processed at a node. 35 | :param block_id: Identifier of a finalised block. 36 | :returns: Execution effects of virtual deploy processing. 37 | 38 | """ 39 | params: dict = param_utils.block_id(block_id) | { 40 | "deploy": serializer.to_json(deploy) 41 | } 42 | 43 | return await get_response( 44 | self.address, 45 | constants.SPECULATIVE_RPC_EXEC_DEPLOY, 46 | params, 47 | "execution_result" 48 | ) 49 | -------------------------------------------------------------------------------- /pycspr/api/sse/__init__.py: -------------------------------------------------------------------------------- 1 | from pycspr.api.sse.client import Client 2 | from pycspr.api.sse.connection import ConnectionInfo 3 | -------------------------------------------------------------------------------- /pycspr/api/sse/connection.py: -------------------------------------------------------------------------------- 1 | import dataclasses 2 | 3 | from pycspr.api import constants 4 | 5 | 6 | @dataclasses.dataclass 7 | class ConnectionInfo: 8 | """Encapsulates information required to connect to a node's SSE API. 9 | 10 | """ 11 | # Host address. 12 | host: str = constants.DEFAULT_HOST 13 | 14 | # Number of exposed speculative SSE port. 15 | port: int = constants.DEFAULT_PORT_SSE 16 | 17 | # Number of exposed JSON-RPC port. 18 | port_rpc: int = constants.DEFAULT_PORT_RPC 19 | -------------------------------------------------------------------------------- /pycspr/api/sse/proxy.py: -------------------------------------------------------------------------------- 1 | import json 2 | import typing 3 | 4 | import requests 5 | import sseclient 6 | 7 | from pycspr.api.sse.connection import ConnectionInfo 8 | from pycspr.types.node import NodeEventChannel 9 | from pycspr.types.node import NodeEventInfo 10 | from pycspr.types.node import NodeEventType 11 | 12 | 13 | class Proxy: 14 | """Node SSE server proxy. 15 | 16 | """ 17 | def __init__(self, connection_info: ConnectionInfo): 18 | """Instance constructor. 19 | 20 | :param connection_info: Information required to connect to a node. 21 | 22 | """ 23 | self.connection_info = connection_info 24 | 25 | @property 26 | def address(self) -> str: 27 | """A node's REST server base address.""" 28 | return f"http://{self.connection_info.host}:{self.connection_info.port}/events" 29 | 30 | def __str__(self): 31 | """Instance string representation.""" 32 | return self.address 33 | 34 | def yield_events( 35 | self, 36 | echannel: NodeEventChannel, 37 | etype: NodeEventType = None, 38 | eid: int = 0 39 | ) -> typing.Generator[NodeEventInfo, None, None]: 40 | """Returns generator yielding (filterable) events emitted by a node's event stream. 41 | 42 | :param echannel: Type of event channel to which to bind. 43 | :param etype: Type of event type to listen for (all if unspecified). 44 | :param eid: Identifier of event from which to start stream listening. 45 | 46 | """ 47 | # Set client. 48 | url = f"{self.address}/{echannel.name.lower()}" 49 | if eid: 50 | url = f"{url}?start_from={eid}" 51 | sse_client = sseclient.SSEClient(requests.get(url, stream=True)) 52 | 53 | # Open connection & iterate event stream. 54 | try: 55 | for event in sse_client.events(): 56 | # Set event data. 57 | try: 58 | edata = json.loads(event.data) 59 | except json.JSONDecodeError: 60 | edata = event.data 61 | 62 | # Set event type. 63 | if isinstance(edata, str): 64 | etype_in = NodeEventType.Shutdown 65 | else: 66 | for etype_in in NodeEventType: 67 | if etype_in.name in edata: 68 | break 69 | else: 70 | raise ValueError(f"Unknown event type: {edata}") 71 | 72 | # If event type is in scope then yield event information. 73 | if etype is None or etype == etype_in: 74 | yield NodeEventInfo(echannel, etype_in, event.id, edata) 75 | 76 | # On error ensure that connection is closed. 77 | except Exception as err: 78 | try: 79 | sse_client.close() 80 | except Exception as inner_err: 81 | print(f"Ignoring error raised on closing SSE connection: {inner_err}.") 82 | finally: 83 | raise err 84 | -------------------------------------------------------------------------------- /pycspr/crypto/__init__.py: -------------------------------------------------------------------------------- 1 | from pycspr.crypto import checksummer 2 | from pycspr.crypto.cl_operations import get_account_hash 3 | from pycspr.crypto.cl_operations import get_account_key 4 | from pycspr.crypto.cl_operations import get_signature_for_deploy_approval 5 | from pycspr.crypto.cl_operations import verify_deploy_approval_signature 6 | from pycspr.crypto.ecc import get_key_pair 7 | from pycspr.crypto.ecc import get_key_pair_from_base64 8 | from pycspr.crypto.ecc import get_key_pair_from_bytes 9 | from pycspr.crypto.ecc import get_key_pair_from_hex_string 10 | from pycspr.crypto.ecc import get_key_pair_from_pem_file 11 | from pycspr.crypto.ecc import get_key_pair_from_seed 12 | from pycspr.crypto.ecc import get_pvk_pem_file_from_bytes 13 | from pycspr.crypto.ecc import get_pvk_pem_from_bytes 14 | from pycspr.crypto.ecc import get_signature 15 | from pycspr.crypto.ecc import get_signature_from_pem_file 16 | from pycspr.crypto.ecc import is_signature_valid 17 | from pycspr.crypto.ecc import DEFAULT_KEY_ALGO 18 | from pycspr.crypto.hashifier import get_hash 19 | from pycspr.crypto.hashifier import DEFAULT_HASH_ALGO 20 | -------------------------------------------------------------------------------- /pycspr/crypto/cl_operations.py: -------------------------------------------------------------------------------- 1 | from pycspr.crypto.ecc import get_signature 2 | from pycspr.crypto.ecc import is_signature_valid 3 | from pycspr.crypto.hashifier import get_hash 4 | from pycspr.types.crypto import DigestBytes 5 | from pycspr.types.crypto import HashAlgorithm 6 | from pycspr.types.crypto import KeyAlgorithm 7 | from pycspr.types.crypto import PublicKey 8 | from pycspr.types.crypto import PrivateKey 9 | from pycspr.types.crypto import Signature 10 | 11 | 12 | # Desired length of hash digest. 13 | _DIGEST_LENGTH = 32 14 | 15 | # Map: Key algorithm <-> length of public key. 16 | _PUBLIC_KEY_LENGTHS = { 17 | KeyAlgorithm.ED25519: 32, 18 | KeyAlgorithm.SECP256K1: 33, 19 | } 20 | 21 | 22 | def get_account_hash(account_key: bytes) -> bytes: 23 | """Returns an on-chain account identifier (hex format) as derived from an account key. 24 | 25 | :param account_key: An on-chain account identifier. 26 | 27 | :returns: An on-chain account identifier. 28 | 29 | """ 30 | key_algo: KeyAlgorithm = KeyAlgorithm(account_key[0]) 31 | public_key: bytes = account_key[1:] 32 | as_bytes: bytes = \ 33 | bytes(key_algo.name.lower(), "utf-8") + \ 34 | bytearray(1) + \ 35 | public_key 36 | 37 | return get_hash(as_bytes, _DIGEST_LENGTH, HashAlgorithm.BLAKE2B) 38 | 39 | 40 | def get_account_key(algo: KeyAlgorithm, pbk: bytes) -> bytes: 41 | """Returns an on-chain account key. 42 | 43 | :param algo: Algorithm used to generate ECC public key. 44 | :param pbk: A byte array representation of an ECC public (aka verifying) key. 45 | :returns: An on-chain account key. 46 | 47 | """ 48 | assert len(pbk) == _PUBLIC_KEY_LENGTHS[algo], \ 49 | f"Invalid {algo.name} public key length." 50 | 51 | return bytes([algo.value]) + pbk 52 | 53 | 54 | def get_signature_for_deploy_approval(digest: DigestBytes, approver: PrivateKey) -> Signature: 55 | """Returns a signature designated to approve a deploy. 56 | 57 | :param digest: Digest of a deploy to be signed. 58 | :param key_of_approver: Private key of approver. 59 | :returns: Digital signature over deploy digest. 60 | 61 | """ 62 | return Signature( 63 | approver.algo, 64 | get_signature(digest, approver.algo, approver.pvk) 65 | ) 66 | 67 | 68 | def verify_deploy_approval_signature( 69 | deploy_hash: DigestBytes, 70 | signature: Signature, 71 | account_public_key: PublicKey, 72 | ) -> bool: 73 | """Returns a flag indicating whether a deploy signature was signed by private key 74 | associated with the passed account key. 75 | 76 | :param deploy_hash: Hash of a deploy that has been signed. 77 | :param sig: A digital signature. 78 | :param account_key: An account key associated with an ECC algorithm. 79 | :returns: A flag indicating whether a deploy signature was signed by private key. 80 | 81 | """ 82 | assert len(deploy_hash) == 32, \ 83 | "Invalid deploy hash. Expected length = 32" 84 | assert len(signature.to_bytes()) == 65, \ 85 | "Invalid deploy approval signature. Expected length = 65" 86 | 87 | return is_signature_valid( 88 | deploy_hash, 89 | signature.algo, 90 | signature.sig, 91 | account_public_key.pbk, 92 | ) 93 | -------------------------------------------------------------------------------- /pycspr/crypto/ecc_ed25519.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import typing 3 | 4 | from cryptography.exceptions import InvalidSignature 5 | from cryptography.hazmat.primitives import serialization 6 | from cryptography.hazmat.primitives.asymmetric import ed25519 7 | 8 | 9 | # Length of ED25519 private key in bytes. 10 | _PVK_LENGTH = 32 11 | 12 | 13 | def get_key_pair(seed: bytes = None) -> typing.Tuple[bytes, bytes]: 14 | """Returns an ED25519 key pair, each key is a 32 byte array. 15 | 16 | :param seed: A seed used as input to deterministic key pair generation. 17 | :returns : 2 member tuple: (private key, public key) 18 | 19 | """ 20 | if seed is None: 21 | sk = ed25519.Ed25519PrivateKey.generate() 22 | else: 23 | sk = ed25519.Ed25519PrivateKey.from_private_bytes(seed) 24 | 25 | return _get_key_pair(sk) 26 | 27 | 28 | def get_key_pair_from_pem_file(fpath: str) -> typing.Tuple[bytes, bytes]: 29 | """Returns an ED25519 key pair mapped from a PEM file representation of a private key. 30 | 31 | :param fpath: PEM file path. 32 | :returns : 2 member tuple: (private key, public key) 33 | 34 | """ 35 | pvk = get_pvk_from_pem_file(fpath) 36 | 37 | return get_key_pair(pvk) 38 | 39 | 40 | def get_pvk_pem_from_bytes(pvk: bytes) -> bytes: 41 | """Returns ED25519 private key (pem) from bytes. 42 | 43 | :param pvk: A private key derived from a generated key pair. 44 | :returns: PEM represenation of signing key. 45 | 46 | """ 47 | private_key = ed25519.Ed25519PrivateKey.from_private_bytes(pvk) 48 | 49 | return private_key.private_bytes( 50 | encoding=serialization.Encoding.PEM, 51 | format=serialization.PrivateFormat.PKCS8, 52 | encryption_algorithm=serialization.NoEncryption() 53 | ) 54 | 55 | 56 | def get_pvk_from_pem_file(fpath: str) -> bytes: 57 | """Returns an ED25519 private key decoded from a PEM file. 58 | 59 | :param fpath: Path to a PEM file. 60 | :returns: A private key. 61 | 62 | """ 63 | # Open pem file. 64 | with open(fpath, "r") as fstream: 65 | as_pem = fstream.readlines() 66 | 67 | # Decode bytes. 68 | pvk_b64 = [i for i in as_pem if i and not i.startswith("-----")][0].strip() 69 | pvk = base64.b64decode(pvk_b64) 70 | 71 | return len(pvk) % _PVK_LENGTH == 0 and pvk[:_PVK_LENGTH] or pvk[-_PVK_LENGTH:] 72 | 73 | 74 | def get_signature(msg: bytes, pvk: bytes) -> bytes: 75 | """Returns an ED25519 digital signature of data signed from a private key PEM file. 76 | 77 | :param msg: A bunch of bytes to be signed. 78 | :param pvk: A private key derived from a generated key pair. 79 | :returns: A digital signature. 80 | 81 | """ 82 | sk = ed25519.Ed25519PrivateKey.from_private_bytes(pvk) 83 | 84 | return sk.sign(msg) 85 | 86 | 87 | def get_signature_from_pem_file(msg: bytes, fpath: str) -> bytes: 88 | """Returns an ED25519 digital signature of data signed from a private key PEM file. 89 | 90 | :param msg: A bunch of bytes to be signed. 91 | :param fpath: PEM file path. 92 | :returns: A digital signature. 93 | 94 | """ 95 | return get_signature(msg, get_pvk_from_pem_file(fpath)) 96 | 97 | 98 | def is_signature_valid(msg_hash: bytes, sig: bytes, pbk: bytes) -> bool: 99 | """Returns a flag indicating whether a signature was signed by a signing key. 100 | 101 | :param msg_hash: Previously signed message hash. 102 | :param sig: A digital signature. 103 | :param pbk: Public verifying key. 104 | :returns: A flag indicating whether a signature was signed by a signing key. 105 | 106 | """ 107 | vk = ed25519.Ed25519PublicKey.from_public_bytes(pbk) 108 | try: 109 | vk.verify(sig, msg_hash) 110 | except InvalidSignature: 111 | return False 112 | else: 113 | return True 114 | 115 | 116 | def _get_key_pair(sk: ed25519.Ed25519PrivateKey) -> typing.Tuple[bytes, bytes]: 117 | """Returns key pair from a signing key. 118 | 119 | """ 120 | pk = sk.public_key() 121 | 122 | return \ 123 | sk.private_bytes( 124 | encoding=serialization.Encoding.Raw, 125 | format=serialization.PrivateFormat.Raw, 126 | encryption_algorithm=serialization.NoEncryption() 127 | ), \ 128 | pk.public_bytes( 129 | encoding=serialization.Encoding.Raw, 130 | format=serialization.PublicFormat.Raw 131 | ) 132 | -------------------------------------------------------------------------------- /pycspr/crypto/ecc_secp256k1.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | import typing 3 | 4 | import ecdsa 5 | 6 | 7 | # Default ECC + associated Casper specific hashing function. 8 | _CURVE = ecdsa.SECP256k1 9 | _HASH_FN = hashlib.sha256 10 | 11 | 12 | def get_key_pair(seed: bytes = None) -> typing.Tuple[bytes, bytes]: 13 | """Returns an SECP256K1 key pair, each key is a 32 byte array. 14 | 15 | :param seed: Entropy source to be used when geenrating key pair. 16 | :returns : 2 member tuple: (private key, public key) 17 | 18 | """ 19 | if seed is None: 20 | sk = ecdsa.SigningKey.generate(curve=_CURVE) 21 | else: 22 | sk = ecdsa.SigningKey.from_string(seed, curve=_CURVE) 23 | 24 | return _get_key_pair(sk) 25 | 26 | 27 | def get_key_pair_from_pem_file(fpath: str) -> typing.Tuple[bytes, bytes]: 28 | """Returns an SECP256K1 key pair mapped from a PEM file representation of a private key. 29 | 30 | :param fpath: PEM file path. 31 | :returns : 2 member tuple: (private key, public key) 32 | 33 | """ 34 | sk = _get_signing_key_from_pem_file(fpath) 35 | 36 | return _get_key_pair(sk) 37 | 38 | 39 | def get_pvk_pem_from_bytes(pvk: bytes) -> bytes: 40 | """Returns SECP256K1 private key (pem) from bytes. 41 | 42 | :param pvk: A private key derived from a generated key pair. 43 | :returns: PEM represenation of signing key. 44 | 45 | """ 46 | sk = ecdsa.SigningKey.from_string(pvk, curve=_CURVE) 47 | 48 | return sk.to_pem() 49 | 50 | 51 | def get_signature(msg: bytes, pvk: bytes) -> bytes: 52 | """Returns an SECP256K1 digital signature of data signed from a private key PEM file. 53 | 54 | :param msg: A bunch of bytes to be signed. 55 | :param pvk: A private key derived from a generated key pair. 56 | :returns: A digital signature. 57 | 58 | """ 59 | sk = ecdsa.SigningKey.from_string(pvk, curve=_CURVE) 60 | 61 | return sk.sign_deterministic(msg, hashfunc=_HASH_FN) 62 | 63 | 64 | def get_signature_from_pem_file(msg: bytes, fpath: str) -> bytes: 65 | """Returns an SECP256K1 digital signature of data signed from a private key PEM file. 66 | 67 | :param msg: A bunch of bytes to be signed. 68 | :param fpath: PEM file path. 69 | :returns: A digital signature. 70 | 71 | """ 72 | sk = _get_signing_key_from_pem_file(fpath) 73 | 74 | return sk.sign_deterministic(msg, hashfunc=_HASH_FN) 75 | 76 | 77 | def is_signature_valid(msg: bytes, sig: bytes, pbk: bytes) -> bool: 78 | """Returns a flag indicating whether a signature was signed by a signing key 79 | associated with passed verification key. 80 | 81 | :param msg: A message that has apparently been signed. 82 | :param sig: A digital signature. 83 | :param pbk: Public key counterpart to generated private key. 84 | :returns: A flag indicating whether a signature was indeed signed by the signing key. 85 | 86 | """ 87 | vk = ecdsa.VerifyingKey.from_string(pbk, curve=_CURVE) 88 | 89 | return vk.verify(sig, msg, hashfunc=_HASH_FN) 90 | 91 | 92 | def _get_key_pair(sk: ecdsa.SigningKey) -> typing.Tuple[bytes, bytes]: 93 | """Returns key pair from a signing key. 94 | 95 | """ 96 | return sk.to_string(), sk.verifying_key.to_string("compressed") 97 | 98 | 99 | def _get_signing_key_from_pem_file(fpath: str) -> ecdsa.SigningKey: 100 | """Returns a signing key mapped from a PEM file representation of a private key. 101 | 102 | """ 103 | with open(fpath, "rb") as fstream: 104 | return ecdsa.SigningKey.from_pem(fstream.read()) 105 | -------------------------------------------------------------------------------- /pycspr/crypto/hashifier.py: -------------------------------------------------------------------------------- 1 | from pycspr.crypto.hashifier_blake2b import get_hash as blake2b 2 | from pycspr.crypto.hashifier_blake3 import get_hash as blake3 3 | from pycspr.types.crypto import DigestBytes 4 | from pycspr.types.crypto import HashAlgorithm 5 | 6 | 7 | # Map: Hash Algo Type -> Hash Algo Implementation. 8 | ALGOS = { 9 | HashAlgorithm.BLAKE2B: blake2b, 10 | HashAlgorithm.BLAKE3: blake3, 11 | } 12 | 13 | # Default length of a hash digest. 14 | DEFAULT_DIGEST_LENGTH = 32 15 | 16 | # Default hash algo. 17 | DEFAULT_HASH_ALGO = HashAlgorithm.BLAKE2B 18 | 19 | 20 | def get_hash( 21 | data: bytes, 22 | size: int = DEFAULT_DIGEST_LENGTH, 23 | algo: HashAlgorithm = DEFAULT_HASH_ALGO 24 | ) -> DigestBytes: 25 | """Maps input to a hash function output. 26 | 27 | :param data: Data to be hashed. 28 | :param size: Desired hashing output length. 29 | :param algo: Type of hashing algo to apply. 30 | :returns: Digest of input data. 31 | 32 | """ 33 | return ALGOS[algo](data, size) 34 | -------------------------------------------------------------------------------- /pycspr/crypto/hashifier_blake2b.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | 3 | 4 | def get_hash(data: bytes, size: int) -> bytes: 5 | """Maps input to a blake2b hash. 6 | 7 | :param data: Data to be hashed. 8 | :param size: Desired hashing output length. 9 | 10 | :returns: Blake2b hash of input data. 11 | 12 | """ 13 | h = hashlib.blake2b(digest_size=size) 14 | h.update(data) 15 | 16 | return h.digest() 17 | -------------------------------------------------------------------------------- /pycspr/crypto/hashifier_blake3.py: -------------------------------------------------------------------------------- 1 | import blake3 as _hashlib 2 | 3 | 4 | def get_hash(data: bytes, size: int) -> bytes: 5 | """Maps input to a blake2b hash. 6 | 7 | :param data: Data to be hashed. 8 | :param size: Desired hashing output length. 9 | 10 | :returns: Blake2b hash of input data. 11 | 12 | """ 13 | return _hashlib.blake3(data).digest(length=size) 14 | -------------------------------------------------------------------------------- /pycspr/factory/__init__.py: -------------------------------------------------------------------------------- 1 | from pycspr.factory.accounts import create_private_key 2 | from pycspr.factory.accounts import parse_private_key 3 | from pycspr.factory.accounts import parse_private_key_bytes 4 | from pycspr.factory.accounts import parse_public_key 5 | from pycspr.factory.accounts import parse_public_key_bytes 6 | from pycspr.factory.deploys import create_deploy 7 | from pycspr.factory.deploys import create_deploy_approval 8 | from pycspr.factory.deploys import create_deploy_arguments 9 | from pycspr.factory.deploys import create_deploy_body 10 | from pycspr.factory.deploys import create_deploy_header 11 | from pycspr.factory.deploys import create_deploy_parameters 12 | from pycspr.factory.deploys import create_deploy_ttl 13 | from pycspr.factory.deploys import create_transfer 14 | from pycspr.factory.deploys import create_transfer_session 15 | from pycspr.factory.deploys import create_standard_payment 16 | from pycspr.factory.deploys import create_validator_auction_bid 17 | from pycspr.factory.deploys import create_validator_auction_bid_withdrawal 18 | from pycspr.factory.deploys import create_validator_delegation 19 | from pycspr.factory.deploys import create_validator_delegation_withdrawal 20 | from pycspr.factory.digests import create_digest_of_block 21 | from pycspr.factory.digests import create_digest_of_block_for_finality_signature 22 | from pycspr.factory.digests import create_digest_of_deploy 23 | from pycspr.factory.digests import create_digest_of_deploy_body 24 | -------------------------------------------------------------------------------- /pycspr/factory/accounts.py: -------------------------------------------------------------------------------- 1 | import pathlib 2 | import typing 3 | 4 | from pycspr.types.crypto import KeyAlgorithm 5 | from pycspr.types.crypto import PrivateKey 6 | from pycspr.types.crypto import PublicKey 7 | from pycspr.crypto import get_key_pair_from_bytes 8 | from pycspr.crypto import get_key_pair_from_pem_file 9 | 10 | 11 | def create_private_key(algo: KeyAlgorithm, pvk: bytes, pbk: bytes) -> PrivateKey: 12 | """Returns an account holder's private key. 13 | 14 | :param algo: ECC key algorithm identifier. 15 | :param pvk: ECC private key. 16 | :param pbk: ECC public key. 17 | 18 | """ 19 | if isinstance(algo, str): 20 | algo = KeyAlgorithm[algo] 21 | 22 | return PrivateKey(pvk, pbk, algo) 23 | 24 | 25 | def parse_private_key( 26 | fpath: pathlib.Path, 27 | algo: typing.Union[str, KeyAlgorithm] = KeyAlgorithm.ED25519 28 | ) -> PrivateKey: 29 | """Returns on-chain account information deserialised from a secret key held on file system. 30 | 31 | :param fpath: Path to secret key pem file associated with the account. 32 | :param algo: ECC key algorithm identifier. 33 | :returns: On-chain account information wrapper. 34 | 35 | """ 36 | algo = KeyAlgorithm[algo] if isinstance(algo, str) else algo 37 | (pvk, pbk) = get_key_pair_from_pem_file(fpath, algo) 38 | 39 | return create_private_key(algo, pvk, pbk) 40 | 41 | 42 | def parse_private_key_bytes( 43 | pvk: bytes, 44 | algo: typing.Union[str, KeyAlgorithm] = KeyAlgorithm.ED25519 45 | ) -> PrivateKey: 46 | """Returns a user's private key deserialised from a secret key. 47 | 48 | :param pvk: A private key. 49 | :param algo: ECC key algorithm identifier. 50 | :returns: Private key wrapper. 51 | 52 | """ 53 | algo = KeyAlgorithm[algo] if isinstance(algo, str) else algo 54 | (pvk, pbk) = get_key_pair_from_bytes(pvk, algo) 55 | 56 | return create_private_key(algo, pvk, pbk) 57 | 58 | 59 | def parse_public_key(fpath: pathlib.Path) -> PublicKey: 60 | """Returns an account holder's public key. 61 | 62 | :param fpath: Path to public key hex file associated with the account. 63 | :returns: An account holder's public key. 64 | 65 | """ 66 | with open(fpath) as fstream: 67 | account_key = bytes.fromhex(fstream.read()) 68 | 69 | return PublicKey.from_bytes(account_key) 70 | 71 | 72 | def parse_public_key_bytes( 73 | pbk: bytes, 74 | algo: typing.Union[str, KeyAlgorithm] 75 | ) -> PublicKey: 76 | """Returns an account holder's public key. 77 | 78 | :param pbk: A public key. 79 | :param algo: ECC key algorithm identifier. 80 | :returns: A public key. 81 | 82 | """ 83 | algo = KeyAlgorithm[algo] if isinstance(algo, str) else algo 84 | 85 | return PublicKey(algo, pbk) 86 | -------------------------------------------------------------------------------- /pycspr/factory/digests.py: -------------------------------------------------------------------------------- 1 | from pycspr import crypto 2 | from pycspr import serializer 3 | from pycspr.types.cl import CLV_Bool 4 | from pycspr.types.cl import CLV_ByteArray 5 | from pycspr.types.cl import CLV_List 6 | from pycspr.types.cl import CLV_Option 7 | from pycspr.types.cl import CLV_PublicKey 8 | from pycspr.types.cl import CLV_String 9 | from pycspr.types.cl import CLV_U64 10 | from pycspr.types.cl import CLT_ByteArray 11 | from pycspr.types.crypto import DigestBytes 12 | from pycspr.types.node import Block 13 | from pycspr.types.node import BlockHeader 14 | from pycspr.types.node import DeployExecutableItem 15 | from pycspr.types.node import DeployHeader 16 | 17 | 18 | def create_digest_of_block(header: BlockHeader) -> DigestBytes: 19 | """Returns a block's digest. 20 | 21 | :param header: Block header information. 22 | :returns: Digest of a block. 23 | 24 | """ 25 | return crypto.get_hash( 26 | serializer.to_bytes( 27 | CLV_ByteArray( 28 | header.parent_hash 29 | ) 30 | ) + 31 | serializer.to_bytes( 32 | CLV_ByteArray( 33 | header.state_root 34 | ) 35 | ) + 36 | serializer.to_bytes( 37 | CLV_ByteArray( 38 | header.body_hash 39 | ) 40 | ) + 41 | serializer.to_bytes( 42 | CLV_Bool( 43 | header.random_bit 44 | ) 45 | ) + 46 | serializer.to_bytes( 47 | CLV_ByteArray( 48 | header.accumulated_seed 49 | ) 50 | ) + 51 | serializer.to_bytes( 52 | CLV_Option( 53 | CLV_ByteArray( 54 | serializer.to_bytes(header.era_end) 55 | ), 56 | CLT_ByteArray 57 | ) 58 | ) + 59 | serializer.to_bytes( 60 | CLV_U64( 61 | int(header.timestamp.value * 1000) 62 | ) 63 | ) + 64 | serializer.to_bytes( 65 | CLV_U64( 66 | header.era_id 67 | ) 68 | ) + 69 | serializer.to_bytes( 70 | CLV_U64( 71 | header.height 72 | ) 73 | ) + 74 | serializer.to_bytes( 75 | header.protocol_version 76 | ) 77 | ) 78 | 79 | 80 | def create_digest_of_block_for_finality_signature(block: Block) -> DigestBytes: 81 | return block.hash + serializer.to_bytes( 82 | CLV_U64(block.header.era_id) 83 | ) 84 | 85 | 86 | def create_digest_of_deploy(header: DeployHeader) -> DigestBytes: 87 | """Returns a deploy's digest. 88 | 89 | :param header: Deploy header information. 90 | :returns: Digestigest of a deploy. 91 | 92 | """ 93 | return crypto.get_hash( 94 | serializer.to_bytes( 95 | CLV_PublicKey.from_public_key(header.account) 96 | ) + 97 | serializer.to_bytes( 98 | CLV_U64(int(header.timestamp.value * 1000)) 99 | ) + 100 | serializer.to_bytes( 101 | CLV_U64(header.ttl.as_milliseconds) 102 | ) + 103 | serializer.to_bytes( 104 | CLV_U64(header.gas_price) 105 | ) + 106 | serializer.to_bytes( 107 | CLV_ByteArray(header.body_hash) 108 | ) + 109 | serializer.to_bytes( 110 | CLV_List(header.dependencies) 111 | ) + 112 | serializer.to_bytes( 113 | CLV_String(header.chain_name) 114 | ) 115 | ) 116 | 117 | 118 | def create_digest_of_deploy_body( 119 | payment: DeployExecutableItem, 120 | session: DeployExecutableItem 121 | ) -> DigestBytes: 122 | """Returns a deploy body's hash digest. 123 | 124 | :param payment: Deploy payment execution logic. 125 | :param session: Deploy session execution logic. 126 | :returns: Hash digest of a deploy body. 127 | 128 | """ 129 | return crypto.get_hash( 130 | serializer.to_bytes(payment) + 131 | serializer.to_bytes(session) 132 | ) 133 | -------------------------------------------------------------------------------- /pycspr/serializer/__init__.py: -------------------------------------------------------------------------------- 1 | from pycspr.serializer.binary import to_bytes 2 | from pycspr.serializer.binary import from_bytes 3 | from pycspr.serializer.json import to_json 4 | from pycspr.serializer.json import from_json 5 | from pycspr.serializer.utils.clv_to_clt import encode as clv_to_clt 6 | from pycspr.serializer.utils.clv_to_parsed import encode as clv_to_parsed 7 | 8 | 9 | __all__ = [ 10 | "clv_to_clt", 11 | "clv_to_parsed", 12 | "to_bytes", 13 | "to_json", 14 | "from_bytes", 15 | "from_json", 16 | ] 17 | -------------------------------------------------------------------------------- /pycspr/serializer/binary/__init__.py: -------------------------------------------------------------------------------- 1 | from pycspr.serializer.binary.decoder import decode 2 | from pycspr.serializer.binary.decoder import decode as from_bytes 3 | from pycspr.serializer.binary.encoder import encode 4 | from pycspr.serializer.binary.encoder import encode as to_bytes 5 | -------------------------------------------------------------------------------- /pycspr/serializer/binary/decoder.py: -------------------------------------------------------------------------------- 1 | from pycspr.serializer.binary import decoder_clt 2 | from pycspr.serializer.binary import decoder_clv 3 | from pycspr.serializer.binary import decoder_node 4 | from pycspr.types.cl import CLT_Type 5 | from pycspr.types.cl import TYPESET_CLT 6 | from pycspr.types.node import TYPESET as TYPESET_NODE 7 | 8 | 9 | def decode(typedef: object, bstream: bytes) -> object: 10 | if isinstance(typedef, CLT_Type): 11 | return decoder_clv.decode(typedef, bstream) 12 | elif typedef in TYPESET_CLT: 13 | return decoder_clt.decode(bstream) 14 | elif typedef in TYPESET_NODE: 15 | return decoder_node.decode(typedef, bstream) 16 | else: 17 | raise ValueError("Unrecognized type definition") 18 | -------------------------------------------------------------------------------- /pycspr/serializer/binary/decoder_clt.py: -------------------------------------------------------------------------------- 1 | from pycspr.serializer.binary.decoder_clv import decode as decode_clv 2 | from pycspr.types.cl import CLT_Type 3 | from pycspr.types.cl import CLT_TypeKey 4 | from pycspr.types.cl import CLT_Any 5 | from pycspr.types.cl import CLT_Bool 6 | from pycspr.types.cl import CLT_ByteArray 7 | from pycspr.types.cl import CLT_I32 8 | from pycspr.types.cl import CLT_I64 9 | from pycspr.types.cl import CLT_U8 10 | from pycspr.types.cl import CLT_U32 11 | from pycspr.types.cl import CLT_U64 12 | from pycspr.types.cl import CLT_U128 13 | from pycspr.types.cl import CLT_U256 14 | from pycspr.types.cl import CLT_U512 15 | from pycspr.types.cl import CLT_Key 16 | from pycspr.types.cl import CLT_List 17 | from pycspr.types.cl import CLT_Map 18 | from pycspr.types.cl import CLT_Option 19 | from pycspr.types.cl import CLT_PublicKey 20 | from pycspr.types.cl import CLT_Result 21 | from pycspr.types.cl import CLT_String 22 | from pycspr.types.cl import CLT_Tuple1 23 | from pycspr.types.cl import CLT_Tuple2 24 | from pycspr.types.cl import CLT_Tuple3 25 | from pycspr.types.cl import CLT_Unit 26 | from pycspr.types.cl import CLT_URef 27 | 28 | 29 | def decode(bstream: bytes) -> CLT_Type: 30 | """Decoder: CL type info <- an array of bytes. 31 | 32 | :param bstream: An array of bytes being decoded. 33 | :returns: A CL type definition. 34 | 35 | """ 36 | typekey, bstream = CLT_TypeKey(int(bstream[0])), bstream[1:] 37 | if typekey in _DECODERS["simple"]: 38 | return bstream, _DECODERS["simple"][typekey]() 39 | elif typekey in _DECODERS["complex"]: 40 | return _DECODERS["complex"][typekey](bstream) 41 | else: 42 | raise ValueError("Unsupported cl type tag") 43 | 44 | 45 | def _decode_byte_array(bstream: bytes): 46 | bstream, size = decode_clv(CLT_U32(), bstream) 47 | 48 | return bstream, CLT_ByteArray(size.value) 49 | 50 | 51 | def _decode_list(bstream: bytes): 52 | bstream, item_type = decode(bstream) 53 | 54 | return bstream, CLT_List(item_type) 55 | 56 | 57 | def _decode_map(bstream: bytes): 58 | bstream, key_type = decode(bstream) 59 | bstream, value_type = decode(bstream) 60 | 61 | return bstream, CLT_Map(key_type, value_type) 62 | 63 | 64 | def _decode_option(bstream: bytes): 65 | bstream, option_type = decode(bstream) 66 | 67 | return bstream, CLT_Option(option_type) 68 | 69 | 70 | def _decode_tuple_1(bstream: bytes): 71 | bstream, t0_type = decode(bstream) 72 | 73 | return bstream, CLT_Tuple1(t0_type) 74 | 75 | 76 | def _decode_tuple_2(bstream: bytes): 77 | bstream, t0_type = decode(bstream) 78 | bstream, t1_type = decode(bstream) 79 | 80 | return bstream, CLT_Tuple2(t0_type, t1_type) 81 | 82 | 83 | def _decode_tuple_3(bstream: bytes): 84 | bstream, t0_type = decode(bstream) 85 | bstream, t1_type = decode(bstream) 86 | bstream, t2_type = decode(bstream) 87 | 88 | return bstream, CLT_Tuple3(t0_type, t1_type, t2_type) 89 | 90 | 91 | _DECODERS = { 92 | "complex": { 93 | CLT_TypeKey.BYTE_ARRAY: _decode_byte_array, 94 | CLT_TypeKey.LIST: _decode_list, 95 | CLT_TypeKey.MAP: _decode_map, 96 | CLT_TypeKey.OPTION: _decode_option, 97 | CLT_TypeKey.TUPLE_1: _decode_tuple_1, 98 | CLT_TypeKey.TUPLE_2: _decode_tuple_2, 99 | CLT_TypeKey.TUPLE_3: _decode_tuple_3, 100 | }, 101 | "simple": { 102 | CLT_TypeKey.ANY: CLT_Any, 103 | CLT_TypeKey.BOOL: CLT_Bool, 104 | CLT_TypeKey.I32: CLT_I32, 105 | CLT_TypeKey.I64: CLT_I64, 106 | CLT_TypeKey.KEY: CLT_Key, 107 | CLT_TypeKey.PUBLIC_KEY: CLT_PublicKey, 108 | CLT_TypeKey.RESULT: CLT_Result, 109 | CLT_TypeKey.STRING: CLT_String, 110 | CLT_TypeKey.U8: CLT_U8, 111 | CLT_TypeKey.U32: CLT_U32, 112 | CLT_TypeKey.U64: CLT_U64, 113 | CLT_TypeKey.U128: CLT_U128, 114 | CLT_TypeKey.U256: CLT_U256, 115 | CLT_TypeKey.U512: CLT_U512, 116 | CLT_TypeKey.UNIT: CLT_Unit, 117 | CLT_TypeKey.UREF: CLT_URef, 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /pycspr/serializer/binary/encoder.py: -------------------------------------------------------------------------------- 1 | from pycspr.serializer.binary import encoder_clt 2 | from pycspr.serializer.binary import encoder_clv 3 | from pycspr.serializer.binary import encoder_node 4 | from pycspr.types.cl import CLT_Type 5 | from pycspr.types.cl import CLV_Value 6 | 7 | 8 | def encode(entity: object) -> bytes: 9 | if isinstance(entity, CLT_Type): 10 | return encoder_clt.encode(entity) 11 | elif isinstance(entity, CLV_Value): 12 | return encoder_clv.encode(entity) 13 | else: 14 | return encoder_node.encode(entity) 15 | -------------------------------------------------------------------------------- /pycspr/serializer/binary/encoder_clt.py: -------------------------------------------------------------------------------- 1 | from pycspr.serializer.binary.encoder_clv import encode as encode_clv 2 | from pycspr.types.cl import CLT_Type 3 | from pycspr.types.cl import CLT_TypeKey 4 | from pycspr.types.cl import CLV_U32 5 | 6 | 7 | def encode(entity: CLT_Type) -> bytes: 8 | """Encoder: CL type -> an array of bytes. 9 | 10 | :param entity: A CL type to be encoded. 11 | :returns: An array of bytes. 12 | 13 | """ 14 | if entity.type_key in _ENCODERS["simple"]: 15 | return bytes([entity.type_key.value]) + bytes([]) 16 | elif entity.type_key in _ENCODERS["complex"]: 17 | return \ 18 | bytes([entity.type_key.value]) + \ 19 | _ENCODERS["complex"][entity.type_key](entity) 20 | else: 21 | raise ValueError("Unrecognized cl type") 22 | 23 | 24 | _ENCODERS: dict = { 25 | "complex": { 26 | CLT_TypeKey.BYTE_ARRAY: lambda x: encode_clv(CLV_U32(x.size)), 27 | CLT_TypeKey.LIST: lambda x: encode(x.inner_type), 28 | CLT_TypeKey.MAP: lambda x: encode(x.key_type) + encode(x.value_type), 29 | CLT_TypeKey.OPTION: lambda x: encode(x.inner_type), 30 | CLT_TypeKey.TUPLE_1: lambda x: encode(x.t0_type), 31 | CLT_TypeKey.TUPLE_2: lambda x: encode(x.t0_type) + encode(x.t1_type), 32 | CLT_TypeKey.TUPLE_3: lambda x: encode(x.t0_type) + encode(x.t1_type) + encode(x.t2_type), 33 | }, 34 | "simple": { 35 | CLT_TypeKey.ANY, 36 | CLT_TypeKey.BOOL, 37 | CLT_TypeKey.I32, 38 | CLT_TypeKey.I64, 39 | CLT_TypeKey.KEY, 40 | CLT_TypeKey.PUBLIC_KEY, 41 | CLT_TypeKey.RESULT, 42 | CLT_TypeKey.STRING, 43 | CLT_TypeKey.U8, 44 | CLT_TypeKey.U32, 45 | CLT_TypeKey.U64, 46 | CLT_TypeKey.U128, 47 | CLT_TypeKey.U256, 48 | CLT_TypeKey.U512, 49 | CLT_TypeKey.UNIT, 50 | CLT_TypeKey.UREF, 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /pycspr/serializer/binary/encoder_clv.py: -------------------------------------------------------------------------------- 1 | import typing 2 | 3 | from pycspr.types.cl import CLV_Value 4 | from pycspr.types.cl import CLV_Any 5 | from pycspr.types.cl import CLV_Bool 6 | from pycspr.types.cl import CLV_ByteArray 7 | from pycspr.types.cl import CLV_I32 8 | from pycspr.types.cl import CLV_I64 9 | from pycspr.types.cl import CLV_U8 10 | from pycspr.types.cl import CLV_U32 11 | from pycspr.types.cl import CLV_U64 12 | from pycspr.types.cl import CLV_U128 13 | from pycspr.types.cl import CLV_U256 14 | from pycspr.types.cl import CLV_U512 15 | from pycspr.types.cl import CLV_Key 16 | from pycspr.types.cl import CLV_List 17 | from pycspr.types.cl import CLV_Map 18 | from pycspr.types.cl import CLV_Option 19 | from pycspr.types.cl import CLV_PublicKey 20 | from pycspr.types.cl import CLV_Result 21 | from pycspr.types.cl import CLV_String 22 | from pycspr.types.cl import CLV_Tuple1 23 | from pycspr.types.cl import CLV_Tuple2 24 | from pycspr.types.cl import CLV_Tuple3 25 | from pycspr.types.cl import CLV_Unit 26 | from pycspr.types.cl import CLV_URef 27 | 28 | 29 | def encode(entity: CLV_Value) -> bytes: 30 | """Encoder: CL value -> array of bytes. 31 | 32 | :param entity: A CL value to be encoded. 33 | :returns: An array of bytes. 34 | 35 | """ 36 | try: 37 | encoder = _ENCODERS[type(entity)] 38 | except KeyError: 39 | raise ValueError(f"CL value cannot be encoded as bytes: {type(entity)}") 40 | else: 41 | return encoder(entity) 42 | 43 | 44 | def _encode_any(entity: CLV_Any) -> bytes: 45 | raise NotImplementedError() 46 | 47 | 48 | def _encode_int( 49 | value: int, 50 | byte_lengths: typing.List[int], 51 | signed: bool, 52 | trim: bool 53 | ) -> bytes: 54 | encoded = None 55 | for length in byte_lengths: 56 | try: 57 | encoded = value.to_bytes(length, "little", signed=signed) 58 | except OverflowError: 59 | continue 60 | 61 | if encoded is None: 62 | raise ValueError("Invalid integer: max size exceeded") 63 | 64 | if trim: 65 | while encoded and encoded[-1] == 0: 66 | encoded = encoded[0:-1] 67 | 68 | if len(byte_lengths) == 1: 69 | return encoded 70 | else: 71 | return bytes([len(encoded)]) + encoded 72 | 73 | 74 | def _encode_list(entity: CLV_List) -> bytes: 75 | return \ 76 | encode(CLV_U32(len(entity.vector))) + \ 77 | bytes([i for j in map(encode, entity.vector) for i in j]) 78 | 79 | 80 | def _encode_map(entity: CLV_Map) -> bytes: 81 | result = bytes([]) 82 | for k, v in entity.value: 83 | result += encode(k) 84 | result += encode(v) 85 | 86 | return encode(CLV_U32(len(entity.value))) + result 87 | 88 | 89 | def _encode_result(entity: CLV_Result) -> bytes: 90 | raise NotImplementedError() 91 | 92 | 93 | def _encode_string(entity: CLV_String) -> bytes: 94 | encoded: bytes = (entity.value or "").encode("utf-8") 95 | 96 | return encode(CLV_U32(len(encoded))) + encoded 97 | 98 | 99 | _ENCODERS = { 100 | CLV_Any: _encode_any, 101 | CLV_Bool: lambda x: bytes([x.value]), 102 | CLV_ByteArray: lambda x: x.value, 103 | CLV_I32: lambda x: _encode_int(x.value, (4, ), True, False), 104 | CLV_I64: lambda x: _encode_int(x.value, (8, ), True, False), 105 | CLV_Key: lambda x: bytes([x.key_type.value]) + x.identifier, 106 | CLV_List: _encode_list, 107 | CLV_Map: _encode_map, 108 | CLV_Option: lambda x: bytes([1]) + encode(x.value) if x.value else bytes([0]), 109 | CLV_PublicKey: lambda x: bytes([x.algo.value]) + x.pbk, 110 | CLV_Result: _encode_result, 111 | CLV_String: _encode_string, 112 | CLV_Tuple1: lambda x: encode(x.v0), 113 | CLV_Tuple2: lambda x: encode(x.v0) + encode(x.v1), 114 | CLV_Tuple3: lambda x: encode(x.v0) + encode(x.v1) + encode(x.v2), 115 | CLV_U8: lambda x: _encode_int(x.value, (1, ), False, False), 116 | CLV_U32: lambda x: _encode_int(x.value, (4, ), False, False), 117 | CLV_U64: lambda x: _encode_int(x.value, (8, ), False, False), 118 | CLV_U128: lambda x: _encode_int(x.value, (1, 4, 8, 16), False, True), 119 | CLV_U256: lambda x: _encode_int(x.value, (1, 4, 8, 16, 32), False, True), 120 | CLV_U512: lambda x: _encode_int(x.value, (1, 4, 8, 16, 32, 64), False, True), 121 | CLV_Unit: lambda _: bytes([]), 122 | CLV_URef: lambda x: x.address + bytes([x.access_rights.value]), 123 | } 124 | -------------------------------------------------------------------------------- /pycspr/serializer/json/__init__.py: -------------------------------------------------------------------------------- 1 | from pycspr.serializer.json.decoder import decode 2 | from pycspr.serializer.json.decoder import decode as from_json 3 | from pycspr.serializer.json.encoder import encode 4 | from pycspr.serializer.json.encoder import encode as to_json 5 | -------------------------------------------------------------------------------- /pycspr/serializer/json/decoder.py: -------------------------------------------------------------------------------- 1 | from pycspr.serializer.json import decoder_clt 2 | from pycspr.serializer.json import decoder_clv 3 | from pycspr.serializer.json import decoder_crypto 4 | from pycspr.serializer.json import decoder_node 5 | from pycspr.types import TYPESET_CLT 6 | from pycspr.types import TYPESET_CLV 7 | from pycspr.types import TYPESET_CRYPTO 8 | from pycspr.types import TYPESET_NODE 9 | 10 | 11 | def decode(typedef: object, encoded: dict) -> object: 12 | """Decodes a domain entity instance from JSON encoded data. 13 | 14 | :param typedef: Domain type to be instantiated. 15 | :param encoded: JSON encoded data. 16 | :returns: A domain type instance. 17 | 18 | """ 19 | if typedef in TYPESET_CLT: 20 | return decoder_clt.decode(encoded) 21 | elif typedef in TYPESET_CLV: 22 | return decoder_clv.decode(encoded) 23 | elif typedef in TYPESET_CRYPTO: 24 | return decoder_crypto.decode(typedef, encoded) 25 | elif typedef in TYPESET_NODE: 26 | return decoder_node.decode(typedef, encoded) 27 | else: 28 | raise ValueError(f"Unsupported type: {typedef}") 29 | -------------------------------------------------------------------------------- /pycspr/serializer/json/decoder_clt.py: -------------------------------------------------------------------------------- 1 | import typing 2 | 3 | from pycspr.types.cl import CLT_Type 4 | from pycspr.types.cl import CLT_Any 5 | from pycspr.types.cl import CLT_Bool 6 | from pycspr.types.cl import CLT_ByteArray 7 | from pycspr.types.cl import CLT_I32 8 | from pycspr.types.cl import CLT_I64 9 | from pycspr.types.cl import CLT_U8 10 | from pycspr.types.cl import CLT_U32 11 | from pycspr.types.cl import CLT_U64 12 | from pycspr.types.cl import CLT_U128 13 | from pycspr.types.cl import CLT_U256 14 | from pycspr.types.cl import CLT_U512 15 | from pycspr.types.cl import CLT_Key 16 | from pycspr.types.cl import CLT_List 17 | from pycspr.types.cl import CLT_Map 18 | from pycspr.types.cl import CLT_Option 19 | from pycspr.types.cl import CLT_PublicKey 20 | from pycspr.types.cl import CLT_Result 21 | from pycspr.types.cl import CLT_String 22 | from pycspr.types.cl import CLT_Tuple1 23 | from pycspr.types.cl import CLT_Tuple2 24 | from pycspr.types.cl import CLT_Tuple3 25 | from pycspr.types.cl import CLT_Unit 26 | from pycspr.types.cl import CLT_URef 27 | 28 | 29 | _SIMPLE_TYPES = { 30 | "Any": CLT_Any, 31 | "Bool": CLT_Bool, 32 | "I32": CLT_I32, 33 | "I64": CLT_I64, 34 | "Key": CLT_Key, 35 | "PublicKey": CLT_PublicKey, 36 | "Result": CLT_Result, 37 | "String": CLT_String, 38 | "U8": CLT_U8, 39 | "U32": CLT_U32, 40 | "U64": CLT_U64, 41 | "U128": CLT_U128, 42 | "U256": CLT_U256, 43 | "U512": CLT_U512, 44 | "Unit": CLT_Unit, 45 | "URef": CLT_URef, 46 | } 47 | 48 | 49 | def decode(encoded: typing.Union[dict, str]) -> CLT_Type: 50 | """Decodes a domain entity instance from JSON encoded data. 51 | 52 | :param encoded: JSON encoded data. 53 | :returns: A CL type related type instance. 54 | 55 | """ 56 | if isinstance(encoded, str) and encoded in _SIMPLE_TYPES: 57 | return _SIMPLE_TYPES[encoded]() 58 | 59 | elif "ByteArray" in encoded: 60 | return CLT_ByteArray( 61 | encoded["ByteArray"] 62 | ) 63 | 64 | elif "List" in encoded: 65 | return CLT_List( 66 | decode(encoded["List"]) 67 | ) 68 | 69 | elif "Map" in encoded: 70 | return CLT_Map( 71 | decode(encoded["Map"]["key"]), 72 | decode(encoded["Map"]["value"]) 73 | ) 74 | 75 | elif "Option" in encoded: 76 | return CLT_Option( 77 | decode(encoded["Option"]) 78 | ) 79 | 80 | elif "Tuple1" in encoded: 81 | return CLT_Tuple1( 82 | decode(encoded["Tuple1"]) 83 | ) 84 | 85 | elif "Tuple2" in encoded: 86 | return CLT_Tuple2( 87 | decode(encoded["Tuple2"][0]), 88 | decode(encoded["Tuple2"][1]) 89 | ) 90 | 91 | elif "Tuple3" in encoded: 92 | return CLT_Tuple3( 93 | decode(encoded["Tuple3"][0]), 94 | decode(encoded["Tuple3"][1]), 95 | decode(encoded["Tuple3"][2]) 96 | ) 97 | 98 | else: 99 | raise ValueError("Invalid CL type JSON representation") 100 | -------------------------------------------------------------------------------- /pycspr/serializer/json/decoder_clv.py: -------------------------------------------------------------------------------- 1 | from pycspr.types.cl import CLT_Type 2 | from pycspr.serializer.json.decoder_clt import decode as decode_clt 3 | from pycspr.serializer.binary.decoder_clv import decode as decode_clv 4 | 5 | 6 | def decode(encoded: dict): 7 | """Decodes a domain entity instance from JSON encoded data. 8 | 9 | :param encoded: JSON encoded data. 10 | :returns: A CL value related type instance. 11 | 12 | """ 13 | if "cl_type" not in encoded or "bytes" not in encoded: 14 | raise ValueError("Invalid CL value JSON encoding") 15 | 16 | # Set cl type. 17 | cl_typedef: CLT_Type = decode_clt(encoded["cl_type"]) 18 | 19 | # Decode cl value. 20 | bstream, cl_value = decode_clv( 21 | cl_typedef, 22 | bytes.fromhex(encoded["bytes"]) 23 | ) 24 | 25 | # Assert entire byte stream has been consumed, 26 | assert len(bstream) == 0 27 | 28 | return cl_value 29 | -------------------------------------------------------------------------------- /pycspr/serializer/json/decoder_crypto.py: -------------------------------------------------------------------------------- 1 | from pycspr.types.crypto import KeyAlgorithm 2 | from pycspr.types.crypto import DigestBytes 3 | from pycspr.types.crypto import DigestHex 4 | from pycspr.types.crypto import MerkleProofBytes 5 | from pycspr.types.crypto import MerkleProofHex 6 | from pycspr.types.crypto import PublicKey 7 | from pycspr.types.crypto import PublicKeyBytes 8 | from pycspr.types.crypto import PublicKeyHex 9 | from pycspr.types.crypto import PrivateKey 10 | from pycspr.types.crypto import PrivateKeyBytes 11 | from pycspr.types.crypto import PrivateKeyHex 12 | from pycspr.types.crypto import Signature 13 | from pycspr.types.crypto import SignatureBytes 14 | from pycspr.types.crypto import SignatureHex 15 | from pycspr.types.crypto import TYPESET 16 | 17 | 18 | def decode(typedef: object, encoded: dict) -> object: 19 | """Decodes a domain entity instance from JSON encoded data. 20 | 21 | :param typedef: Domain type to be instantiated. 22 | :param encoded: JSON encoded data. 23 | :returns: A crypto related type instance. 24 | 25 | """ 26 | if typedef not in TYPESET: 27 | raise ValueError("Unsupported type") 28 | 29 | try: 30 | decoder = DECODERS[typedef] 31 | except KeyError: 32 | raise ValueError(f"Cannot decode {typedef} from json") 33 | else: 34 | return decoder(encoded) 35 | 36 | 37 | def _decode_public_key(encoded: PublicKeyBytes) -> PublicKey: 38 | return PublicKey( 39 | algo=KeyAlgorithm(encoded[0]), 40 | pbk=encoded[1:] 41 | ) 42 | 43 | 44 | def _decode_private_key(encoded: PrivateKeyBytes) -> PrivateKey: 45 | raise NotImplementedError("_decode_private_key") 46 | 47 | 48 | def _decode_signature(encoded: SignatureBytes) -> Signature: 49 | return Signature( 50 | algo=KeyAlgorithm(encoded[0]), 51 | sig=encoded[1:] 52 | ) 53 | 54 | 55 | DECODERS = { 56 | DigestBytes: lambda x: x, 57 | DigestHex: lambda x: decode(DigestBytes, bytes.fromhex(x)), 58 | MerkleProofBytes: lambda x: x, 59 | MerkleProofHex: lambda x: decode(MerkleProofBytes, bytes.fromhex(x)), 60 | PublicKey: _decode_public_key, 61 | PublicKeyBytes: lambda x: decode(PublicKey, x), 62 | PublicKeyHex: lambda x: decode(PublicKeyBytes, bytes.fromhex(x)), 63 | PrivateKey: _decode_private_key, 64 | PrivateKeyBytes: lambda x: decode(PrivateKey, x), 65 | PrivateKeyHex: lambda x: decode(PrivateKeyBytes, bytes.fromhex(x)), 66 | Signature: _decode_signature, 67 | SignatureBytes: lambda x: decode(Signature, x), 68 | SignatureHex: lambda x: decode(SignatureBytes, bytes.fromhex(x)), 69 | } 70 | -------------------------------------------------------------------------------- /pycspr/serializer/json/decoder_primitives.py: -------------------------------------------------------------------------------- 1 | DECODERS = { 2 | bool: lambda x: None if x is None else bool(x), 3 | bytes: lambda x: None if x is None else bytes.fromhex(x), 4 | dict: lambda x: x, 5 | int: lambda x: None if x is None else int(x), 6 | str: lambda x: None if x is None else x.strip(), 7 | } 8 | -------------------------------------------------------------------------------- /pycspr/serializer/json/encoder.py: -------------------------------------------------------------------------------- 1 | import typing 2 | 3 | from pycspr.serializer.json import encoder_clt, encoder_clv, encoder_crypto, encoder_node 4 | from pycspr.types import TYPESET_CLT, TYPESET_CLV, TYPESET_CRYPTO, TYPESET_NODE 5 | 6 | 7 | def encode(entity: object) -> typing.Union[str, dict]: 8 | """Encodes a domain entity instance to a JSON encodeable dictionary. 9 | 10 | :param entity: A node related type instance to be encoded. 11 | :returns: A JSON encodeable dictionary or string. 12 | 13 | """ 14 | typedef = type(entity) 15 | if typedef in TYPESET_CLT: 16 | return encoder_clt.encode(entity) 17 | elif typedef in TYPESET_CLV: 18 | return encoder_clv.encode(entity) 19 | elif typedef in TYPESET_CRYPTO: 20 | return encoder_crypto.encode(entity) 21 | elif typedef in TYPESET_NODE: 22 | return encoder_node.encode(entity) 23 | else: 24 | raise ValueError(f"Unsupported type: {typedef}") 25 | -------------------------------------------------------------------------------- /pycspr/serializer/json/encoder_clt.py: -------------------------------------------------------------------------------- 1 | import typing 2 | 3 | from pycspr.types.cl import CLT_Type 4 | from pycspr.types.cl import CLT_TypeKey 5 | 6 | 7 | def encode(entity: CLT_Type) -> typing.Union[str, dict]: 8 | """Encodes a domain entity instance to a JSON encodeable dictionary. 9 | 10 | :param entity: A Cl type related type instance to be encoded. 11 | :returns: A JSON encodeable dictionary. 12 | 13 | """ 14 | try: 15 | encoder = _ENCODERS[entity.type_key] 16 | except KeyError: 17 | raise ValueError("Invalid CL type") 18 | else: 19 | return encoder(entity) 20 | 21 | 22 | _ENCODERS: dict = { 23 | CLT_TypeKey.ANY: 24 | lambda _: "Any", 25 | CLT_TypeKey.BOOL: 26 | lambda _: "Bool", 27 | CLT_TypeKey.BYTE_ARRAY: 28 | lambda x: { 29 | "ByteArray": x.size 30 | }, 31 | CLT_TypeKey.I32: 32 | lambda _: "I32", 33 | CLT_TypeKey.I64: 34 | lambda _: "I64", 35 | CLT_TypeKey.KEY: 36 | lambda _: "Key", 37 | CLT_TypeKey.LIST: 38 | lambda x: { 39 | "List": encode(x.inner_type) 40 | }, 41 | CLT_TypeKey.MAP: 42 | lambda x: { 43 | "Map": { 44 | "key": encode(x.key_type), 45 | "value": encode(x.value_type) 46 | } 47 | }, 48 | CLT_TypeKey.OPTION: 49 | lambda x: { 50 | "Option": encode(x.inner_type) 51 | }, 52 | CLT_TypeKey.PUBLIC_KEY: 53 | lambda _: "PublicKey", 54 | CLT_TypeKey.RESULT: 55 | lambda _: "Result", 56 | CLT_TypeKey.STRING: 57 | lambda _: "String", 58 | CLT_TypeKey.TUPLE_1: 59 | lambda x: { 60 | "Tuple1": encode(x.t0_type) 61 | }, 62 | CLT_TypeKey.TUPLE_2: 63 | lambda x: { 64 | "Tuple2": [ 65 | encode(x.t0_type), 66 | encode(x.t1_type), 67 | ] 68 | }, 69 | CLT_TypeKey.TUPLE_3: 70 | lambda x: { 71 | "Tuple3": [ 72 | encode(x.t0_type), 73 | encode(x.t1_type), 74 | encode(x.t2_type) 75 | ] 76 | }, 77 | CLT_TypeKey.U8: 78 | lambda _: "U8", 79 | CLT_TypeKey.U32: 80 | lambda _: "U32", 81 | CLT_TypeKey.U64: 82 | lambda _: "U64", 83 | CLT_TypeKey.U128: 84 | lambda _: "U128", 85 | CLT_TypeKey.U256: 86 | lambda _: "U256", 87 | CLT_TypeKey.U512: 88 | lambda _: "U512", 89 | CLT_TypeKey.UNIT: 90 | lambda _: "Unit", 91 | CLT_TypeKey.UREF: 92 | lambda _: "URef", 93 | } 94 | -------------------------------------------------------------------------------- /pycspr/serializer/json/encoder_clv.py: -------------------------------------------------------------------------------- 1 | from pycspr.crypto import checksummer 2 | from pycspr.serializer.binary.encoder_clv import encode as encode_clv 3 | from pycspr.serializer.json.encoder_clt import encode as encode_clt 4 | from pycspr.serializer.utils import clv_to_clt 5 | from pycspr.serializer.utils import clv_to_parsed 6 | from pycspr.types.cl import CLV_Value 7 | 8 | 9 | def encode(entity: CLV_Value) -> dict: 10 | """Encodes a domain entity instance to a JSON encodeable dictionary. 11 | 12 | :param entity: A CL value related type instance to be encoded. 13 | :returns: A JSON encodeable dictionary. 14 | 15 | """ 16 | return { 17 | "cl_type": encode_clt( 18 | clv_to_clt(entity) 19 | ), 20 | "bytes": checksummer.encode_bytes( 21 | encode_clv(entity) 22 | ), 23 | "parsed": clv_to_parsed(entity) 24 | } 25 | -------------------------------------------------------------------------------- /pycspr/serializer/json/encoder_crypto.py: -------------------------------------------------------------------------------- 1 | from pycspr.types.crypto import PublicKey 2 | from pycspr.types.crypto import PublicKeyHex 3 | from pycspr.types.crypto import Signature 4 | 5 | 6 | def encode(entity: object) -> dict: 7 | """Encodes a domain entity instance to a JSON encodeable dictionary. 8 | 9 | :param entity: A node related type instance to be encoded. 10 | :returns: A JSON encodeable dictionary. 11 | 12 | """ 13 | typedef = type(entity) 14 | try: 15 | encoder = ENCODERS[typedef] 16 | except KeyError: 17 | raise ValueError(f"Unknown entity type: {typedef} :: {entity}") 18 | else: 19 | return encoder(entity) 20 | 21 | 22 | def _encode_public_key(entity: PublicKey) -> PublicKeyHex: 23 | return entity.to_hex() 24 | 25 | 26 | def _encode_signature(entity: Signature) -> str: 27 | return entity.to_hex() 28 | 29 | 30 | ENCODERS = { 31 | PublicKey: _encode_public_key, 32 | Signature: _encode_signature, 33 | } 34 | -------------------------------------------------------------------------------- /pycspr/serializer/json/encoder_primitives.py: -------------------------------------------------------------------------------- 1 | ENCODERS = { 2 | bool: lambda x: x, 3 | bytes: lambda x: None if x is None else x.hex(), 4 | dict: lambda x: x, 5 | int: lambda x: x, 6 | str: lambda x: None if x is None else x.strip(), 7 | } 8 | -------------------------------------------------------------------------------- /pycspr/serializer/utils/__init__.py: -------------------------------------------------------------------------------- 1 | from pycspr.serializer.utils.clv_to_clt import encode as clv_to_clt 2 | from pycspr.serializer.utils.clv_to_parsed import encode as clv_to_parsed 3 | -------------------------------------------------------------------------------- /pycspr/serializer/utils/clv_to_clt.py: -------------------------------------------------------------------------------- 1 | from pycspr.types.cl import CLT_Type 2 | from pycspr.types.cl import CLT_Bool 3 | from pycspr.types.cl import CLT_ByteArray 4 | from pycspr.types.cl import CLT_I32 5 | from pycspr.types.cl import CLT_I64 6 | from pycspr.types.cl import CLT_U8 7 | from pycspr.types.cl import CLT_U32 8 | from pycspr.types.cl import CLT_U64 9 | from pycspr.types.cl import CLT_U128 10 | from pycspr.types.cl import CLT_U256 11 | from pycspr.types.cl import CLT_U512 12 | from pycspr.types.cl import CLT_Key 13 | from pycspr.types.cl import CLT_List 14 | from pycspr.types.cl import CLT_Map 15 | from pycspr.types.cl import CLT_Option 16 | from pycspr.types.cl import CLT_PublicKey 17 | from pycspr.types.cl import CLT_String 18 | from pycspr.types.cl import CLT_Tuple1 19 | from pycspr.types.cl import CLT_Tuple2 20 | from pycspr.types.cl import CLT_Tuple3 21 | from pycspr.types.cl import CLT_Unit 22 | from pycspr.types.cl import CLT_URef 23 | from pycspr.types.cl import CLV_Value 24 | from pycspr.types.cl import CLV_Bool 25 | from pycspr.types.cl import CLV_ByteArray 26 | from pycspr.types.cl import CLV_I32 27 | from pycspr.types.cl import CLV_I64 28 | from pycspr.types.cl import CLV_U8 29 | from pycspr.types.cl import CLV_U32 30 | from pycspr.types.cl import CLV_U64 31 | from pycspr.types.cl import CLV_U128 32 | from pycspr.types.cl import CLV_U256 33 | from pycspr.types.cl import CLV_U512 34 | from pycspr.types.cl import CLV_Key 35 | from pycspr.types.cl import CLV_List 36 | from pycspr.types.cl import CLV_Map 37 | from pycspr.types.cl import CLV_Option 38 | from pycspr.types.cl import CLV_PublicKey 39 | from pycspr.types.cl import CLV_String 40 | from pycspr.types.cl import CLV_Tuple1 41 | from pycspr.types.cl import CLV_Tuple2 42 | from pycspr.types.cl import CLV_Tuple3 43 | from pycspr.types.cl import CLV_Unit 44 | from pycspr.types.cl import CLV_URef 45 | 46 | 47 | def encode(entity: CLV_Value) -> CLT_Type: 48 | """Encodes a CL value as a CL type definition. 49 | 50 | :param entity: A CL value to be encoded. 51 | :returns: A CL type definition. 52 | 53 | """ 54 | try: 55 | encoder = _ENCODERS[type(entity)] 56 | except KeyError: 57 | raise ValueError(f"CL value cannot be encoded as CL type: {type(entity)}") 58 | else: 59 | return encoder(entity) 60 | 61 | 62 | def _encode_list(entity: CLV_List): 63 | if len(entity.vector) == 0: 64 | raise ValueError("List is empty, therefore cannot derive it's item cl type") 65 | 66 | i = entity.vector[0] 67 | for i1 in entity.vector[1:]: 68 | if type(i) is not type(i1): 69 | raise ValueError("Inconsistent list item types") 70 | 71 | return CLT_List(encode(i)) 72 | 73 | 74 | def _encode_map(entity: CLV_Map): 75 | if len(entity.value) == 0: 76 | raise ValueError("Map is empty, therefore cannot derive it's cl type") 77 | 78 | k, v = entity.value[0] 79 | for k1, v1 in entity.value[1:]: 80 | if type(k1) is not type(k) or type(v1) is not type(v): 81 | raise ValueError("Inconsistent value name/key pairs") 82 | 83 | return CLT_Map(encode(k), encode(v)) 84 | 85 | 86 | _ENCODERS: dict = { 87 | CLV_Bool: 88 | lambda _: CLT_Bool(), 89 | CLV_ByteArray: 90 | lambda x: CLT_ByteArray(len(x)), 91 | CLV_I32: 92 | lambda _: CLT_I32(), 93 | CLV_I64: 94 | lambda _: CLT_I64(), 95 | CLV_Key: 96 | lambda _: CLT_Key(), 97 | CLV_List: 98 | _encode_list, 99 | CLV_Map: 100 | _encode_map, 101 | CLV_Option: 102 | lambda x: CLT_Option(x.option_type), 103 | CLV_PublicKey: 104 | lambda _: CLT_PublicKey(), 105 | CLV_String: 106 | lambda _: CLT_String(), 107 | CLV_Tuple1: 108 | lambda x: CLT_Tuple1(encode(x.v0)), 109 | CLV_Tuple2: 110 | lambda x: CLT_Tuple2(encode(x.v0), encode(x.v1)), 111 | CLV_Tuple3: 112 | lambda x: CLT_Tuple3(encode(x.v0), encode(x.v1), encode(x.v2)), 113 | CLV_U8: 114 | lambda _: CLT_U8(), 115 | CLV_U32: 116 | lambda _: CLT_U32(), 117 | CLV_U64: 118 | lambda _: CLT_U64(), 119 | CLV_U128: 120 | lambda _: CLT_U128(), 121 | CLV_U256: 122 | lambda _: CLT_U256(), 123 | CLV_U512: 124 | lambda _: CLT_U512(), 125 | CLV_Unit: 126 | lambda _: CLT_Unit(), 127 | CLV_URef: 128 | lambda _: CLT_URef(), 129 | } 130 | -------------------------------------------------------------------------------- /pycspr/serializer/utils/clv_to_parsed.py: -------------------------------------------------------------------------------- 1 | from pycspr.crypto import checksummer 2 | from pycspr.types.cl import CLV_Value 3 | from pycspr.types.cl import CLV_Any 4 | from pycspr.types.cl import CLV_Bool 5 | from pycspr.types.cl import CLV_ByteArray 6 | from pycspr.types.cl import CLV_I32 7 | from pycspr.types.cl import CLV_I64 8 | from pycspr.types.cl import CLV_U8 9 | from pycspr.types.cl import CLV_U32 10 | from pycspr.types.cl import CLV_U64 11 | from pycspr.types.cl import CLV_U128 12 | from pycspr.types.cl import CLV_U256 13 | from pycspr.types.cl import CLV_U512 14 | from pycspr.types.cl import CLV_Key 15 | from pycspr.types.cl import CLV_KeyType 16 | from pycspr.types.cl import CLV_List 17 | from pycspr.types.cl import CLV_Map 18 | from pycspr.types.cl import CLV_Option 19 | from pycspr.types.cl import CLV_PublicKey 20 | from pycspr.types.cl import CLV_Result 21 | from pycspr.types.cl import CLV_String 22 | from pycspr.types.cl import CLV_Tuple1 23 | from pycspr.types.cl import CLV_Tuple2 24 | from pycspr.types.cl import CLV_Tuple3 25 | from pycspr.types.cl import CLV_Unit 26 | from pycspr.types.cl import CLV_URef 27 | 28 | 29 | def encode(entity: CLV_Value) -> object: 30 | """Encodes a CL value as value interpretable by humans. 31 | 32 | :param entity: A CL value to be encoded. 33 | :returns: A humanized CL value representation. 34 | 35 | """ 36 | try: 37 | encoder = _ENCODERS[type(entity)] 38 | except KeyError: 39 | raise ValueError("CL value cannot be encoded as a human interpretable value") 40 | else: 41 | return encoder(entity) 42 | 43 | 44 | def _encode_any(entity: CLV_Any) -> object: 45 | raise NotImplementedError() 46 | 47 | 48 | def _encode_key(entity: CLV_Key) -> bytes: 49 | if entity.key_type == CLV_KeyType.ACCOUNT: 50 | return { 51 | "Account": f"account-hash-{checksummer.encode_bytes(entity.identifier)}" 52 | } 53 | elif entity.key_type == CLV_KeyType.HASH: 54 | return { 55 | "Hash": f"hash-{checksummer.encode_bytes(entity.identifier)}" 56 | } 57 | elif entity.key_type == CLV_KeyType.UREF: 58 | return { 59 | "URef": f"uref-{checksummer.encode_bytes(entity.identifier)}" 60 | } 61 | 62 | 63 | def _encode_result(entity: CLV_Result) -> bytes: 64 | raise NotImplementedError() 65 | 66 | 67 | _ENCODERS = { 68 | CLV_Any: 69 | _encode_any, 70 | CLV_Bool: 71 | lambda x: str(x.value), 72 | CLV_ByteArray: 73 | lambda x: checksummer.encode_bytes(x.value), 74 | CLV_I32: 75 | lambda x: x.value, 76 | CLV_I64: 77 | lambda x: x.value, 78 | CLV_Key: 79 | _encode_key, 80 | CLV_List: 81 | lambda x: [encode(i) for i in x.vector], 82 | CLV_Map: 83 | lambda x: [{"key": encode(k), "value": encode(v)} for (k, v) in x.value], 84 | CLV_Option: 85 | lambda x: "" if x.value is None else encode(x.value), 86 | CLV_PublicKey: 87 | lambda x: checksummer.encode_account_key(x.account_key), 88 | CLV_Result: 89 | _encode_result, 90 | CLV_String: 91 | lambda x: x.value, 92 | CLV_Tuple1: 93 | lambda x: (encode(x.v0),), 94 | CLV_Tuple2: 95 | lambda x: (encode(x.v0), encode(x.v1)), 96 | CLV_Tuple3: 97 | lambda x: (encode(x.v0), encode(x.v1), encode(x.v2)), 98 | CLV_U8: 99 | lambda x: x.value, 100 | CLV_U32: 101 | lambda x: x.value, 102 | CLV_U64: 103 | lambda x: x.value, 104 | CLV_U128: 105 | lambda x: str(x.value), 106 | CLV_U256: 107 | lambda x: str(x.value), 108 | CLV_U512: 109 | lambda x: str(x.value), 110 | CLV_Unit: 111 | lambda x: "", 112 | CLV_URef: 113 | lambda x: f"uref-{checksummer.encode_bytes(x.address)}-{x.access_rights.value:03}", 114 | } 115 | -------------------------------------------------------------------------------- /pycspr/types/__init__.py: -------------------------------------------------------------------------------- 1 | from pycspr.types import node 2 | from pycspr.types import cl 3 | from pycspr.types import crypto 4 | 5 | from pycspr.types.cl import TYPESET as TYPESET_CL 6 | from pycspr.types.cl import TYPESET_CLT 7 | from pycspr.types.cl import TYPESET_CLV 8 | from pycspr.types.crypto import TYPESET as TYPESET_CRYPTO 9 | from pycspr.types.node import TYPESET as TYPESET_NODE 10 | 11 | TYPESET: set = TYPESET_CL | TYPESET_CRYPTO | TYPESET_NODE 12 | -------------------------------------------------------------------------------- /pycspr/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/casper-network/casper-python-sdk/ce072e240fb9dbec263c2d6e93f6ef2f18e6e8f6/pycspr/utils/__init__.py -------------------------------------------------------------------------------- /pycspr/utils/constants.py: -------------------------------------------------------------------------------- 1 | # Default deploy time to live. 2 | DEFAULT_DEPLOY_TTL = "5m" 3 | 4 | # Default deploy gas price. 5 | DEFAULT_GAS_PRICE = 1 6 | 7 | # Maximum deploy time to live = 2 hours. 8 | DEPLOY_TTL_MS_MAX = 1000 * 60 * 60 * 2 9 | 10 | # Maximum value of a transfer ID. 11 | MAX_TRANSFER_ID = (2 ** 63) - 1 12 | 13 | # Minimum amount in motes of a wasmless transfer. 14 | MIN_TRANSFER_AMOUNT_MOTES = 2_500_000_000 15 | 16 | # Millisecond durations of relevance. 17 | MS_1_SECOND: int = 1000 18 | MS_1_MINUTE: int = 60 * MS_1_SECOND 19 | MS_1_HOUR: int = 60 * MS_1_MINUTE 20 | 21 | # Default number of motes to pay for standard payments. 22 | STANDARD_PAYMENT_FOR_NATIVE_TRANSFERS = int(1e8) 23 | 24 | # Default number of motes to pay for standard delegation. 25 | STANDARD_PAYMENT_FOR_DELEGATION = int(5e9) 26 | 27 | # Default number of motes to pay for standard delegation withdrawal. 28 | STANDARD_PAYMENT_FOR_DELEGATION_WITHDRAWAL = int(5e9) 29 | 30 | # Default number of motes to pay for standard auction bid. 31 | STANDARD_PAYMENT_FOR_AUCTION_BID = int(5e9) 32 | 33 | # Default number of motes to pay for standard auction bid withdrawal. 34 | STANDARD_PAYMENT_FOR_AUCTION_BID_WITHDRAWAL = int(5e9) 35 | -------------------------------------------------------------------------------- /pycspr/utils/convertor.py: -------------------------------------------------------------------------------- 1 | import datetime as dt 2 | 3 | from pycspr.utils import constants 4 | 5 | 6 | def humanized_time_interval_from_ms(value: int) -> int: 7 | for typeof, timespan in ( 8 | ("hours", constants.MS_1_HOUR), 9 | ("minutes", constants.MS_1_MINUTE), 10 | ("seconds", constants.MS_1_SECOND), 11 | ("ms", 1), 12 | ): 13 | if value % timespan == 0: 14 | return f"{int(value / timespan)}{typeof}" 15 | 16 | raise ValueError("Unsupported humanized time interval") 17 | 18 | 19 | def iso_datetime_from_timestamp(value: float) -> str: 20 | ts_3_decimal_places = round(value, 3) 21 | ts_datetime = dt.datetime.fromtimestamp( 22 | ts_3_decimal_places, 23 | tz=dt.timezone.utc 24 | ) 25 | ts_iso = ts_datetime.isoformat() 26 | 27 | return f"{ts_iso[:-9]}Z" 28 | 29 | 30 | def ms_from_humanized_time_interval(value: str) -> int: 31 | value = value.lower() 32 | try: 33 | int(value) 34 | except ValueError: 35 | ... 36 | else: 37 | return int(value) 38 | 39 | if value.endswith("ms"): 40 | return int(value[0:-2]) 41 | if value.endswith("msec"): 42 | return int(value[0:-4]) 43 | 44 | if value.endswith("seconds"): 45 | return int(value[0:-7]) * constants.MS_1_SECOND 46 | if value.endswith("second"): 47 | return int(value[0:-6]) * constants.MS_1_SECOND 48 | if value.endswith("sec"): 49 | return int(value[0:-3]) * constants.MS_1_SECOND 50 | 51 | if value.endswith("minutes"): 52 | return int(value[0:-7]) * constants.MS_1_MINUTE 53 | if value.endswith("minute"): 54 | return int(value[0:-6]) * constants.MS_1_MINUTE 55 | if value.endswith("min"): 56 | return int(value[0:-3]) * constants.MS_1_MINUTE 57 | if value.endswith("m"): 58 | return int(value[0:-1]) * constants.MS_1_MINUTE 59 | 60 | if value.endswith("hours"): 61 | return int(value[0:-5]) * constants.MS_1_HOUR 62 | if value.endswith("hour"): 63 | return int(value[0:-4]) * constants.MS_1_HOUR 64 | if value.endswith("hr"): 65 | return int(value[0:-2]) * constants.MS_1_HOUR 66 | if value.endswith("h"): 67 | return int(value[0:-1]) * constants.MS_1_HOUR 68 | 69 | if value.endswith("s"): 70 | return int(value[0:-1]) * constants.MS_1_SECOND 71 | 72 | raise ValueError("Unsupported humanized time interval") 73 | 74 | 75 | def timestamp_from_iso_datetime(value: str) -> float: 76 | if value.endswith("Z"): 77 | value = value[:-1] 78 | value = f"{value}+00:00" 79 | 80 | return dt.datetime.fromisoformat(value).timestamp() 81 | -------------------------------------------------------------------------------- /pycspr/utils/io.py: -------------------------------------------------------------------------------- 1 | import json 2 | import pathlib 3 | import typing 4 | 5 | from pycspr import serializer 6 | from pycspr import factory 7 | from pycspr.types.node import Block 8 | from pycspr.types.node import Deploy 9 | 10 | # Domain types that can be written to file system. 11 | _ENTITY_TYPEDEFS = typing.Union[Block, Deploy] 12 | 13 | 14 | def get_deploy_size_bytes(deploy: Deploy) -> int: 15 | """Returns size of a deploy in bytes. 16 | 17 | :deploy: Deploy to be written in JSON format. 18 | :returns: Size of deploy in bytes. 19 | 20 | """ 21 | size: int = len(deploy.hash) 22 | for approval in deploy.approvals: 23 | size += len(approval.signature) 24 | size += len(approval.signer) 25 | size += len(serializer.to_bytes(deploy.header)) 26 | size += len(serializer.to_bytes( 27 | factory.create_deploy_body(deploy.payment, deploy.session)) 28 | ) 29 | 30 | return size 31 | 32 | 33 | def read_block(fpath: typing.Union[pathlib.Path, str]) -> Block: 34 | """Reads a block from file system. 35 | 36 | :fpath: Path to target file. 37 | 38 | """ 39 | return _read_entity(Block, fpath) 40 | 41 | 42 | def read_deploy(fpath: typing.Union[pathlib.Path, str]) -> Deploy: 43 | """Reads a deploy from file system. 44 | 45 | :fpath: Path to target file. 46 | 47 | """ 48 | return _read_entity(Deploy, fpath) 49 | 50 | 51 | def read_wasm(fpath: typing.Union[pathlib.Path, str]) -> bytes: 52 | """Read a smart contract from file system. 53 | 54 | :fpath: Path to target file. 55 | 56 | """ 57 | return _read_file(fpath) 58 | 59 | 60 | def write_block( 61 | block: Block, 62 | fpath: typing.Union[pathlib.Path, str], 63 | force: bool = True 64 | ) -> pathlib.Path: 65 | """Writes a block to file system. 66 | 67 | :deploy: Block to be written in JSON format. 68 | :fpath: Path to target file. 69 | :force: Flag indicating whether block will be written if a file already exists. 70 | :returns: Path to written file. 71 | 72 | """ 73 | return _write_entity(block, fpath, force) 74 | 75 | 76 | def write_deploy( 77 | deploy: Deploy, 78 | fpath: typing.Union[pathlib.Path, str], 79 | force: bool = True 80 | ) -> pathlib.Path: 81 | """Writes a deploy to file system. 82 | 83 | :deploy: Deploy to be written in JSON format. 84 | :fpath: Path to target file. 85 | :force: Flag indicating whether deploy will be written if a file already exists. 86 | :returns: Path to written file. 87 | 88 | """ 89 | return _write_entity(deploy, fpath, force) 90 | 91 | 92 | def _read_entity( 93 | typedef: _ENTITY_TYPEDEFS, 94 | fpath: typing.Union[pathlib.Path, str] 95 | ): 96 | return serializer.from_json(typedef, json.loads(_read_file(fpath))) 97 | 98 | 99 | def _read_file(fpath: typing.Union[pathlib.Path, str]) -> bytes: 100 | fpath = pathlib.Path(fpath) if isinstance(fpath, str) else fpath 101 | with open(fpath, "rb") as fstream: 102 | return fstream.read() 103 | 104 | 105 | def _write_entity( 106 | entity: _ENTITY_TYPEDEFS, 107 | fpath: typing.Union[pathlib.Path, str], 108 | force: bool = True 109 | ) -> pathlib.Path: 110 | if not isinstance(fpath, (pathlib.Path, str)): 111 | raise ValueError("Unrecognized file path type") 112 | 113 | fpath = pathlib.Path(fpath) if isinstance(fpath, str) else fpath 114 | if not force and fpath.exists(): 115 | raise IOError("Entity has already been written to file system") 116 | 117 | with open(str(fpath), "w") as fstream: 118 | fstream.writelines(json.dumps(serializer.to_json(entity), indent=4)) 119 | 120 | return fpath 121 | -------------------------------------------------------------------------------- /pycspr/verifier/__init__.py: -------------------------------------------------------------------------------- 1 | from pycspr.verifier.of_block import verify_block 2 | from pycspr.verifier.of_deploy import verify_deploy 3 | -------------------------------------------------------------------------------- /pycspr/verifier/of_block.py: -------------------------------------------------------------------------------- 1 | import enum 2 | 3 | from pycspr import crypto 4 | from pycspr import factory 5 | from pycspr.types.node import Block 6 | from pycspr.types.node import Weight 7 | 8 | 9 | class InvalidBlockExceptionType(enum.Enum): 10 | """Enumeration over set of invalid block exception types. 11 | 12 | """ 13 | ExpectedSwitchBlock = enum.auto() 14 | NotFound = enum.auto() 15 | InvalidEra = enum.auto() 16 | InvalidFinalitySignature = enum.auto() 17 | InvalidHash = enum.auto() 18 | InvalidProposer = enum.auto() 19 | InsufficientFinalitySignatureWeight = enum.auto() 20 | 21 | 22 | class InvalidBlockException(Exception): 23 | """Exception thrown when a block is deemed invalid. 24 | 25 | """ 26 | def __init__(self, err_type: InvalidBlockExceptionType): 27 | msg: str = f"Invalid block -> {err_type.name}" 28 | super(InvalidBlockException, self).__init__(msg) 29 | 30 | 31 | def verify_block( 32 | block: Block, 33 | switch_block_of_previous_era: Block = None, 34 | ) -> Block: 35 | """Validates a block. 36 | 37 | :block: A block to be validated. 38 | :switch_block_of_previous_era: The switch block of previous consensus era. 39 | :returns: The block if considered valid, otherwise raises exception. 40 | 41 | """ 42 | # TODO: extend input parameters -> blockID: if hash then ina descent, if height then ascending & switch block must be available 43 | 44 | # BL-000: Exception if block was not downloaded. 45 | if block is None: 46 | raise InvalidBlockException(InvalidBlockExceptionType.NotFound) 47 | 48 | # BL-001: Exception if recomputed block hash is not equal to actual block hash. 49 | if block.hash != factory.create_digest_of_block(block.header): 50 | pass 51 | # raise InvalidBlockException(InvalidBlockExceptionType.InvalidHash) 52 | 53 | # BL-002: Exception if switch block is not from a previous era. 54 | if switch_block_of_previous_era is not None: 55 | if switch_block_of_previous_era.header.era_id != block.header.era_id - 1: 56 | raise InvalidBlockException(InvalidBlockExceptionType.InvalidEra) 57 | 58 | # BL-003: Exception if a block signatory is not an era signatory. 59 | if switch_block_of_previous_era is not None: 60 | for signatory in block.signatories: 61 | if signatory not in switch_block_of_previous_era.header.era_end.next_era_signatories: 62 | raise InvalidBlockException(InvalidBlockExceptionType.InvalidProposer) 63 | 64 | # BL-004: Exception if a finality signature is invalid. 65 | block_digest_for_finality_signature: bytes = \ 66 | factory.create_digest_of_block_for_finality_signature(block) 67 | for proof in block.proofs: 68 | if not crypto.is_signature_valid( 69 | block_digest_for_finality_signature, 70 | proof.signature.algo, 71 | proof.signature.sig, 72 | proof.public_key.pbk, 73 | ): 74 | raise InvalidBlockException(InvalidBlockExceptionType.InvalidFinalitySignature) 75 | 76 | # BL-005: Exception if weight of finality signatures is insufficient. 77 | if switch_block_of_previous_era is not None: 78 | proven_weight: Weight = \ 79 | block.get_finality_signature_weight(switch_block_of_previous_era) 80 | required_weight: Weight = \ 81 | switch_block_of_previous_era.validator_weight_required_for_finality_in_next_era 82 | if proven_weight < required_weight: 83 | raise InvalidBlockException(InvalidBlockExceptionType.InsufficientFinalitySignatureWeight) 84 | 85 | return block 86 | 87 | 88 | def validate_switch_block( 89 | block: Block, 90 | switch_block_of_previous_era: Block = None, 91 | ) -> Block: 92 | """Validates last block in an era of consensus. 93 | 94 | :block: A block to be validated. 95 | :switch_block_of_previous_era: The switch block of the previous consensus era. 96 | :returns: The block if considered valid, otherwise raises exception. 97 | 98 | """ 99 | # Rule 1: Verify block was downloaded. 100 | if block is None: 101 | raise InvalidBlockException(InvalidBlockExceptionType.NotFound) 102 | 103 | # Rule 2: Verify block is a switch block. 104 | if block.is_switch is False: 105 | raise InvalidBlockException(InvalidBlockExceptionType.ExpectedSwitchBlock) 106 | 107 | # Rule 3: Apply standard block validation rules. 108 | return validate_block(block, switch_block_of_previous_era) 109 | -------------------------------------------------------------------------------- /pycspr/verifier/of_deploy.py: -------------------------------------------------------------------------------- 1 | import enum 2 | 3 | from pycspr import crypto 4 | from pycspr import factory 5 | from pycspr.types.node import Deploy 6 | 7 | 8 | class InvalidDeployExceptionType(enum.Enum): 9 | """Enumeration over set of invalid deploy exception types. 10 | 11 | """ 12 | InvalidApproval = enum.auto() 13 | InvalidBodyHash = enum.auto() 14 | InvalidHash = enum.auto() 15 | 16 | 17 | class InvalidDeployException(Exception): 18 | """Exception thrown when a deploy is deemed invalid. 19 | 20 | """ 21 | def __init__(self, err_type: InvalidDeployExceptionType): 22 | msg: str = f"Invalid deploy -> {err_type.name}" 23 | super(InvalidDeployException, self).__init__(msg) 24 | 25 | 26 | def verify_deploy(deploy: Deploy) -> Deploy: 27 | """Validates a deploy. 28 | 29 | :deploy: Deploy to be validated. 30 | :returns: The deploy if considered valid, otherwise raises exception. 31 | 32 | """ 33 | # Rule 1: Verify deploy body hash. 34 | body_hash: bytes = factory.create_digest_of_deploy_body(deploy.payment, deploy.session) 35 | if deploy.header.body_hash != body_hash: 36 | raise InvalidDeployException(InvalidDeployExceptionType.InvalidBodyHash) 37 | 38 | # Rule 2: Verify deploy hash. 39 | deploy_hash: bytes = factory.create_digest_of_deploy(deploy.header) 40 | if deploy.hash != deploy_hash: 41 | raise InvalidDeployException(InvalidDeployExceptionType.InvalidHash) 42 | 43 | # Rule 3: Verify signature authenticity. 44 | for approval in deploy.approvals: 45 | if not crypto.is_signature_valid( 46 | deploy.hash, 47 | approval.signer.algo, 48 | approval.signature.sig, 49 | approval.signer.pbk, 50 | ): 51 | raise InvalidDeployException(InvalidDeployExceptionType.InvalidApproval) 52 | 53 | return deploy 54 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "pycspr" 3 | version = "1.2.1" 4 | authors = ["Mark A. Greenslade "] 5 | classifiers = [ 6 | "Development Status :: 4 - Beta", 7 | "Intended Audience :: Developers", 8 | "Natural Language :: English", 9 | "License :: OSI Approved :: Apache Software License", 10 | "Operating System :: OS Independent", 11 | "Programming Language :: Python", 12 | "Programming Language :: Python :: 3", 13 | "Topic :: Software Development :: Libraries :: Python Modules", 14 | ] 15 | description = "Python library for interacting with a CSPR node." 16 | documentation = "https://github.com/casper-network/casper-python-sdk/blob/main/README.md" 17 | keywords = ["Casper Network", "Blockchain", "Python"] 18 | license = "Apache-2.0" 19 | maintainers = ["Mark A. Greenslade "] 20 | homepage = "https://github.com/casper-network/casper-python-sdk" 21 | readme = "README.md" 22 | repository = "https://github.com/casper-network/casper-python-sdk" 23 | 24 | [tool.poetry.dependencies] 25 | python = "^3.12" 26 | requests = "^2.31.0" 27 | jsonrpcclient = "^4.0.3" 28 | cryptography = "^42.0.2" 29 | ecdsa = "^0.18.0" 30 | blake3 = "^0.4.1" 31 | sseclient-py = "^1.8.0" 32 | pytest-asyncio = "^0.23.7" 33 | 34 | [tool.poetry.group.dev.dependencies] 35 | flake8 = "^7.0.0" 36 | pytest = "^8.0.0" 37 | 38 | [tool.poetry.group.docs] 39 | optional = true 40 | 41 | [tool.poetry.group.docs.dependencies] 42 | mkdocs = "*" 43 | 44 | [build-system] 45 | requires = ["poetry-core"] 46 | build-backend = "poetry.core.masonry.api" 47 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | asyncio_mode = auto 3 | log_cli = true 4 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/casper-network/casper-python-sdk/ce072e240fb9dbec263c2d6e93f6ef2f18e6e8f6/tests/__init__.py -------------------------------------------------------------------------------- /tests/_test_sc_lifecycle_2.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | import pytest 4 | 5 | import pycctl 6 | import pycspr 7 | from pycctl.types import AccountType 8 | from pycspr.api.rpc import Client as NodeClient 9 | from pycspr.types.node import Deploy 10 | from pycspr.types.cl import CLV_String 11 | from pycspr.types.cl import CLV_U8 12 | from pycspr.types.cl import CLV_U256 13 | from pycspr.types.node import DeployOfModuleBytes 14 | from pycctl.fsys import get_path_to_account_private_key 15 | 16 | 17 | _CONTRACT_FNAME = "activate_bid.wasm" 18 | _TOKEN_DECIMALS = 10 19 | _TOKEN_NAME = "Acme Wilderness" 20 | _TOKEN_SUPPLY = int(1e18) 21 | _TOKEN_SYMBOL = "ACME" 22 | 23 | 24 | async def test_sc_exists(): 25 | assert pycctl.fsys.get_path_to_binary(_CONTRACT_FNAME).exists() 26 | 27 | 28 | async def test_sc_installation(RPC_CLIENT: NodeClient): 29 | # Set installation tx. 30 | tx: Deploy = await _get_tx() 31 | 32 | # Dispatch tx. 33 | await _dispatch(RPC_CLIENT, tx) 34 | 35 | # Install SC 36 | # dispatch wasm 37 | # await finalisation 38 | # assert named keys mutation 39 | # assert sc initial state 40 | # cache contract version address 41 | # cache contract package address 42 | raise ValueError("dsadsad") 43 | 44 | 45 | async def _get_tx() -> Deploy: 46 | sc_binary_path = pycctl.fsys.get_path_to_binary(_CONTRACT_FNAME) 47 | 48 | sc_operator_pvk = pycspr.parse_private_key( 49 | get_path_to_account_private_key(AccountType.USER) 50 | ) 51 | 52 | tx_params = \ 53 | pycspr.create_deploy_parameters( 54 | account=sc_operator_pvk, 55 | chain_name=pycctl.constants.CHAIN_NAME, 56 | ) 57 | 58 | tx_payment = \ 59 | pycspr.create_standard_payment(pycctl.constants.SC_PAYMENT_INSTALL) 60 | 61 | tx_session = \ 62 | DeployOfModuleBytes( 63 | module_bytes=pycspr.read_wasm(sc_binary_path), 64 | args={ 65 | "token_decimals": CLV_U8(_TOKEN_DECIMALS), 66 | "token_name": CLV_String(_TOKEN_NAME), 67 | "token_symbol": CLV_String(_TOKEN_SYMBOL), 68 | "token_total_supply": CLV_U256(_TOKEN_SUPPLY), 69 | } 70 | ) 71 | 72 | tx = pycspr.create_deploy(tx_params, tx_payment, tx_session) 73 | 74 | tx.approve(sc_operator_pvk) 75 | 76 | return tx 77 | 78 | 79 | async def _dispatch(client: NodeClient, tx: Deploy): 80 | client.send_deploy(tx) 81 | while True: 82 | time.sleep(float(1)) 83 | execution_info = client.get_deploy(tx.hash).execution_info 84 | print(execution_info) 85 | 86 | break 87 | -------------------------------------------------------------------------------- /tests/_test_sc_lifecycle_3.py: -------------------------------------------------------------------------------- 1 | # Prelude 2 | # assert wasm exists 3 | # assert node is up 4 | # assert test accounts are funded 5 | 6 | 7 | 8 | # Invoke SC by hash 2: users are approved 9 | # dispatch deploy -> variant = ByContractHash 10 | # await finalisation 11 | # query contract state 12 | # assert contract state 13 | 14 | # Invoke SC by hash 3: users transfers tokens 15 | # dispatch deploy -> variant = ByContractHash 16 | # await finalisation 17 | # query contract state 18 | # assert contract state 19 | 20 | import asyncio 21 | import dataclasses 22 | import json 23 | import os 24 | import pathlib 25 | import time 26 | 27 | import requests 28 | import pytest 29 | 30 | import pycspr 31 | import pycctl 32 | import tests.utils.cctl as cctl 33 | 34 | from pycspr import NodeRpcClient 35 | 36 | 37 | @dataclasses.dataclass 38 | class TestContext(): 39 | __test__ = False 40 | 41 | client: NodeRpcClient = None 42 | 43 | def __init__(self, client: NodeRpcClient): 44 | self.client = client 45 | 46 | 47 | async def _test_pre_requisites(ctx: TestContext): 48 | 49 | async def test_net_assets_exist(): 50 | assert cctl.get_evar() is not None 51 | assert cctl.get_path_to_assets().exists() 52 | 53 | async def test_net_account_keys_exist(): 54 | for account_idx in range(1, cctl.COUNT_OF_USERS + 1): 55 | assert cctl.get_public_key_of_user(account_idx) is not None 56 | assert cctl.get_private_key_of_user(account_idx) is not None 57 | for account_idx in range(1, cctl.COUNT_OF_VALDIATORS + 1): 58 | assert cctl.get_public_key_of_validator(account_idx) is not None 59 | assert cctl.get_private_key_of_validator(account_idx) is not None 60 | assert cctl.get_public_key_of_faucet() is not None 61 | assert cctl.get_private_key_of_faucet() is not None 62 | 63 | async def test_net_is_up(): 64 | assert ctx.client.get_node_status()["reactor_state"] == "Validate" 65 | 66 | async def test_net_accounts_are_funded(): 67 | for account_idx in range(1, cctl.COUNT_OF_USERS + 1): 68 | public_key = cctl.get_public_key_of_user(account_idx) 69 | account_key = public_key.account_key 70 | purse_id = ctx.client.get_account_main_purse_uref(account_key) 71 | assert await ctx.client.get_account_balance(purse_id) > 0 72 | 73 | time.sleep(1.0) 74 | 75 | for func in { 76 | test_net_assets_exist, 77 | test_net_account_keys_exist, 78 | test_net_is_up, 79 | test_net_accounts_are_funded, 80 | }: 81 | await func() 82 | 83 | 84 | async def _test_sc_installation(ctx: TestContext): 85 | # Install SC 86 | # dispatch wasm 87 | # await finalisation 88 | # assert named keys mutation 89 | # assert sc initial state 90 | # cache contract version address 91 | # cache contract package address 92 | pass 93 | 94 | 95 | async def _test_sc_invocation_1(ctx: TestContext): 96 | # Invoke SC by hash 1: users deposit cspr for token 97 | # dispatch deploy -> variant = ByContractHash 98 | # await finalisation 99 | # query contract state 100 | # assert contract state 101 | pass 102 | 103 | 104 | async def _test_sc_invocation_2(ctx: TestContext): 105 | # Invoke SC by hash 2: users are approved 106 | # dispatch deploy -> variant = ByContractHash 107 | # await finalisation 108 | # query contract state 109 | # assert contract state 110 | pass 111 | 112 | 113 | async def _test_sc_invocation_3(ctx: TestContext): 114 | # Invoke SC by hash 3: users transfers tokens 115 | # dispatch deploy -> variant = ByContractHash 116 | # await finalisation 117 | # query contract state 118 | # assert contract state 119 | pass 120 | 121 | 122 | async def test_01(RPC_CLIENT) -> None: 123 | ctx = TestContext(RPC_CLIENT) 124 | for func in { 125 | _test_pre_requisites, 126 | _test_sc_installation, 127 | _test_sc_invocation_1, 128 | _test_sc_invocation_2, 129 | _test_sc_invocation_3, 130 | }: 131 | await func(ctx) 132 | 133 | # raise TypeError() 134 | -------------------------------------------------------------------------------- /tests/assets/accounts/account-1/public_key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MCowBQYDK2VwAyEAA/ofZ9aFGIyOnsnFvGRb3NCyI28lU6mJmgViNpDbAsQ= 3 | -----END PUBLIC KEY----- 4 | -------------------------------------------------------------------------------- /tests/assets/accounts/account-1/public_key_hex: -------------------------------------------------------------------------------- 1 | 0103fa1f67d685188c8e9ec9c5bc645bdcd0b2236f2553a9899a05623690db02c4 -------------------------------------------------------------------------------- /tests/assets/accounts/account-1/secret_key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MC4CAQAwBQYDK2VwBCIEIF6gk3Bpkt/6K9vXjNwzkF/Aif1sScSBcJpbSrm92O2v 3 | -----END PRIVATE KEY----- 4 | -------------------------------------------------------------------------------- /tests/assets/accounts/account-2/public_key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MDYwEAYHKoZIzj0CAQYFK4EEAAoDIgACRdHrkNDXV/DndYp49BdjCnM4gn8FC2IO 3 | CneaxxjAZoI= 4 | -----END PUBLIC KEY----- 5 | -------------------------------------------------------------------------------- /tests/assets/accounts/account-2/public_key_hex: -------------------------------------------------------------------------------- 1 | 020245d1eb90d0d757f0e7758a78f417630a7338827f050b620e0a779ac718c06682 -------------------------------------------------------------------------------- /tests/assets/accounts/account-2/secret_key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PRIVATE KEY----- 2 | MC4CAQEEIL/GQ4SMLxQs7+9/EjGkoNvlk2fv9N/xibqjGsspaRDZoAcGBSuBBAAK 3 | -----END EC PRIVATE KEY----- 4 | -------------------------------------------------------------------------------- /tests/assets/vectors/cl-types.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "cl_type": "ANY", 4 | "hex": "XXX" 5 | }, 6 | { 7 | "cl_type": "BOOL", 8 | "hex": "XXX" 9 | }, 10 | { 11 | "cl_type": "BYTE_ARRAY", 12 | "cl_type_size": 32, 13 | "hex": "XXX" 14 | }, 15 | { 16 | "cl_type": "I32", 17 | "hex": "XXX" 18 | }, 19 | { 20 | "cl_type": "I64", 21 | "hex": "XXX" 22 | }, 23 | { 24 | "cl_type": "KEY", 25 | "hex": "XXX" 26 | }, 27 | { 28 | "cl_type": "LIST", 29 | "cl_type_inner_type": "BOOL", 30 | "hex": "XXX" 31 | }, 32 | { 33 | "cl_type": "LIST", 34 | "cl_type_inner_type": "U64", 35 | "hex": "XXX" 36 | }, 37 | { 38 | "cl_type": "MAP", 39 | "cl_type_key_type": "STRING", 40 | "cl_type_value_type": "U64", 41 | "hex": "XXX" 42 | }, 43 | { 44 | "cl_type": "OPTION", 45 | "cl_type_inner_type": "STRING", 46 | "hex": "XXX" 47 | }, 48 | { 49 | "cl_type": "PUBLIC_KEY", 50 | "hex": "XXX" 51 | }, 52 | { 53 | "cl_type": "RESULT", 54 | "hex": "XXX" 55 | }, 56 | { 57 | "cl_type": "STRING", 58 | "hex": "XXX" 59 | }, 60 | { 61 | "cl_type": "TUPLE_1", 62 | "cl_type_t0_type": "U64", 63 | "hex": "XXX" 64 | }, 65 | { 66 | "cl_type": "TUPLE_2", 67 | "cl_type_t0_type": "U64", 68 | "cl_type_t1_type": "U128", 69 | "hex": "XXX" 70 | }, 71 | { 72 | "cl_type": "TUPLE_3", 73 | "cl_type_t0_type": "U64", 74 | "cl_type_t1_type": "U128", 75 | "cl_type_t2_type": "U256", 76 | "hex": "XXX" 77 | }, 78 | { 79 | "cl_type": "U8", 80 | "hex": "XXX" 81 | }, 82 | { 83 | "cl_type": "U32", 84 | "hex": "XXX" 85 | }, 86 | { 87 | "cl_type": "U64", 88 | "hex": "XXX" 89 | }, 90 | { 91 | "cl_type": "U64", 92 | "hex": "XXX" 93 | }, 94 | { 95 | "cl_type": "U128", 96 | "hex": "XXX" 97 | }, 98 | { 99 | "cl_type": "U256", 100 | "hex": "XXX" 101 | }, 102 | { 103 | "cl_type": "U512", 104 | "hex": "XXX" 105 | }, 106 | { 107 | "cl_type": "UNIT", 108 | "hex": "XXX" 109 | }, 110 | { 111 | "cl_type": "UREF", 112 | "hex": "XXX" 113 | } 114 | ] -------------------------------------------------------------------------------- /tests/assets/vectors/crypto-checksums.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "input": "", 4 | "checksum": "" 5 | }, 6 | { 7 | "input": "010573d52dfa032716fdc2ae5396987f280304106fc11f6ac6ccf287b81dc09ed7", 8 | "checksum": "010573D52dFA032716fDC2AE5396987f280304106fC11f6Ac6Ccf287B81dc09ED7" 9 | }, 10 | { 11 | "input": "51da5ae5c39880bfe4f94b0898332d1bd37e647f72f79cf23475df1bb1f85bea", 12 | "checksum": "51DA5aE5C39880Bfe4f94B0898332d1BD37e647F72f79Cf23475df1Bb1f85bEA" 13 | }, 14 | { 15 | "input": "51DA5aE5C39880Bfe4f94B0898332d1BD37e647F72f79Cf23475df1Bb1f85bEA51da5ae5c39880bfe4f94b0898332d1bd37e647f72f79cf23475df1bb1f85bea51DA5aE5C39880Bfe4f94B0898332d1BD37e647F72f79Cf23475df1Bb1f85bEA", 16 | "checksum": "51da5ae5c39880bfe4f94b0898332d1bd37e647f72f79cf23475df1bb1f85bea51da5ae5c39880bfe4f94b0898332d1bd37e647f72f79cf23475df1bb1f85bea51da5ae5c39880bfe4f94b0898332d1bd37e647f72f79cf23475df1bb1f85bea" 17 | } 18 | ] -------------------------------------------------------------------------------- /tests/assets/vectors/crypto-hashes.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "data": "أبو يوسف يعقوب بن إسحاق الصبّاح الكندي‎", 4 | "hashes": [ 5 | { 6 | "algo": "BLAKE2B", 7 | "digest": "44682ea86b704fb3c65cd16f84a76b621e04bbdb3746280f25cf062220e471b4" 8 | } 9 | ] 10 | } 11 | ] 12 | -------------------------------------------------------------------------------- /tests/assets/vectors/crypto-key-pairs.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "algo": "ED25519", 4 | "pvk": "822f86c1ea9546d37c6c3a451877c8c168fdcab51b65ccec142ba27a7a6b548c", 5 | "pbk": "cd62f1c5cca51fa3c25f4c76a46dd5f6b0988c95da6ea835ec4441d68dcea393", 6 | "accountKey": "01cd62f1c5cca51fa3c25f4c76a46dd5f6b0988c95da6ea835ec4441d68dcea393", 7 | "accountHash": "66549b1bb56c423caaa10363c1167db03f91d67aa349638c6943feae57a1dde9" 8 | }, 9 | { 10 | "algo": "SECP256K1", 11 | "pvk": "f6e487b6d386f1221bd3b7c2a0319124bff80b2243d3dac3180a1195cec29685", 12 | "pbk": "0272dcc1d384a6ddad06fde1ceb2f1fe524f84ddf5ee3bdb3682eb7b927de0e682", 13 | "accountKey": "020272dcc1d384a6ddad06fde1ceb2f1fe524f84ddf5ee3bdb3682eb7b927de0e682", 14 | "accountHash": "0994e932ab30ee88c4df24bbfc6e8686ad494c5ce7e2ee692afe4687a5d8a4e3" 15 | } 16 | ] -------------------------------------------------------------------------------- /tests/assets/vectors/crypto-misc.json: -------------------------------------------------------------------------------- 1 | { 2 | "account-key": "011e0ee16a28b65e3cfa74d003eea4811b06173438e920fa38961ce60eb23548f4", 3 | "signature": "01f43d8300bbd683b90bd2474d35da7cca664df5a505c47f4d35bf93531dc359bdf50b5e9493a2484306cc5eb41933b9fb118cf7954cf04d6c28441fcb1ee42f02" 4 | } -------------------------------------------------------------------------------- /tests/assets/vectors/crypto-signatures.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "data": "أبو يوسف يعقوب بن إسحاق الصبّاح الكندي‎", 4 | "key": { 5 | "algo": "ED25519", 6 | "pbk": "cd62f1c5cca51fa3c25f4c76a46dd5f6b0988c95da6ea835ec4441d68dcea393", 7 | "pvk": "822f86c1ea9546d37c6c3a451877c8c168fdcab51b65ccec142ba27a7a6b548c" 8 | }, 9 | "sig": "c507aa390d6779d949d38258d6e1c8509664c16e645bdda9e70365ae06b23944b041aee323ea3783c923d90c73efa9c59d7b1ed87a5a1332a245874bd944fb07" 10 | }, 11 | { 12 | "data": "أبو يوسف يعقوب بن إسحاق الصبّاح الكندي‎", 13 | "key": { 14 | "algo": "SECP256K1", 15 | "pbk": "0272dcc1d384a6ddad06fde1ceb2f1fe524f84ddf5ee3bdb3682eb7b927de0e682", 16 | "pvk": "f6e487b6d386f1221bd3b7c2a0319124bff80b2243d3dac3180a1195cec29685" 17 | }, 18 | "sig": "7b32f21b5861de8faa1c41205e6c17c6ff1167f59fe701a143cd588f1ef4833741a901c2539a7f755a7164458b1ba4acbf1ef209ce84533567a00ccfb13e3625" 19 | } 20 | ] -------------------------------------------------------------------------------- /tests/assets/vectors/deploys-1.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "bytes": { 4 | "session": "050300000006000000616d6f756e74050000000400f9029508060000007461726765742100000001616cf75506bc21ebb0db9d2b559b7825c5f5d31242f89b2b3ad68d216b0c1c3016020000006964090000000101000000000000000d05", 5 | "payment": "00000000000100000006000000616d6f756e74060000000500e40b540208" 6 | }, 7 | "hashes": { 8 | "deploy": "cea82bbae03fcdf2f0ed6bc66fb904e532720bd105f1a3a6a136d07f86e073b2", 9 | "body": "b984d6384ddae3e84bd8a28bd79495b65bd2951673a58297b368ab6e085ec73c" 10 | }, 11 | "payment": { 12 | "amount": 10000000000 13 | }, 14 | "session": { 15 | "amount": 2500000000, 16 | "correlation_id": 1, 17 | "target": "01616cf75506bc21ebb0db9d2b559b7825c5f5d31242f89b2b3ad68d216b0c1c30" 18 | }, 19 | "typeof": "transfer" 20 | } 21 | ] 22 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | # Simply import fixtures as a plugin. 2 | pytest_plugins = "tests.fixtures" 3 | -------------------------------------------------------------------------------- /tests/fixtures/__init__.py: -------------------------------------------------------------------------------- 1 | from tests.fixtures.accounts import account_key 2 | from tests.fixtures.accounts import account_hash 3 | from tests.fixtures.accounts import a_test_account 4 | from tests.fixtures.accounts import create_account 5 | from tests.fixtures.accounts import cp1 6 | from tests.fixtures.accounts import cp2 7 | from tests.fixtures.accounts import test_account_1 8 | from tests.fixtures.chain import account_main_purse_uref 9 | from tests.fixtures.chain import block 10 | from tests.fixtures.chain import block_hash 11 | from tests.fixtures.chain import global_state_id 12 | from tests.fixtures.chain import state_root_hash 13 | from tests.fixtures.chain import switch_block 14 | from tests.fixtures.chain import switch_block_hash 15 | from tests.fixtures.contracts import path_to_wasm_auction_bid 16 | from tests.fixtures.contracts import path_to_wasm_auction_bid_withdrawal 17 | from tests.fixtures.contracts import path_to_wasm_delegate 18 | from tests.fixtures.contracts import path_to_wasm_delegate_withdrawal 19 | from tests.fixtures.deploys import a_test_chain_id 20 | from tests.fixtures.deploys import a_test_timestamp 21 | from tests.fixtures.deploys import a_test_ttl_humanized 22 | from tests.fixtures.deploys import a_test_uref 23 | from tests.fixtures.deploys import deploy_params 24 | from tests.fixtures.deploys import deploy_params_static 25 | from tests.fixtures.deploys import a_deploy 26 | from tests.fixtures.iterator_deploy_entities import yield_entities as deploy_entities_iterator 27 | from tests.fixtures.node import CONNECTION_REST 28 | from tests.fixtures.node import CONNECTION_RPC 29 | from tests.fixtures.node import CONNECTION_RPC_SPECULATIVE 30 | from tests.fixtures.node import CONNECTION_SSE 31 | from tests.fixtures.node import RPC_CLIENT 32 | from tests.fixtures.node import REST_CLIENT 33 | from tests.fixtures.node import SSE_CLIENT 34 | from tests.fixtures.node import SPECULATIVE_RPC_CLIENT 35 | from tests.fixtures.node import NODE_HOST 36 | from tests.fixtures.node import PORT_REST 37 | from tests.fixtures.node import PORT_RPC 38 | from tests.fixtures.node import PORT_RPC_SPECULATIVE 39 | from tests.fixtures.node import PORT_SSE 40 | from tests.fixtures.vectors import cl_types as cl_types_vector 41 | from tests.fixtures.vectors import cl_values as cl_values_vector 42 | from tests.fixtures.vectors import crypto_checksums 43 | from tests.fixtures.vectors import crypto_hashes 44 | from tests.fixtures.vectors import crypto_key_pairs 45 | from tests.fixtures.vectors import crypto_key_pair_specs 46 | from tests.fixtures.vectors import crypto_signatures 47 | from tests.fixtures.vectors import deploys_1 48 | from tests.fixtures.vectors import test_account_key 49 | from tests.fixtures.vectors import test_bytes 50 | from tests.fixtures.vectors import test_signature 51 | -------------------------------------------------------------------------------- /tests/fixtures/accounts.py: -------------------------------------------------------------------------------- 1 | import operator 2 | import os 3 | import pathlib 4 | 5 | import pytest 6 | 7 | import pycspr 8 | 9 | 10 | _PATH_TO_ASSETS = pathlib.Path(os.path.dirname(__file__)).parent / "assets" 11 | _PATH_TO_ACCOUNTS = _PATH_TO_ASSETS / "accounts" 12 | _PATH_TO_CCTL_ASSETS = pathlib.Path(os.getenv("CCTL")) / "assets" 13 | 14 | 15 | @pytest.fixture(scope="session") 16 | def a_test_account(crypto_key_pairs) -> pycspr.types.crypto.PrivateKey: 17 | """Returns a test account key. 18 | 19 | """ 20 | algo, pbk, pvk = operator.itemgetter("algo", "pbk", "pvk")(crypto_key_pairs[0]) 21 | 22 | return pycspr.factory.create_private_key(algo, pvk, pbk) 23 | 24 | 25 | @pytest.fixture(scope="session") 26 | def test_account_1(): 27 | """Returns test user account information. 28 | 29 | """ 30 | path = _PATH_TO_ACCOUNTS / "account-1" / "secret_key.pem" 31 | (pvk, pbk) = pycspr.get_key_pair_from_pem_file(path) 32 | 33 | return pycspr.types.crypto.PrivateKey( 34 | pbk=pbk, 35 | pvk=pvk, 36 | algo=pycspr.KeyAlgorithm.ED25519 37 | ) 38 | 39 | 40 | @pytest.fixture(scope="session") 41 | def test_account_2(): 42 | """Returns test user account information. 43 | 44 | """ 45 | path = _PATH_TO_ACCOUNTS / "account-2" / "secret_key.pem" 46 | (pvk, pbk) = pycspr.get_key_pair_from_pem_file(path) 47 | 48 | return pycspr.types.crypto.PrivateKey( 49 | pbk=pbk, 50 | pvk=pvk, 51 | algo=pycspr.KeyAlgorithm.SECP256K1 52 | ) 53 | 54 | 55 | @pytest.fixture(scope="session") 56 | def cp1(): 57 | """Returns counter-party 1 test account key. 58 | 59 | """ 60 | return get_account_of_cctl_faucet() 61 | 62 | 63 | @pytest.fixture(scope="session") 64 | def cp2(): 65 | """Returns counter-party 2 test account key. 66 | 67 | """ 68 | return get_account_of_cctl_user(1) 69 | 70 | 71 | @pytest.fixture(scope="session") 72 | def account_key() -> bytes: 73 | """Returns a test CCTL account key. 74 | 75 | """ 76 | path = _PATH_TO_CCTL_ASSETS / "users" / "user-1" / "public_key_hex" 77 | 78 | with open(path) as fstream: 79 | return bytes.fromhex(fstream.read()) 80 | 81 | 82 | @pytest.fixture(scope="session") 83 | def account_hash(account_key: bytes) -> bytes: 84 | """Returns a test CCTL account key. 85 | 86 | """ 87 | return pycspr.get_account_hash(account_key) 88 | 89 | 90 | def get_account_of_cctl_faucet(): 91 | """Returns account information related to CCTL faucet. 92 | 93 | """ 94 | path = _PATH_TO_CCTL_ASSETS / "faucet" / "secret_key.pem" 95 | 96 | return pycspr.parse_private_key(path, pycspr.KeyAlgorithm.ED25519) 97 | 98 | 99 | def get_account_of_cctl_user(user_id: int): 100 | """Returns account information related to CCTL user 1. 101 | 102 | """ 103 | path = _PATH_TO_CCTL_ASSETS / "users" / f"user-{user_id}" / "secret_key.pem" 104 | 105 | return pycspr.parse_private_key(path, pycspr.KeyAlgorithm.ED25519) 106 | 107 | 108 | def create_account(): 109 | """Returns a test account. 110 | 111 | """ 112 | algo = pycspr.DEFAULT_KEY_ALGO 113 | pvk, pbk = pycspr.get_key_pair(algo) 114 | 115 | return pycspr.factory.create_private_key(algo, pvk, pbk) 116 | -------------------------------------------------------------------------------- /tests/fixtures/chain.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from pycspr import NodeRpcClient 4 | from pycspr.types.crypto import DigestBytes 5 | from pycspr.types.node import AccountKey 6 | from pycspr.types.node import Block 7 | from pycspr.types.node import GlobalStateID 8 | from pycspr.types.node import GlobalStateIDType 9 | from pycspr.types.node import StateRootHash 10 | from pycspr.types.node import URef 11 | 12 | 13 | @pytest.fixture(scope="session") 14 | async def account_main_purse_uref( 15 | RPC_CLIENT: NodeRpcClient, 16 | account_key: AccountKey 17 | ) -> URef: 18 | """Returns an on-chain account's main purse unforgeable reference. 19 | 20 | """ 21 | return await RPC_CLIENT.get_account_main_purse_uref(account_key) 22 | 23 | 24 | @pytest.fixture(scope="session") 25 | async def block(RPC_CLIENT: NodeRpcClient) -> Block: 26 | """Returns most recent block. 27 | 28 | """ 29 | return await RPC_CLIENT.get_block() 30 | 31 | 32 | @pytest.fixture(scope="session") 33 | async def block_hash(block: Block) -> DigestBytes: 34 | """Returns hash of most recent block. 35 | 36 | """ 37 | return block.hash 38 | 39 | 40 | @pytest.fixture(scope="session") 41 | def global_state_id(state_root_hash: StateRootHash) -> GlobalStateID: 42 | """Returns current state root hash. 43 | 44 | """ 45 | return GlobalStateID(state_root_hash, GlobalStateIDType.STATE_ROOT_HASH) 46 | 47 | 48 | @pytest.fixture(scope="session") 49 | async def state_root_hash(RPC_CLIENT: NodeRpcClient) -> StateRootHash: 50 | """Returns current state root hash. 51 | 52 | """ 53 | return await RPC_CLIENT.get_state_root_hash() 54 | 55 | 56 | @pytest.fixture(scope="session") 57 | async def switch_block(RPC_CLIENT: NodeRpcClient) -> dict: 58 | """Returns hash of most recent switch block. 59 | 60 | """ 61 | return await RPC_CLIENT.get_block_at_era_switch() 62 | 63 | 64 | @pytest.fixture(scope="session") 65 | async def switch_block_hash(switch_block: Block) -> DigestBytes: 66 | """Returns hash of most recent switch block. 67 | 68 | """ 69 | return switch_block.hash 70 | -------------------------------------------------------------------------------- /tests/fixtures/contracts.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pathlib 3 | 4 | import pytest 5 | 6 | 7 | _PATH_TO_CCTL_ASSETS = pathlib.Path(os.getenv("CCTL")) / "assets" 8 | 9 | 10 | @pytest.fixture(scope="session") 11 | def path_to_wasm_auction_bid() -> str: 12 | return _PATH_TO_CCTL_ASSETS / "bin" / "add_bid.wasm" 13 | 14 | 15 | @pytest.fixture(scope="session") 16 | def path_to_wasm_auction_bid_withdrawal() -> str: 17 | return _PATH_TO_CCTL_ASSETS / "bin" / "withdraw_bid.wasm" 18 | 19 | 20 | @pytest.fixture(scope="session") 21 | def path_to_wasm_delegate() -> str: 22 | return _PATH_TO_CCTL_ASSETS / "bin" / "delegate.wasm" 23 | 24 | 25 | @pytest.fixture(scope="session") 26 | def path_to_wasm_delegate_withdrawal() -> str: 27 | return _PATH_TO_CCTL_ASSETS / "bin" / "undelegate.wasm" 28 | -------------------------------------------------------------------------------- /tests/fixtures/deploys.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import random 3 | 4 | import pytest 5 | 6 | import pycspr 7 | from pycspr.types.node import Deploy 8 | from pycspr.types.node import DeployParameters 9 | from pycspr.types.node import DeployBody 10 | from pycspr.types.node import DeployHeader 11 | from pycspr.types.node import DeployApproval 12 | from tests.fixtures.accounts import create_account 13 | 14 | 15 | _A_KNOWN_ISO_TIMESTAMP = "2021-06-28T15:55:25.335+00:00" 16 | _A_KNOWN_DEPLOY_TIMESTAMP = datetime.datetime.fromisoformat(_A_KNOWN_ISO_TIMESTAMP).timestamp() 17 | _A_KNOWN_DEPLOY_HUMANIZED_TTL = "2hr" 18 | 19 | 20 | @pytest.fixture(scope="session") 21 | def a_test_chain_id() -> str: 22 | return create_chain_id() 23 | 24 | 25 | @pytest.fixture(scope="session") 26 | def a_test_timestamp() -> int: 27 | return create_timestamp() 28 | 29 | 30 | @pytest.fixture(scope="session") 31 | def a_test_ttl_humanized() -> str: 32 | return create_deploy_ttl_humanized() 33 | 34 | 35 | @pytest.fixture(scope="session") 36 | def a_test_uref() -> str: 37 | return create_uref() 38 | 39 | 40 | @pytest.fixture(scope="function") 41 | def deploy_params(a_test_chain_id, a_test_ttl_humanized, cp1): 42 | return create_deploy_params( 43 | account=cp1, 44 | chain_id=a_test_chain_id, 45 | ttl_humanized=a_test_ttl_humanized 46 | ) 47 | 48 | 49 | @pytest.fixture(scope="function") 50 | def deploy_params_static(a_test_chain_id, test_account_1): 51 | return pycspr.create_deploy_parameters( 52 | account=test_account_1.to_public_key(), 53 | chain_name=a_test_chain_id, 54 | dependencies=[], 55 | gas_price=10, 56 | timestamp=_A_KNOWN_DEPLOY_TIMESTAMP, 57 | ttl=pycspr.create_deploy_ttl(_A_KNOWN_DEPLOY_HUMANIZED_TTL), 58 | ) 59 | 60 | 61 | @pytest.fixture(scope="function") 62 | def a_deploy(deploy_params, cp1, cp2): 63 | deploy = pycspr.create_transfer( 64 | deploy_params, 65 | amount=2500000000, 66 | correlation_id=1, 67 | target=cp2.to_public_key(), 68 | ) 69 | deploy.set_approval(pycspr.create_deploy_approval(deploy, cp1)) 70 | 71 | return deploy 72 | 73 | 74 | def create_chain_id() -> str: 75 | return "cspr-dev-cctl" 76 | 77 | 78 | def create_deploy() -> Deploy: 79 | return create_transfer() 80 | 81 | 82 | def create_transfer() -> Deploy: 83 | cp1 = create_account() 84 | cp2 = create_account() 85 | params = create_deploy_params(account=cp1) 86 | 87 | deploy = pycspr.create_transfer( 88 | params=params, 89 | amount=2500000000, 90 | correlation_id=1, 91 | target=cp2.to_public_key(), 92 | ) 93 | deploy.set_approval(pycspr.create_deploy_approval(deploy, cp1)) 94 | 95 | return deploy 96 | 97 | 98 | def create_deploy_approval() -> DeployApproval: 99 | return create_deploy().approvals[0] 100 | 101 | 102 | def create_deploy_body() -> DeployBody: 103 | return create_deploy().get_body() 104 | 105 | 106 | def create_deploy_header() -> DeployHeader: 107 | return create_deploy().header 108 | 109 | 110 | def create_deploy_params( 111 | account=None, 112 | chain_id=None, 113 | timestamp=None, 114 | ttl_humanized=None 115 | ) -> DeployParameters: 116 | account = account or create_account() 117 | chain_id = chain_id or create_chain_id() 118 | timestamp = timestamp or create_timestamp() 119 | ttl_humanized = ttl_humanized or create_deploy_ttl_humanized() 120 | 121 | return pycspr.create_deploy_parameters( 122 | account=account.to_public_key(), 123 | chain_name=chain_id, 124 | dependencies=[], 125 | gas_price=10, 126 | timestamp=timestamp or create_timestamp(), 127 | ttl=ttl_humanized 128 | ) 129 | 130 | 131 | def create_timestamp() -> str: 132 | return datetime.datetime.now(tz=datetime.timezone.utc).timestamp() 133 | 134 | 135 | def create_deploy_ttl_humanized() -> str: 136 | (unit, quantity) = random.choice(( 137 | ("ms", random.randint(1, 1000 * 60 * 60 * 2)), 138 | ("seconds", random.randint(1, 60)), 139 | ("minutes", random.randint(1, 60)), 140 | ("hours", random.randint(1, 2)), 141 | )) 142 | 143 | return f"{quantity}{unit}" 144 | 145 | 146 | def create_uref() -> str: 147 | return "uref-827d5984270fed5aaaf076e1801733414a307ed8c5d85cad8ebe6265ba887b3a-007" 148 | -------------------------------------------------------------------------------- /tests/fixtures/iterator_cl_types.py: -------------------------------------------------------------------------------- 1 | import typing 2 | 3 | from pycspr.types.cl import CLT_Type 4 | from pycspr.types.cl import CLT_TypeKey 5 | from pycspr.types.cl import CLT_Any 6 | from pycspr.types.cl import CLT_Bool 7 | from pycspr.types.cl import CLT_ByteArray 8 | from pycspr.types.cl import CLT_I32 9 | from pycspr.types.cl import CLT_I64 10 | from pycspr.types.cl import CLT_U8 11 | from pycspr.types.cl import CLT_U32 12 | from pycspr.types.cl import CLT_U64 13 | from pycspr.types.cl import CLT_U128 14 | from pycspr.types.cl import CLT_U256 15 | from pycspr.types.cl import CLT_U512 16 | from pycspr.types.cl import CLT_Key 17 | from pycspr.types.cl import CLT_List 18 | from pycspr.types.cl import CLT_Map 19 | from pycspr.types.cl import CLT_Option 20 | from pycspr.types.cl import CLT_PublicKey 21 | from pycspr.types.cl import CLT_Result 22 | from pycspr.types.cl import CLT_String 23 | from pycspr.types.cl import CLT_Tuple1 24 | from pycspr.types.cl import CLT_Tuple2 25 | from pycspr.types.cl import CLT_Tuple3 26 | from pycspr.types.cl import CLT_Unit 27 | from pycspr.types.cl import CLT_URef 28 | 29 | 30 | def yield_cl_types(fixtures: list) -> typing.Iterator[CLT_Type]: 31 | for fixture in fixtures: 32 | type_key = fixture["cl_type"] 33 | 34 | if type_key == CLT_TypeKey.ANY: 35 | yield CLT_Any() 36 | 37 | elif type_key == CLT_TypeKey.BOOL: 38 | yield CLT_Bool() 39 | 40 | elif type_key == CLT_TypeKey.BYTE_ARRAY: 41 | yield CLT_ByteArray(fixture["cl_type_size"]) 42 | 43 | elif type_key == CLT_TypeKey.I32: 44 | yield CLT_I32() 45 | 46 | elif type_key == CLT_TypeKey.I64: 47 | yield CLT_I64() 48 | 49 | elif type_key == CLT_TypeKey.KEY: 50 | yield CLT_Key() 51 | 52 | elif type_key == CLT_TypeKey.LIST: 53 | for inner_type in _get_inner_types(): 54 | yield CLT_List(inner_type) 55 | 56 | elif type_key == CLT_TypeKey.MAP: 57 | yield CLT_Map(CLT_String(), CLT_I32()) 58 | 59 | elif type_key == CLT_TypeKey.OPTION: 60 | for inner_type in _get_inner_types(): 61 | yield CLT_Option(inner_type) 62 | 63 | elif type_key == CLT_TypeKey.PUBLIC_KEY: 64 | yield CLT_PublicKey() 65 | 66 | elif type_key == CLT_TypeKey.RESULT: 67 | yield CLT_Result() 68 | 69 | elif type_key == CLT_TypeKey.STRING: 70 | yield CLT_String() 71 | 72 | elif type_key == CLT_TypeKey.TUPLE_1: 73 | yield CLT_Tuple1(CLT_U64()) 74 | 75 | elif type_key == CLT_TypeKey.TUPLE_2: 76 | yield CLT_Tuple2(CLT_U64(), CLT_U128()) 77 | 78 | elif type_key == CLT_TypeKey.TUPLE_3: 79 | yield CLT_Tuple3( 80 | CLT_U64(), CLT_U128(), CLT_U256() 81 | ) 82 | 83 | elif type_key == CLT_TypeKey.U8: 84 | yield CLT_U8() 85 | 86 | elif type_key == CLT_TypeKey.U32: 87 | yield CLT_U32() 88 | 89 | elif type_key == CLT_TypeKey.U64: 90 | yield CLT_U64() 91 | 92 | elif type_key == CLT_TypeKey.U128: 93 | yield CLT_U128() 94 | 95 | elif type_key == CLT_TypeKey.U256: 96 | yield CLT_U256() 97 | 98 | elif type_key == CLT_TypeKey.U512: 99 | yield CLT_U512() 100 | 101 | elif type_key == CLT_TypeKey.UNIT: 102 | yield CLT_Unit() 103 | 104 | elif type_key == CLT_TypeKey.UREF: 105 | yield CLT_URef() 106 | 107 | 108 | def _get_inner_types(): 109 | return [ 110 | CLT_Any(), 111 | CLT_Bool(), 112 | CLT_ByteArray(32), 113 | CLT_I32(), 114 | CLT_I64(), 115 | CLT_Key(), 116 | CLT_List(CLT_U64()), 117 | CLT_Map(CLT_String(), CLT_I32()), 118 | CLT_PublicKey(), 119 | CLT_Result(), 120 | CLT_String(), 121 | CLT_Tuple1(CLT_U64()), 122 | CLT_Tuple2(CLT_U64(), CLT_U128()), 123 | CLT_Tuple3(CLT_U64(), CLT_U128(), CLT_U256()), 124 | CLT_U8(), 125 | CLT_U32(), 126 | CLT_U64(), 127 | CLT_U128(), 128 | CLT_U256(), 129 | CLT_U512(), 130 | CLT_Unit(), 131 | CLT_URef(), 132 | ] 133 | -------------------------------------------------------------------------------- /tests/fixtures/iterator_cl_values.py: -------------------------------------------------------------------------------- 1 | import typing 2 | 3 | from pycspr.types.cl import CLT_TypeKey 4 | from pycspr.types.cl import CLT_U64 5 | from pycspr.types.cl import CLV_Value 6 | from pycspr.types.cl import CLV_Bool 7 | from pycspr.types.cl import CLV_ByteArray 8 | from pycspr.types.cl import CLV_I32 9 | from pycspr.types.cl import CLV_I64 10 | from pycspr.types.cl import CLV_U8 11 | from pycspr.types.cl import CLV_U32 12 | from pycspr.types.cl import CLV_U64 13 | from pycspr.types.cl import CLV_U128 14 | from pycspr.types.cl import CLV_U256 15 | from pycspr.types.cl import CLV_U512 16 | from pycspr.types.cl import CLV_Key 17 | from pycspr.types.cl import CLV_List 18 | from pycspr.types.cl import CLV_Map 19 | from pycspr.types.cl import CLV_Option 20 | from pycspr.types.cl import CLV_PublicKey 21 | from pycspr.types.cl import CLV_String 22 | from pycspr.types.cl import CLV_Tuple1 23 | from pycspr.types.cl import CLV_Tuple2 24 | from pycspr.types.cl import CLV_Tuple3 25 | from pycspr.types.cl import CLV_Unit 26 | from pycspr.types.cl import CLV_URef 27 | from pycspr.types.crypto import PublicKey 28 | 29 | 30 | def yield_cl_values(fixtures: list) -> typing.Iterator[CLV_Value]: 31 | for fixture in fixtures: 32 | type_key: str = fixture["cl_type"] 33 | value: str = fixture["value"] 34 | 35 | if type_key == CLT_TypeKey.ANY: 36 | continue 37 | 38 | elif type_key == CLT_TypeKey.BOOL: 39 | yield CLV_Bool(value) 40 | 41 | elif type_key == CLT_TypeKey.BYTE_ARRAY: 42 | yield CLV_ByteArray(bytes.fromhex(value)) 43 | 44 | elif type_key == CLT_TypeKey.I32: 45 | yield CLV_I32(value) 46 | 47 | elif type_key == CLT_TypeKey.I64: 48 | yield CLV_I64(value) 49 | 50 | elif type_key == CLT_TypeKey.KEY: 51 | yield CLV_Key.from_str(value) 52 | 53 | elif type_key == CLT_TypeKey.LIST: 54 | yield CLV_List([CLV_U64(i) for i in value]) 55 | 56 | elif type_key == CLT_TypeKey.MAP: 57 | yield CLV_Map( 58 | [(CLV_String(k), CLV_U64(v)) for k, v in value.items()] 59 | ) 60 | 61 | elif type_key == CLT_TypeKey.OPTION: 62 | yield CLV_Option(CLV_U64(value), CLT_U64()) 63 | 64 | elif type_key == CLT_TypeKey.PUBLIC_KEY: 65 | yield CLV_PublicKey.from_public_key( 66 | PublicKey.from_bytes(bytes.fromhex(value)) 67 | ) 68 | 69 | elif type_key == CLT_TypeKey.RESULT: 70 | continue 71 | 72 | elif type_key == CLT_TypeKey.STRING: 73 | yield CLV_String(value) 74 | 75 | elif type_key == CLT_TypeKey.TUPLE_1: 76 | yield CLV_Tuple1( 77 | CLV_U64(value[0]) 78 | ) 79 | 80 | elif type_key == CLT_TypeKey.TUPLE_2: 81 | yield CLV_Tuple2( 82 | CLV_U64(value[0]), 83 | CLV_U128(value[1]) 84 | ) 85 | 86 | elif type_key == CLT_TypeKey.TUPLE_3: 87 | yield CLV_Tuple3( 88 | CLV_U64(value[0]), 89 | CLV_U128(value[1]), 90 | CLV_U256(value[2]) 91 | ) 92 | 93 | elif type_key == CLT_TypeKey.U8: 94 | yield CLV_U8(value) 95 | 96 | elif type_key == CLT_TypeKey.U32: 97 | yield CLV_U32(value) 98 | 99 | elif type_key == CLT_TypeKey.U64: 100 | yield CLV_U64(value) 101 | 102 | elif type_key == CLT_TypeKey.U128: 103 | yield CLV_U128(value) 104 | 105 | elif type_key == CLT_TypeKey.U256: 106 | yield CLV_U256(value) 107 | 108 | elif type_key == CLT_TypeKey.U512: 109 | yield CLV_U512(value) 110 | 111 | elif type_key == CLT_TypeKey.UNIT: 112 | yield CLV_Unit() 113 | 114 | elif type_key == CLT_TypeKey.UREF: 115 | yield CLV_URef.from_str(value) 116 | -------------------------------------------------------------------------------- /tests/fixtures/iterator_deploy_entities.py: -------------------------------------------------------------------------------- 1 | import typing 2 | 3 | import pytest 4 | 5 | import pycspr 6 | from pycspr.types.cl import CLV_U64 7 | from pycspr.types.crypto import PublicKey 8 | from pycspr.types.crypto import Signature 9 | from pycspr.types.node import DeployApproval 10 | from pycspr.types.node import DeployArgument 11 | from pycspr.types.node import DeployOfModuleBytes 12 | from pycspr.types.node import DeployOfStoredContractByHash 13 | from pycspr.types.node import DeployOfStoredContractByHashVersioned 14 | from pycspr.types.node import DeployOfStoredContractByName 15 | from pycspr.types.node import DeployOfStoredContractByNameVersioned 16 | from pycspr.types.node import DeployOfTransfer 17 | from tests.fixtures.deploys import create_deploy 18 | from tests.fixtures.deploys import create_deploy_body 19 | from tests.fixtures.deploys import create_deploy_header 20 | 21 | 22 | @pytest.fixture(scope="session") 23 | def yield_entities( 24 | test_account_key: bytes, 25 | test_bytes: bytes, 26 | test_signature: bytes 27 | ) -> typing.Iterator[object]: 28 | def _inner(): 29 | yield create_deploy() 30 | yield _create_deploy_argument() 31 | yield _create_deploy_approval(test_account_key, test_signature) 32 | yield create_deploy_body() 33 | yield create_deploy_header() 34 | yield _create_module_bytes(test_bytes) 35 | yield _create_stored_contract_by_hash(test_bytes) 36 | yield _create_stored_contract_by_hash_versioned(test_bytes) 37 | yield _create_stored_contract_by_name() 38 | yield _create_stored_contract_by_name_versioned() 39 | yield _create_transfer(test_account_key) 40 | 41 | return _inner 42 | 43 | 44 | def _create_deploy_argument() -> DeployArgument: 45 | return DeployArgument("test-arg", CLV_U64(1000000)) 46 | 47 | 48 | def _create_deploy_argument_set() -> typing.List[DeployArgument]: 49 | return [ 50 | DeployArgument("test-arg-1", CLV_U64(1000001)), 51 | DeployArgument("test-arg-2", CLV_U64(1000002)), 52 | DeployArgument("test-arg-3", CLV_U64(1000003)), 53 | ] 54 | 55 | 56 | def _create_deploy_approval(account_key: bytes, signature: bytes) -> DeployApproval: 57 | return DeployApproval( 58 | signer=PublicKey.from_bytes(account_key), 59 | signature=Signature.from_bytes(signature) 60 | ) 61 | 62 | 63 | def _create_module_bytes(some_bytes: bytes) -> DeployOfModuleBytes: 64 | return DeployOfModuleBytes( 65 | args=_create_deploy_argument_set(), 66 | module_bytes=some_bytes 67 | ) 68 | 69 | 70 | def _create_stored_contract_by_hash(some_bytes: bytes) -> DeployOfStoredContractByHash: 71 | return DeployOfStoredContractByHash( 72 | args=_create_deploy_argument_set(), 73 | entry_point="an-entry-point", 74 | hash=some_bytes 75 | ) 76 | 77 | 78 | def _create_stored_contract_by_hash_versioned( 79 | some_bytes: bytes 80 | ) -> DeployOfStoredContractByHashVersioned: 81 | return DeployOfStoredContractByHashVersioned( 82 | args=_create_deploy_argument_set(), 83 | entry_point="an-entry-point", 84 | hash=some_bytes, 85 | version=123 86 | ) 87 | 88 | 89 | def _create_stored_contract_by_name() -> DeployOfStoredContractByName: 90 | return DeployOfStoredContractByName( 91 | args=_create_deploy_argument_set(), 92 | entry_point="an-entry-point", 93 | name="hello-dolly" 94 | ) 95 | 96 | 97 | def _create_stored_contract_by_name_versioned() -> DeployOfStoredContractByNameVersioned: 98 | return DeployOfStoredContractByNameVersioned( 99 | args=_create_deploy_argument_set(), 100 | entry_point="an-entry-point", 101 | name="hello-dolly", 102 | version=321 103 | ) 104 | 105 | 106 | def _create_transfer(account_key: bytes) -> DeployOfTransfer: 107 | return pycspr.factory.create_transfer_session( 108 | amount=int(1e14), 109 | correlation_id=123456, 110 | target=PublicKey.from_bytes(account_key), 111 | ) 112 | -------------------------------------------------------------------------------- /tests/fixtures/node.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | 5 | import pycspr 6 | 7 | 8 | @pytest.fixture(scope="session") 9 | def RPC_CLIENT(CONNECTION_RPC: pycspr.NodeRpcConnectionInfo) -> pycspr.NodeRpcClient: 10 | return pycspr.NodeRpcClient(CONNECTION_RPC) 11 | 12 | 13 | @pytest.fixture(scope="session") 14 | def SPECULATIVE_RPC_CLIENT( 15 | CONNECTION_RPC_SPECULATIVE: pycspr.NodeSpeculativeRpcConnectionInfo 16 | ) -> pycspr.NodeSpeculativeRpcClient: 17 | return pycspr.NodeSpeculativeRpcClient(CONNECTION_RPC_SPECULATIVE) 18 | 19 | 20 | @pytest.fixture(scope="session") 21 | def REST_CLIENT( 22 | CONNECTION_REST: pycspr.NodeRestConnectionInfo 23 | ) -> pycspr.NodeRestClient: 24 | return pycspr.NodeRestClient(CONNECTION_REST) 25 | 26 | 27 | @pytest.fixture(scope="session") 28 | def SSE_CLIENT(CONNECTION_SSE: pycspr.NodeSseConnectionInfo) -> pycspr.NodeSseClient: 29 | return pycspr.NodeSseClient(CONNECTION_SSE) 30 | 31 | 32 | @pytest.fixture(scope="session") 33 | def NODE_HOST() -> str: 34 | return os.getenv("PYCSPR_TEST_NODE_HOST", "localhost") 35 | 36 | 37 | @pytest.fixture(scope="session") 38 | def PORT_REST() -> int: 39 | return int(os.getenv("PYCSPR_TEST_NODE_PORT_REST", 14101)) 40 | 41 | 42 | @pytest.fixture(scope="session") 43 | def PORT_RPC() -> int: 44 | return int(os.getenv("PYCSPR_TEST_NODE_PORT_RPC", 11101)) 45 | 46 | 47 | @pytest.fixture(scope="session") 48 | def PORT_RPC_SPECULATIVE() -> int: 49 | return int(os.getenv("PYCSPR_TEST_NODE_PORT_RPC_SPECULATIVE", 25101)) 50 | 51 | 52 | @pytest.fixture(scope="session") 53 | def PORT_SSE() -> int: 54 | return int(os.getenv("PYCSPR_TEST_NODE_PORT_SSE", 18101)) 55 | 56 | 57 | @pytest.fixture(scope="session") 58 | def CONNECTION_REST(NODE_HOST: str, PORT_REST: int) -> pycspr.NodeRestConnectionInfo: 59 | return pycspr.NodeRestConnectionInfo(NODE_HOST, PORT_REST) 60 | 61 | 62 | @pytest.fixture(scope="session") 63 | def CONNECTION_RPC(NODE_HOST: str, PORT_RPC: int) -> pycspr.NodeRpcConnectionInfo: 64 | return pycspr.NodeRpcConnectionInfo(NODE_HOST, PORT_RPC) 65 | 66 | 67 | @pytest.fixture(scope="session") 68 | def CONNECTION_RPC_SPECULATIVE( 69 | NODE_HOST: str, 70 | PORT_RPC_SPECULATIVE: int 71 | ) -> pycspr.NodeSpeculativeRpcConnectionInfo: 72 | return pycspr.NodeSpeculativeRpcConnectionInfo(NODE_HOST, PORT_RPC_SPECULATIVE) 73 | 74 | 75 | @pytest.fixture(scope="session") 76 | def CONNECTION_SSE(NODE_HOST: str, PORT_SSE: int, PORT_RPC: int) -> pycspr.NodeSseConnectionInfo: 77 | return pycspr.NodeSseConnectionInfo(NODE_HOST, PORT_SSE, PORT_RPC) 78 | -------------------------------------------------------------------------------- /tests/fixtures/vectors.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | import pathlib 4 | import typing 5 | 6 | import pytest 7 | 8 | import pycspr 9 | from pycspr.types.cl import CLT_TypeKey 10 | from tests.fixtures.iterator_cl_types import yield_cl_types 11 | from tests.fixtures.iterator_cl_values import yield_cl_values 12 | 13 | 14 | _PATH_TO_ASSETS = pathlib.Path(os.path.dirname(__file__)).parent / "assets" 15 | _PATH_TO_VECTORS = _PATH_TO_ASSETS / "vectors" 16 | 17 | 18 | @pytest.fixture(scope="session") 19 | def cl_types() -> list: 20 | class _Accessor(): 21 | """Streamlines access to cl types vector. 22 | 23 | """ 24 | def __init__(self): 25 | self.fixtures = _read_vector("cl-types.json") 26 | for obj in self.fixtures: 27 | obj["cl_type"] = CLT_TypeKey[obj["cl_type"]] 28 | 29 | def __iter__(self): 30 | return yield_cl_types(self.fixtures) 31 | 32 | return _Accessor() 33 | 34 | 35 | @pytest.fixture(scope="session") 36 | def cl_values() -> list: 37 | class _Accessor(): 38 | """Streamlines access to cl values vector. 39 | 40 | """ 41 | def __init__(self): 42 | self.fixtures = _read_vector("cl-values.json") 43 | for obj in self.fixtures: 44 | obj["cl_type"] = CLT_TypeKey[obj["cl_type"]] 45 | 46 | def __iter__(self): 47 | return yield_cl_values(self.fixtures) 48 | 49 | return _Accessor() 50 | 51 | 52 | @pytest.fixture(scope="session") 53 | def crypto_checksums() -> list: 54 | data = _read_vector("crypto-checksums.json") 55 | for i in data: 56 | i["input"] = bytes.fromhex(i["input"]) 57 | 58 | return data 59 | 60 | 61 | @pytest.fixture(scope="session") 62 | def crypto_hashes() -> list: 63 | data = _read_vector("crypto-hashes.json") 64 | for i in data: 65 | for j in i["hashes"]: 66 | j["digest"] = bytes.fromhex(j["digest"]) 67 | 68 | return data 69 | 70 | 71 | @pytest.fixture(scope="session") 72 | def crypto_key_pairs() -> list: 73 | data = _read_vector("crypto-key-pairs.json") 74 | for i in data: 75 | i["pbk"] = bytes.fromhex(i["pbk"]) 76 | i["pvk"] = bytes.fromhex(i["pvk"]) 77 | i["accountKey"] = bytes.fromhex(i["accountKey"]) 78 | i["accountHash"] = bytes.fromhex(i["accountHash"]) 79 | 80 | return data 81 | 82 | 83 | @pytest.fixture(scope="session") 84 | def crypto_key_pair_specs() -> typing.Tuple[pycspr.KeyAlgorithm, int, int]: 85 | """Returns sets of specifications for key pair generation. 86 | 87 | """ 88 | return ( 89 | (pycspr.KeyAlgorithm.ED25519, 32, 32), 90 | (pycspr.KeyAlgorithm.SECP256K1, 32, 33), 91 | ) 92 | 93 | 94 | @pytest.fixture(scope="session") 95 | def crypto_signatures() -> list: 96 | data = _read_vector("crypto-signatures.json") 97 | for i in data: 98 | i["key"]["pbk"] = bytes.fromhex(i["key"]["pbk"]) 99 | i["key"]["pvk"] = bytes.fromhex(i["key"]["pvk"]) 100 | i["sig"] = bytes.fromhex(i["sig"]) 101 | 102 | return data 103 | 104 | 105 | @pytest.fixture(scope="session") 106 | def deploys_1() -> list: 107 | data = _read_vector("deploys-1.json") 108 | for i in data: 109 | i["bytes"]["payment"] = bytes.fromhex(i["bytes"]["payment"]) 110 | i["bytes"]["session"] = bytes.fromhex(i["bytes"]["session"]) 111 | i["hashes"]["body"] = bytes.fromhex(i["hashes"]["body"]) 112 | i["hashes"]["deploy"] = bytes.fromhex(i["hashes"]["deploy"]) 113 | i["session"]["target"] = bytes.fromhex(i["session"]["target"]) 114 | 115 | return data 116 | 117 | 118 | @pytest.fixture(scope="session") 119 | def test_bytes(test_account_key) -> bytes: 120 | return test_account_key[1:] 121 | 122 | 123 | @pytest.fixture(scope="session") 124 | def test_account_key() -> bytes: 125 | data = _read_vector("crypto-misc.json") 126 | 127 | return bytes.fromhex(data["account-key"]) 128 | 129 | 130 | @pytest.fixture(scope="session") 131 | def test_signature() -> bytes: 132 | data = _read_vector("crypto-misc.json") 133 | 134 | return bytes.fromhex(data["signature"]) 135 | 136 | 137 | def _read_vector(fname: str, parser: typing.Callable = json.load): 138 | with open(_PATH_TO_VECTORS / fname) as fstream: 139 | return fstream.read() if parser is None else parser(fstream) 140 | -------------------------------------------------------------------------------- /tests/test_api_rest.py: -------------------------------------------------------------------------------- 1 | from pycspr import NodeRestClient 2 | from pycspr.types.node import NodeStatus 3 | from pycspr.types.node import ValidatorChanges 4 | 5 | 6 | async def test_get_chainspec(REST_CLIENT: NodeRestClient): 7 | data = await REST_CLIENT.get_chainspec() 8 | assert isinstance(data, dict) 9 | assert len(data) == 3 10 | assert "chainspec_bytes" in data 11 | assert "maybe_genesis_accounts_bytes" in data 12 | assert "maybe_global_state_bytes" in data 13 | 14 | 15 | async def test_get_node_metrics(REST_CLIENT: NodeRestClient): 16 | data = await REST_CLIENT.get_node_metrics() 17 | assert isinstance(data, list) 18 | assert len(data) > 300 19 | 20 | 21 | async def test_get_node_metric(REST_CLIENT: NodeRestClient): 22 | data = await REST_CLIENT.get_node_metric("mem_deploy_gossiper") 23 | assert isinstance(data, list) 24 | assert len(data) == 1 25 | 26 | data = await REST_CLIENT.get_node_metric("xxxxxxxxxxxxxxxxxxxxx") 27 | assert isinstance(data, list) 28 | assert len(data) == 0 29 | 30 | 31 | async def test_get_node_status_1(REST_CLIENT: NodeRestClient): 32 | data: dict = await REST_CLIENT.get_node_status(decode=False) 33 | assert isinstance(data, dict) 34 | assert len(data) == 14 35 | 36 | 37 | async def test_get_node_status_2(REST_CLIENT: NodeRestClient): 38 | data: NodeStatus = await REST_CLIENT.get_node_status() 39 | assert isinstance(data, NodeStatus) 40 | 41 | 42 | async def test_get_node_rpc_schema(REST_CLIENT: NodeRestClient): 43 | data = await REST_CLIENT.get_node_rpc_schema() 44 | assert isinstance(data, dict) 45 | assert len(data) == 5 46 | 47 | 48 | async def test_get_validator_changes_1(REST_CLIENT: NodeRestClient): 49 | data = await REST_CLIENT.get_validator_changes(decode=False) 50 | assert isinstance(data, list) 51 | for item in data: 52 | assert isinstance(item, dict) 53 | 54 | 55 | async def test_get_validator_changes_2(REST_CLIENT: NodeRestClient): 56 | data = await REST_CLIENT.get_validator_changes() 57 | assert isinstance(data, list) 58 | for item in data: 59 | assert isinstance(item, ValidatorChanges) 60 | -------------------------------------------------------------------------------- /tests/test_api_rpc_1.py: -------------------------------------------------------------------------------- 1 | from pycspr import NodeRpcClient 2 | from pycspr.api.constants import RPC_ENDPOINTS 3 | 4 | 5 | async def test_get_rpc_schema(RPC_CLIENT: NodeRpcClient): 6 | data = await RPC_CLIENT.get_rpc_schema() 7 | 8 | assert isinstance(data, dict) 9 | assert "openrpc" in data 10 | 11 | 12 | async def test_get_rpc_endpoints(RPC_CLIENT: NodeRpcClient): 13 | data = await RPC_CLIENT.get_rpc_endpoints() 14 | 15 | assert isinstance(data, list) 16 | assert data == sorted(RPC_ENDPOINTS) 17 | 18 | 19 | async def test_get_rpc_endpoint(RPC_CLIENT: NodeRpcClient): 20 | for endpoint in RPC_ENDPOINTS: 21 | data = await RPC_CLIENT.get_rpc_endpoint(endpoint) 22 | assert isinstance(data, dict) 23 | 24 | 25 | async def test_get_chainspec(RPC_CLIENT: NodeRpcClient): 26 | data = await RPC_CLIENT.get_chainspec() 27 | 28 | assert isinstance(data, dict) 29 | for field in { 30 | "chainspec_bytes", 31 | "maybe_genesis_accounts_bytes", 32 | "maybe_global_state_bytes", 33 | }: 34 | assert field in data 35 | -------------------------------------------------------------------------------- /tests/test_api_rpc_2.py: -------------------------------------------------------------------------------- 1 | import typing 2 | 3 | from pycspr import NodeRpcClient 4 | from pycspr.types.node import ValidatorChanges 5 | from pycspr.types.node import NodePeer 6 | from pycspr.types.node import NodeStatus 7 | 8 | 9 | async def test_get_node_peers_1(RPC_CLIENT: NodeRpcClient): 10 | data: typing.List[dict] = await RPC_CLIENT.get_node_peers(decode=False) 11 | assert isinstance(data, list) 12 | for item in data: 13 | assert isinstance(item, dict) 14 | 15 | 16 | async def test_get_node_peers_2(RPC_CLIENT: NodeRpcClient): 17 | data: typing.List[NodePeer] = await RPC_CLIENT.get_node_peers(decode=True) 18 | assert isinstance(data, list) 19 | for item in data: 20 | assert isinstance(item, NodePeer) 21 | 22 | 23 | async def test_get_node_status_1(RPC_CLIENT: NodeRpcClient): 24 | data: dict = await RPC_CLIENT.get_node_status(decode=False) 25 | assert isinstance(data, dict) 26 | 27 | 28 | async def test_get_node_status_2(RPC_CLIENT: NodeRpcClient): 29 | data: NodeStatus = await RPC_CLIENT.get_node_status(decode=True) 30 | assert isinstance(data, NodeStatus) 31 | assert isinstance(data.peers, list) 32 | for item in data.peers: 33 | assert isinstance(item, NodePeer) 34 | 35 | 36 | async def test_get_validator_changes(RPC_CLIENT: NodeRpcClient): 37 | data = await RPC_CLIENT.get_validator_changes() 38 | 39 | assert isinstance(data, list) 40 | for item in data: 41 | assert isinstance(item, ValidatorChanges) 42 | -------------------------------------------------------------------------------- /tests/test_api_rpc_3.py: -------------------------------------------------------------------------------- 1 | from pycspr import NodeRpcClient 2 | from pycspr.types.node import AuctionState 3 | from pycspr.types.node import EraSummary 4 | 5 | 6 | async def test_get_auction_info_1(RPC_CLIENT: NodeRpcClient): 7 | data = await RPC_CLIENT.get_auction_info() 8 | assert isinstance(data, AuctionState) 9 | 10 | 11 | async def test_get_auction_info_2(RPC_CLIENT: NodeRpcClient): 12 | data = await RPC_CLIENT.get_auction_info(decode=False) 13 | assert isinstance(data, dict) 14 | 15 | 16 | async def test_get_era_info_by_switch_block_1(RPC_CLIENT: NodeRpcClient, switch_block_hash: str): 17 | data = await RPC_CLIENT.get_era_info_by_switch_block(switch_block_hash) 18 | assert isinstance(data, EraSummary) 19 | 20 | 21 | async def test_get_era_info_by_switch_block_2(RPC_CLIENT: NodeRpcClient, switch_block_hash: str): 22 | data = await RPC_CLIENT.get_era_info_by_switch_block(switch_block_hash, decode=False) 23 | assert isinstance(data, dict) 24 | 25 | 26 | async def test_get_era_summary_1(RPC_CLIENT: NodeRpcClient, block_hash: str): 27 | data = await RPC_CLIENT.get_era_summary(block_hash) 28 | assert isinstance(data, EraSummary) 29 | 30 | 31 | async def test_get_era_summary_2(RPC_CLIENT: NodeRpcClient, block_hash: str): 32 | data = await RPC_CLIENT.get_era_summary(block_hash, decode=False) 33 | assert isinstance(data, dict) 34 | -------------------------------------------------------------------------------- /tests/test_api_rpc_4.py: -------------------------------------------------------------------------------- 1 | from pycspr import NodeRpcClient 2 | from pycspr.types.node import StateRootHash 3 | from pycspr.types.node import Address 4 | 5 | 6 | async def test_get_state_item( 7 | RPC_CLIENT: NodeRpcClient, 8 | account_hash: Address, 9 | state_root_hash: StateRootHash 10 | ): 11 | account_hash = f"account-hash-{account_hash.hex()}" 12 | data = await RPC_CLIENT.get_state_item(account_hash, [], state_root_hash) 13 | 14 | assert isinstance(data, dict) 15 | assert "Account" in data 16 | -------------------------------------------------------------------------------- /tests/test_api_rpc_5.py: -------------------------------------------------------------------------------- 1 | from pycspr import NodeRpcClient 2 | from pycspr.types.node import GlobalStateID 3 | from pycspr.types.node import PurseID 4 | from pycspr.types.node import PurseIDType 5 | from pycspr.types.node import URef 6 | 7 | 8 | async def test_get_account_balance_under_purse_uref( 9 | RPC_CLIENT: NodeRpcClient, 10 | account_main_purse_uref: URef, 11 | global_state_id: GlobalStateID 12 | ): 13 | purse_id = PurseID(account_main_purse_uref, PurseIDType.UREF) 14 | data = await RPC_CLIENT.get_account_balance(purse_id, global_state_id) 15 | 16 | assert isinstance(data, int) 17 | assert data >= 0 18 | 19 | 20 | async def test_get_account_balance_under_account_hash( 21 | RPC_CLIENT: NodeRpcClient, 22 | account_hash: bytes, 23 | global_state_id: GlobalStateID 24 | ): 25 | purse_id = PurseID(account_hash, PurseIDType.ACCOUNT_HASH) 26 | data = await RPC_CLIENT.get_account_balance(purse_id, global_state_id) 27 | 28 | assert isinstance(data, int) 29 | assert data >= 0 30 | 31 | 32 | async def test_get_account_balance_under_account_key( 33 | RPC_CLIENT: NodeRpcClient, 34 | account_key: bytes, 35 | global_state_id: GlobalStateID 36 | ): 37 | purse_id = PurseID(account_key, PurseIDType.PUBLIC_KEY) 38 | data = await RPC_CLIENT.get_account_balance(purse_id, global_state_id) 39 | 40 | assert isinstance(data, int) 41 | assert data >= 0 42 | -------------------------------------------------------------------------------- /tests/test_api_rpc_6.py: -------------------------------------------------------------------------------- 1 | from pycspr import NodeRpcClient 2 | from pycspr.types.node import AccountInfo 3 | from pycspr.types.node import URef 4 | from pycspr.types.node import URefAccessRights 5 | 6 | 7 | async def test_get_state_root(RPC_CLIENT: NodeRpcClient): 8 | def _assert(response): 9 | assert isinstance(response, bytes) 10 | assert len(response) == 32 11 | 12 | for block_id in (None, 1): 13 | _assert(await RPC_CLIENT.get_state_root_hash(block_id)) 14 | 15 | 16 | async def test_get_account_info_1(RPC_CLIENT: NodeRpcClient, account_key: bytes): 17 | data: dict = await RPC_CLIENT.get_account_info(account_key, decode=False) 18 | assert isinstance(data, dict) 19 | 20 | 21 | async def test_get_account_info_2(RPC_CLIENT: NodeRpcClient, account_key: bytes): 22 | data: AccountInfo = await RPC_CLIENT.get_account_info(account_key, decode=True) 23 | assert isinstance(data, AccountInfo) 24 | 25 | 26 | async def test_get_account_main_purse_uref(RPC_CLIENT: NodeRpcClient, account_key: bytes): 27 | data: URef = await RPC_CLIENT.get_account_main_purse_uref(account_key) 28 | assert isinstance(data, URef) 29 | assert len(data.address) == 32 30 | assert data.access_rights == URefAccessRights.READ_ADD_WRITE 31 | -------------------------------------------------------------------------------- /tests/test_api_rpc_7.py: -------------------------------------------------------------------------------- 1 | from pycspr import NodeRpcClient 2 | from pycspr.types.node import Deploy 3 | 4 | 5 | async def test_send_deploy(RPC_CLIENT: NodeRpcClient, a_deploy: Deploy): 6 | data = await RPC_CLIENT.send_deploy(a_deploy) 7 | assert isinstance(data, str) 8 | assert a_deploy.hash == bytes.fromhex(data) 9 | -------------------------------------------------------------------------------- /tests/test_api_rpc_8.py: -------------------------------------------------------------------------------- 1 | from pycspr import NodeRpcClient 2 | from pycspr.types.node import Block 3 | 4 | 5 | async def test_get_block_1(RPC_CLIENT: NodeRpcClient): 6 | data: dict = await RPC_CLIENT.get_block(decode=False) 7 | assert isinstance(data, dict) 8 | 9 | 10 | async def test_get_block_2(RPC_CLIENT: NodeRpcClient): 11 | data: Block = await RPC_CLIENT.get_block(decode=True) 12 | assert isinstance(data, Block) 13 | -------------------------------------------------------------------------------- /tests/test_api_speculative_rpc.py: -------------------------------------------------------------------------------- 1 | from pycspr import NodeSpeculativeRpcClient 2 | from pycspr.types.node import Deploy 3 | 4 | 5 | def test_pre_requisites(SPECULATIVE_RPC_CLIENT: NodeSpeculativeRpcClient, a_deploy: Deploy): 6 | assert SPECULATIVE_RPC_CLIENT is not None 7 | assert isinstance(a_deploy, Deploy) 8 | 9 | 10 | async def test_deploy(SPECULATIVE_RPC_CLIENT: NodeSpeculativeRpcClient, a_deploy: Deploy): 11 | data: dict = await SPECULATIVE_RPC_CLIENT.speculative_exec(a_deploy) 12 | assert isinstance(data, dict) 13 | assert "Success" in data 14 | -------------------------------------------------------------------------------- /tests/test_api_sse.py: -------------------------------------------------------------------------------- 1 | from pycspr import NodeEventChannel 2 | from pycspr import NodeEventInfo 3 | from pycspr import NodeEventType 4 | from pycspr import NodeRpcClient 5 | from pycspr import NodeSseClient 6 | 7 | 8 | async def test_await_n_blocks(SSE_CLIENT: NodeSseClient, RPC_CLIENT: NodeRpcClient) -> None: 9 | offset = 2 10 | current = await RPC_CLIENT.get_block_height() 11 | future = current + offset 12 | await SSE_CLIENT.await_n_blocks(offset) 13 | assert await RPC_CLIENT.get_block_height() == future 14 | 15 | 16 | async def test_await_n_eras(SSE_CLIENT: NodeSseClient, RPC_CLIENT: NodeRpcClient) -> None: 17 | offset = 1 18 | current = await RPC_CLIENT.get_era_height() 19 | future = current + offset 20 | await SSE_CLIENT.await_n_eras(offset) 21 | assert await RPC_CLIENT.get_era_height() == future 22 | 23 | 24 | async def test_await_n_events(SSE_CLIENT: NodeSseClient, RPC_CLIENT: NodeRpcClient) -> None: 25 | offset = 1 26 | current = await RPC_CLIENT.get_block_height() 27 | future = current + offset 28 | await SSE_CLIENT.await_n_events(offset, NodeEventChannel.main, NodeEventType.BlockAdded) 29 | assert await RPC_CLIENT.get_block_height() == future 30 | 31 | 32 | async def test_await_until_block_n(SSE_CLIENT: NodeSseClient, RPC_CLIENT: NodeRpcClient) -> None: 33 | offset = 2 34 | future = await RPC_CLIENT.get_block_height() + offset 35 | await SSE_CLIENT.await_until_block_n(future) 36 | assert await RPC_CLIENT.get_block_height() == future 37 | 38 | 39 | async def test_await_until_era_n(SSE_CLIENT: NodeSseClient, RPC_CLIENT: NodeRpcClient) -> None: 40 | offset = 1 41 | future = await RPC_CLIENT.get_era_height() + offset 42 | await SSE_CLIENT.await_until_era_n(future) 43 | assert await RPC_CLIENT.get_era_height() == future 44 | 45 | 46 | async def test_get_events(SSE_CLIENT: NodeSseClient, RPC_CLIENT: NodeRpcClient) -> None: 47 | def ecallback(einfo: NodeEventInfo): 48 | assert isinstance(einfo, NodeEventInfo) 49 | raise InterruptedError() 50 | 51 | try: 52 | SSE_CLIENT.get_events(ecallback, NodeEventChannel.main, NodeEventType.BlockAdded) 53 | except InterruptedError: 54 | pass 55 | else: 56 | raise ValueError("Event capture error") 57 | 58 | 59 | async def test_yield_events(SSE_CLIENT: NodeSseClient, RPC_CLIENT: NodeRpcClient) -> None: 60 | for einfo in SSE_CLIENT.yield_events(NodeEventChannel.main, NodeEventType.BlockAdded): 61 | assert isinstance(einfo, NodeEventInfo) 62 | break 63 | -------------------------------------------------------------------------------- /tests/test_crypto_1.py: -------------------------------------------------------------------------------- 1 | import operator 2 | 3 | import pycspr 4 | 5 | 6 | def test_get_hash(crypto_hashes): 7 | getter_1 = operator.itemgetter("data", "hashes") 8 | getter_2 = operator.itemgetter("algo", "digest") 9 | for data, hashes in [getter_1(i) for i in crypto_hashes]: 10 | for algo, digest in [getter_2(j) for j in hashes]: 11 | algo = pycspr.HashAlgorithm[algo] 12 | assert digest == pycspr.get_hash(data.encode("utf-8"), 32, algo) 13 | 14 | 15 | def test_get_account(crypto_key_pairs): 16 | getter = operator.itemgetter("algo", "pbk", "accountKey", "accountHash") 17 | for algo, pbk, account_key, accountHash in [getter(i) for i in crypto_key_pairs]: 18 | algo = pycspr.KeyAlgorithm[algo] 19 | assert account_key == pycspr.get_account_key(algo, pbk) 20 | print(pycspr.get_account_hash(account_key).hex()) 21 | assert pycspr.get_account_hash(account_key) == accountHash 22 | -------------------------------------------------------------------------------- /tests/test_crypto_2.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import pathlib 3 | import secrets 4 | 5 | import pycspr 6 | 7 | 8 | def test_that_a_key_pair_can_be_generated(crypto_key_pair_specs): 9 | for key_algo, pvk_length, pbk_length in crypto_key_pair_specs: 10 | pvk, pbk = pycspr.get_key_pair(key_algo) 11 | assert len(pvk) == pvk_length 12 | assert len(pbk) == pbk_length 13 | 14 | 15 | def test_that_a_key_pair_can_be_deserialized_from_base64(crypto_key_pair_specs): 16 | for key_algo, _, _ in crypto_key_pair_specs: 17 | pvk, pbk = pycspr.get_key_pair(key_algo) 18 | pvk_b64 = base64.b64encode(pvk) 19 | assert (pvk, pbk) == pycspr.get_key_pair_from_base64(pvk_b64, key_algo) 20 | 21 | 22 | def test_that_a_key_pair_can_be_deserialized_from_bytes(crypto_key_pair_specs): 23 | for key_algo, _, _ in crypto_key_pair_specs: 24 | pvk, pbk = pycspr.get_key_pair(key_algo) 25 | pvk_pem = pycspr.get_pvk_pem_from_bytes(pvk, key_algo) 26 | assert isinstance(pvk_pem, bytes) 27 | 28 | 29 | def test_that_a_key_pair_can_be_deserialized_from_pem_file(crypto_key_pair_specs): 30 | for key_algo, _, _ in crypto_key_pair_specs: 31 | pvk, pbk = pycspr.get_key_pair(key_algo) 32 | path_to_pvk_pem_file = pycspr.get_pvk_pem_file_from_bytes(pvk, key_algo) 33 | assert pathlib.Path(path_to_pvk_pem_file).is_file() 34 | assert (pvk, pbk) == \ 35 | pycspr.get_key_pair_from_pem_file(path_to_pvk_pem_file, key_algo) 36 | 37 | 38 | def test_that_a_key_pair_can_be_deserialized_from_a_seed(crypto_key_pair_specs): 39 | for key_algo, pvk_length, pbk_length in crypto_key_pair_specs: 40 | seed = secrets.token_bytes(32) 41 | pvk, pbk = pycspr.get_key_pair_from_seed(seed, key_algo) 42 | assert len(pvk) == pvk_length 43 | assert len(pbk) == pbk_length 44 | 45 | 46 | def test_that_a_public_key_is_deserializable_from_a_private_key_as_byte_array(crypto_key_pairs): 47 | for fixture in crypto_key_pairs: 48 | algo = pycspr.KeyAlgorithm[fixture["algo"]] 49 | _, pbk = pycspr.get_key_pair_from_bytes(fixture["pvk"], algo) 50 | assert fixture["pbk"] == pbk 51 | 52 | 53 | def test_that_a_public_key_is_deserializable_from_a_private_key_as_base64(crypto_key_pairs): 54 | for fixture in crypto_key_pairs: 55 | algo = pycspr.KeyAlgorithm[fixture["algo"]] 56 | _, pbk = pycspr.get_key_pair_from_base64(base64.b64encode(fixture["pvk"]), algo) 57 | assert fixture["pbk"] == pbk 58 | 59 | 60 | def test_that_a_public_key_is_deserializable_from_a_private_key_as_hex(crypto_key_pairs): 61 | for fixture in crypto_key_pairs: 62 | algo = pycspr.KeyAlgorithm[fixture["algo"]] 63 | _, pbk = pycspr.get_key_pair_from_hex_string(fixture["pvk"].hex(), algo) 64 | assert fixture["pbk"] == pbk 65 | -------------------------------------------------------------------------------- /tests/test_crypto_3.py: -------------------------------------------------------------------------------- 1 | import pycspr 2 | 3 | 4 | def test_that_a_signature_is_generatable(crypto_signatures): 5 | for fixture in crypto_signatures: 6 | algo = pycspr.KeyAlgorithm[fixture["key"]["algo"]] 7 | data = fixture["data"].encode("utf-8") 8 | pvk = fixture["key"]["pvk"] 9 | assert fixture["sig"] == \ 10 | pycspr.get_signature(data, algo, pvk), \ 11 | pycspr.get_signature(data, algo, pvk).hex() 12 | 13 | 14 | def test_that_a_signature_is_verifiable(crypto_signatures): 15 | for fixture in crypto_signatures: 16 | algo = pycspr.KeyAlgorithm[fixture["key"]["algo"]] 17 | data = fixture["data"].encode("utf-8") 18 | sig = fixture["sig"] 19 | pbk = fixture["key"]["pbk"] 20 | assert pycspr.is_signature_valid(data, algo, sig, pbk) 21 | -------------------------------------------------------------------------------- /tests/test_crypto_4.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | 3 | from pycspr.crypto import checksummer 4 | 5 | 6 | def test_checksum_encoding(crypto_checksums): 7 | assert inspect.isfunction(checksummer.encode_bytes) 8 | for row in crypto_checksums: 9 | assert row["checksum"] == checksummer.encode_bytes(row["input"]) 10 | 11 | 12 | def test_checksum_decoding(crypto_checksums): 13 | assert inspect.isfunction(checksummer.decode_bytes) 14 | for row in crypto_checksums: 15 | encoded: str = checksummer.encode_bytes(row["input"]) 16 | assert row["input"] == checksummer.decode_bytes(encoded) 17 | -------------------------------------------------------------------------------- /tests/test_deploy_lifecycle.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pathlib 3 | import tempfile 4 | 5 | import pycspr 6 | 7 | 8 | def test_that_deploy_is_unapproved_when_instantiated(deploy_params, cp2): 9 | deploy = _create_deploy(deploy_params, cp2) 10 | assert len(deploy.approvals) == 0 11 | 12 | 13 | def test_that_deploy_can_be_approved(deploy_params, cp1, cp2): 14 | deploy = _create_deploy(deploy_params, cp2) 15 | deploy.set_approval(pycspr.factory.create_deploy_approval(deploy, cp1)) 16 | assert len(deploy.approvals) == 1 17 | assert deploy.approvals[0].signer == cp1.to_public_key() 18 | 19 | 20 | def test_that_deploy_can_be_approved_by_multiple_parties(deploy_params, cp1, cp2): 21 | deploy = _create_deploy(deploy_params, cp2) 22 | deploy.set_approval(pycspr.factory.create_deploy_approval(deploy, cp1)) 23 | deploy.set_approval(pycspr.factory.create_deploy_approval(deploy, cp2)) 24 | assert len(deploy.approvals) == 2 25 | 26 | 27 | def test_that_deploy_approvals_are_deduplicated(deploy_params, cp1, cp2): 28 | deploy = _create_deploy(deploy_params, cp2) 29 | for _ in range(10): 30 | deploy.set_approval(pycspr.factory.create_deploy_approval(deploy, cp1)) 31 | deploy.set_approval(pycspr.factory.create_deploy_approval(deploy, cp2)) 32 | assert len(deploy.approvals) == 2 33 | 34 | 35 | def test_that_a_deploy_can_be_written_to_fs(deploy_params, cp1, cp2): 36 | deploy = _create_deploy(deploy_params, cp2) 37 | with tempfile.TemporaryFile() as fp: 38 | fpath = str(fp) 39 | pycspr.write_deploy(deploy, fpath) 40 | assert os.path.exists(fpath) 41 | os.remove(fpath) 42 | 43 | 44 | def test_that_a_deploy_can_be_written_to_and_read_from_fs(deploy_params, cp1, cp2): 45 | deploy_1 = _create_deploy(deploy_params, cp2) 46 | with tempfile.TemporaryFile() as fp: 47 | fpath = str(fp) 48 | pycspr.write_deploy(deploy_1, fpath) 49 | deploy_2 = pycspr.read_deploy(fpath) 50 | assert isinstance(deploy_2, type(deploy_1)) 51 | assert pycspr.serializer.to_json(deploy_1) == \ 52 | pycspr.serializer.to_json(deploy_2) 53 | os.remove(fpath) 54 | 55 | 56 | def _create_deploy(deploy_params, cp2): 57 | return pycspr.factory.create_transfer( 58 | deploy_params, 59 | amount=123456789, 60 | target=cp2.to_public_key(), 61 | correlation_id=1 62 | ) 63 | -------------------------------------------------------------------------------- /tests/test_digests.py: -------------------------------------------------------------------------------- 1 | import pycspr 2 | from pycspr.types.crypto import PublicKey 3 | 4 | 5 | def test_derivation_of_a_deploy_body_hash(deploys_1): 6 | for vector in [v for v in deploys_1 if v["typeof"] == "transfer"]: 7 | entity = pycspr.factory.create_deploy_body( 8 | pycspr.factory.create_standard_payment( 9 | vector["payment"]["amount"] 10 | ), 11 | pycspr.factory.create_transfer_session( 12 | vector["session"]["amount"], 13 | PublicKey.from_bytes(vector["session"]["target"]), 14 | vector["session"]["correlation_id"] 15 | ) 16 | ) 17 | assert entity.hash == vector["hashes"]["body"] 18 | 19 | 20 | def test_derivation_of_a_deploy_hash(deploy_params_static, deploys_1): 21 | for vector in [v for v in deploys_1 if v["typeof"] == "transfer"]: 22 | entity = pycspr.factory.create_deploy( 23 | deploy_params_static, 24 | pycspr.factory.create_standard_payment( 25 | vector["payment"]["amount"] 26 | ), 27 | pycspr.factory.create_transfer_session( 28 | vector["session"]["amount"], 29 | PublicKey.from_bytes(vector["session"]["target"]), 30 | vector["session"]["correlation_id"] 31 | ) 32 | ) 33 | 34 | assert entity.hash == vector["hashes"]["deploy"] 35 | -------------------------------------------------------------------------------- /tests/test_factory_2.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | import pycspr 4 | from pycspr.types.node import Deploy 5 | 6 | 7 | def test_create_validator_auction_bid(deploy_params, a_test_account, path_to_wasm_auction_bid): 8 | assert isinstance(pycspr.create_validator_auction_bid( 9 | params=deploy_params, 10 | amount=random.randint(0, int(1e9)), 11 | delegation_rate=random.randint(0, 20), 12 | public_key=a_test_account.to_public_key(), 13 | path_to_wasm=path_to_wasm_auction_bid 14 | ), 15 | Deploy 16 | ) 17 | 18 | 19 | def test_create_validator_auction_bid_withdrawal( 20 | deploy_params, 21 | a_test_account, 22 | a_test_uref, 23 | path_to_wasm_auction_bid_withdrawal 24 | ): 25 | assert isinstance(pycspr.create_validator_auction_bid_withdrawal( 26 | params=deploy_params, 27 | amount=random.randint(0, int(1e9)), 28 | public_key=a_test_account.to_public_key(), 29 | path_to_wasm=path_to_wasm_auction_bid_withdrawal, 30 | unbond_purse_ref=a_test_uref 31 | ), 32 | Deploy 33 | ) 34 | 35 | 36 | def test_create_validator_delegate(deploy_params, a_test_account, path_to_wasm_delegate): 37 | assert isinstance(pycspr.create_validator_delegation( 38 | params=deploy_params, 39 | amount=random.randint(0, int(1e9)), 40 | public_key_of_delegator=a_test_account.to_public_key(), 41 | public_key_of_validator=a_test_account.to_public_key(), 42 | path_to_wasm=path_to_wasm_delegate 43 | ), 44 | Deploy 45 | ) 46 | 47 | 48 | def test_create_validator_delegate_withdrawal( 49 | deploy_params, 50 | a_test_account, 51 | path_to_wasm_delegate_withdrawal 52 | ): 53 | assert isinstance(pycspr.create_validator_delegation_withdrawal( 54 | params=deploy_params, 55 | amount=random.randint(0, int(1e9)), 56 | public_key_of_delegator=a_test_account.to_public_key(), 57 | public_key_of_validator=a_test_account.to_public_key(), 58 | path_to_wasm=path_to_wasm_delegate_withdrawal 59 | ), 60 | Deploy 61 | ) 62 | -------------------------------------------------------------------------------- /tests/test_factory_3.py: -------------------------------------------------------------------------------- 1 | import pycspr 2 | 3 | 4 | def test_that_deploy_size_in_bytes_is_deterministic(deploy_params, cp1, cp2): 5 | deploy = _create_deploy(deploy_params, cp1, cp2) 6 | assert pycspr.get_deploy_size_bytes(deploy) == 395 7 | 8 | 9 | def test_that_deploy_validation_succeeds(deploy_params, cp1, cp2): 10 | assert pycspr.validate_deploy( 11 | _create_deploy(deploy_params, cp1, cp2) 12 | ) 13 | 14 | 15 | def _create_deploy(deploy_params, cp1, cp2): 16 | deploy = pycspr.factory.create_transfer( 17 | deploy_params, 18 | amount=123456789, 19 | target=cp2.to_public_key(), 20 | correlation_id=1 21 | ) 22 | deploy.approve(cp1) 23 | 24 | return deploy 25 | -------------------------------------------------------------------------------- /tests/test_interface.py: -------------------------------------------------------------------------------- 1 | import enum 2 | import inspect 3 | 4 | import pycspr 5 | 6 | 7 | def _has_class(mod, cls): 8 | """Asserts that a container exposes a class. 9 | 10 | """ 11 | _has_member(mod, cls) 12 | assert inspect.isclass(getattr(mod, cls)), "{} is not a class".format(cls) 13 | 14 | 15 | def _has_constant(mod, constant): 16 | """Asserts that a container exposes a constant. 17 | 18 | """ 19 | _has_member(mod, constant) 20 | 21 | 22 | def _has_enum(mod, enm): 23 | """Asserts that a container exposes an enumeration. 24 | 25 | """ 26 | _has_member(mod, enm) 27 | assert issubclass(getattr(mod, enm), enum.Enum), "{} is not an enum".format(enm) 28 | 29 | 30 | def _has_exception(mod, err): 31 | """Asserts that a container exposes an exception. 32 | 33 | """ 34 | _has_class(mod, err) 35 | assert issubclass(getattr(mod, err), Exception), \ 36 | "Exception type does not inherit from builtin Exception class." 37 | 38 | 39 | def _has_function(mod, func): 40 | """Asserts that a container exposes a function. 41 | 42 | """ 43 | _has_member(mod, func) 44 | assert inspect.isfunction(getattr(mod, func)), "{} is not a function".format(func) 45 | 46 | 47 | def _has_member(mod, member): 48 | """Asserts that a module exposes a member. 49 | 50 | """ 51 | assert inspect.ismodule(mod) 52 | assert hasattr(mod, member), "Missing member: {}".format(member) 53 | 54 | 55 | # Expected interface. 56 | _INTERFACE_OF_LIBRARY = { 57 | _has_class: { 58 | "NodeEventInfo", 59 | "NodeRestClient", 60 | "NodeRestConnectionInfo", 61 | "NodeRpcClient", 62 | "NodeRpcConnectionInfo", 63 | "NodeRpcProxyError", 64 | "NodeSpeculativeRpcClient", 65 | "NodeSpeculativeRpcConnectionInfo", 66 | "NodeSseClient", 67 | "NodeSseConnectionInfo", 68 | "PublicKey", 69 | "PrivateKey", 70 | }, 71 | _has_enum: { 72 | "HashAlgorithm", 73 | "KeyAlgorithm", 74 | "NodeEventChannel", 75 | "NodeEventType", 76 | }, 77 | _has_constant: { 78 | "DEFAULT_HASH_ALGO", 79 | "DEFAULT_KEY_ALGO", 80 | "SSE_CHANNEL_TO_SSE_EVENT", 81 | }, 82 | _has_exception: { 83 | "InvalidDeployException", 84 | }, 85 | _has_function: { 86 | "create_deploy", 87 | "create_deploy_approval", 88 | "create_deploy_arguments", 89 | "create_deploy_parameters", 90 | "create_deploy_ttl", 91 | "create_transfer", 92 | "create_standard_payment", 93 | "create_validator_auction_bid", 94 | "create_validator_auction_bid_withdrawal", 95 | "create_validator_delegation", 96 | "create_validator_delegation_withdrawal", 97 | "from_bytes", 98 | "from_json", 99 | "parse_private_key", 100 | "parse_private_key_bytes", 101 | "parse_public_key", 102 | "parse_public_key_bytes", 103 | "get_account_hash", 104 | "get_account_key", 105 | "get_deploy_size_bytes", 106 | "get_hash", 107 | "read_deploy", 108 | "read_wasm", 109 | "to_bytes", 110 | "to_json", 111 | "write_deploy", 112 | }, 113 | _has_member: { 114 | "crypto", 115 | "factory", 116 | "types", 117 | } 118 | } 119 | 120 | 121 | def test_version_of_library(): 122 | assert pycspr.__version__ == "1.2.1" 123 | 124 | 125 | def test_exports_of_library(): 126 | for assertor, members in _INTERFACE_OF_LIBRARY.items(): 127 | for member in members: 128 | try: 129 | assertor(pycspr, member) 130 | except Exception as err: 131 | print(err) 132 | raise err 133 | 134 | 135 | def _test_exports(module, interface): 136 | for assertor, members in interface.items(): 137 | for member in members: 138 | assertor(module, member) 139 | -------------------------------------------------------------------------------- /tests/test_sc_lifecycle_1.py: -------------------------------------------------------------------------------- 1 | import pycctl 2 | 3 | 4 | async def test_that_cctl_assets_exist() -> None: 5 | pycctl.validator.validate_infra_net_assets_setup() 6 | 7 | 8 | async def test_that_cctl_network_is_up() -> None: 9 | await pycctl.validator.validate_infra_net_is_up() 10 | 11 | 12 | async def test_that_cctl_accounts_are_funded() -> None: 13 | await pycctl.validator.validate_chain_accounts_are_funded() 14 | -------------------------------------------------------------------------------- /tests/test_serializer_1.py: -------------------------------------------------------------------------------- 1 | from pycspr import serializer 2 | from pycspr.types.cl import CLT_Type 3 | 4 | 5 | def test_that_cl_types_serialisation_to_and_from_bytes(cl_types_vector): 6 | for entity in cl_types_vector: 7 | encoded = serializer.to_bytes(entity) 8 | assert isinstance(encoded, bytes) 9 | _, decoded = serializer.from_bytes(CLT_Type, encoded) 10 | assert entity == decoded 11 | 12 | 13 | def test_that_cl_types_serialisation_to_and_from_json(cl_types_vector): 14 | for entity in cl_types_vector: 15 | encoded = serializer.to_json(entity) 16 | assert isinstance(encoded, (str, dict)) 17 | decoded = serializer.from_json(type(entity), encoded) 18 | assert entity == decoded 19 | -------------------------------------------------------------------------------- /tests/test_serializer_2.py: -------------------------------------------------------------------------------- 1 | from pycspr import serializer 2 | 3 | 4 | def test_that_cl_values_serialisation_to_and_from_bytes(cl_values_vector): 5 | for entity in cl_values_vector: 6 | encoded = serializer.to_bytes(entity) 7 | assert isinstance(encoded, bytes) 8 | cl_typedef = serializer.clv_to_clt(entity) 9 | _, decoded = serializer.from_bytes(cl_typedef, encoded) 10 | assert entity == decoded 11 | 12 | 13 | def test_that_cl_values_serialisation_to_and_from_json(cl_values_vector): 14 | for entity in cl_values_vector: 15 | encoded = serializer.to_json(entity) 16 | assert isinstance(encoded, dict) 17 | decoded = serializer.from_json(type(entity), encoded) 18 | assert entity == decoded 19 | -------------------------------------------------------------------------------- /tests/test_serializer_3.py: -------------------------------------------------------------------------------- 1 | from pycspr import serializer 2 | from pycspr.types.node import DeployBody 3 | 4 | 5 | def test_that_node_entities_serialisation_to_and_from_bytes(deploy_entities_iterator): 6 | for entity in deploy_entities_iterator(): 7 | print(type(entity)) 8 | 9 | encoded = serializer.to_bytes(entity) 10 | _, decoded = serializer.from_bytes(type(entity), encoded) 11 | assert entity == decoded 12 | 13 | 14 | def test_that_node_entities_serialisation_to_and_from_json(deploy_entities_iterator): 15 | for entity in deploy_entities_iterator(): 16 | if type(entity) not in (DeployBody, ): 17 | encoded = serializer.to_json(entity) 18 | decoded = serializer.from_json(type(entity), encoded) 19 | assert entity == decoded 20 | -------------------------------------------------------------------------------- /tests/test_serializer_4.py: -------------------------------------------------------------------------------- 1 | import pycspr 2 | from pycspr import serializer 3 | from pycspr.types.crypto import PublicKey 4 | 5 | 6 | def test_that_node_standard_payment_serialises_to_and_from_bytes(deploys_1): 7 | for vector in [v for v in deploys_1 if v["typeof"] == "transfer"]: 8 | entity = pycspr.create_standard_payment( 9 | vector["payment"]["amount"] 10 | ) 11 | encoded = serializer.to_bytes(entity) 12 | assert encoded == vector["bytes"]["payment"] 13 | _, decoded = serializer.from_bytes(type(entity), encoded) 14 | assert entity == decoded 15 | 16 | 17 | def test_that_node_standard_payment_serialises_to_and_from_json(deploys_1): 18 | for vector in [v for v in deploys_1 if v["typeof"] == "transfer"]: 19 | entity = pycspr.create_standard_payment( 20 | vector["payment"]["amount"] 21 | ) 22 | encoded = serializer.to_json(entity) 23 | decoded = serializer.from_json(type(entity), encoded) 24 | assert entity == decoded 25 | 26 | 27 | def test_that_node_standard_transfer_session_serialises_to_and_from_bytes(deploys_1): 28 | for vector in [v for v in deploys_1 if v["typeof"] == "transfer"]: 29 | amount: int = vector["session"]["amount"] 30 | correlation_id: int = vector["session"]["correlation_id"] 31 | target: PublicKey = PublicKey.from_bytes(vector["session"]["target"]) 32 | entity = pycspr.factory.create_transfer_session(amount, target, correlation_id) 33 | 34 | encoded = serializer.to_bytes(entity) 35 | assert encoded == vector["bytes"]["session"] 36 | 37 | _, decoded = serializer.from_bytes(type(entity), encoded) 38 | assert entity == decoded 39 | 40 | 41 | def test_that_node_standard_transfer_session_serialises_to_and_from_json(deploys_1): 42 | for vector in [v for v in deploys_1 if v["typeof"] == "transfer"]: 43 | amount: int = vector["session"]["amount"] 44 | correlation_id: int = vector["session"]["correlation_id"] 45 | target: PublicKey = PublicKey.from_bytes(vector["session"]["target"]) 46 | entity = pycspr.factory.create_transfer_session(amount, target, correlation_id) 47 | 48 | encoded = serializer.to_json(entity) 49 | decoded = serializer.from_json(type(entity), encoded) 50 | assert entity == decoded 51 | -------------------------------------------------------------------------------- /tests/test_serializer_5.py: -------------------------------------------------------------------------------- 1 | import pycspr 2 | from pycspr import serializer 3 | from pycspr.types.crypto import PublicKeyBytes 4 | 5 | 6 | def test_that_node_standard_transfer_serialises_to_and_from_json( 7 | deploy_params_static, 8 | deploys_1 9 | ): 10 | for vector in [v for v in deploys_1 if v["typeof"] == "transfer"]: 11 | entity = pycspr.create_transfer( 12 | params=deploy_params_static, 13 | amount=vector["session"]["amount"], 14 | target=serializer.from_json(PublicKeyBytes, vector["session"]["target"]), 15 | correlation_id=vector["session"]["correlation_id"] 16 | ) 17 | encoded = serializer.to_json(entity) 18 | decoded = serializer.from_json(type(entity), encoded) 19 | assert entity == decoded 20 | -------------------------------------------------------------------------------- /tests/test_utils_conversion.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | from pycspr.utils import convertor 4 | 5 | 6 | def test_ms_from_humanized_time_interval(): 7 | for (unit, ms, quantity) in ( 8 | ("ms", 1, random.randint(1, int(1e9))), 9 | ("s", 1000, random.randint(1, 60)), 10 | ("m", 60000, random.randint(1, 60)), 11 | ("h", 3600000, random.randint(1, 2)), 12 | ): 13 | assert convertor.ms_from_humanized_time_interval(f"{quantity}{unit}") == quantity * ms 14 | -------------------------------------------------------------------------------- /tests/utils/__init__.py: -------------------------------------------------------------------------------- 1 | from tests.utils import cctl 2 | -------------------------------------------------------------------------------- /tests/utils/cctl.py: -------------------------------------------------------------------------------- 1 | import enum 2 | import os 3 | import pathlib 4 | 5 | import pycspr 6 | 7 | 8 | _NAME_OF_CCTL_EVAR = "CCTL" 9 | 10 | COUNT_OF_USERS = 10 11 | COUNT_OF_VALDIATORS = 10 12 | 13 | 14 | class AccountType(enum.Enum): 15 | FAUCET = enum.auto() 16 | USER = enum.auto() 17 | VALIDATOR = enum.auto() 18 | 19 | 20 | class AsymmetricKeyType(enum.Enum): 21 | PRIVATE = enum.auto() 22 | PUBLIC = enum.auto() 23 | 24 | 25 | KEY_FILE_NAME_BY_KEY_TYPE = { 26 | AsymmetricKeyType.PUBLIC: "public_key_hex", 27 | AsymmetricKeyType.PRIVATE: "secret_key.pem", 28 | } 29 | 30 | _cache = { 31 | "keys": {} 32 | } 33 | 34 | 35 | def get_evar(): 36 | return os.getenv(_NAME_OF_CCTL_EVAR) 37 | 38 | 39 | def get_path_to_assets() -> pathlib.Path: 40 | return pathlib.Path(get_evar()) / "assets" 41 | 42 | 43 | def get_path_to_public_key_of_faucet() -> pathlib.Path: 44 | return _get_path_to_asymmetric_key(AsymmetricKeyType.PUBLIC, AccountType.FAUCET) 45 | 46 | 47 | def get_path_to_private_key_of_faucet() -> pathlib.Path: 48 | return _get_path_to_asymmetric_key(AsymmetricKeyType.PRIVATE, AccountType.FAUCET) 49 | 50 | 51 | def get_path_to_public_key_of_user(account_idx: int) -> pathlib.Path: 52 | return _get_path_to_asymmetric_key(AsymmetricKeyType.PUBLIC, AccountType.USER, account_idx) 53 | 54 | 55 | def get_path_to_private_key_of_user(account_idx: int) -> pathlib.Path: 56 | return _get_path_to_asymmetric_key(AsymmetricKeyType.PRIVATE, AccountType.USER, account_idx) 57 | 58 | 59 | def get_path_to_public_key_of_validator(account_idx: int) -> pathlib.Path: 60 | return _get_path_to_asymmetric_key( 61 | AsymmetricKeyType.PUBLIC, 62 | AccountType.VALIDATOR, 63 | account_idx 64 | ) 65 | 66 | 67 | def get_path_to_private_key_of_validator(account_idx: int) -> pathlib.Path: 68 | return _get_path_to_asymmetric_key( 69 | AsymmetricKeyType.PRIVATE, 70 | AccountType.VALIDATOR, 71 | account_idx 72 | ) 73 | 74 | 75 | def get_public_key_of_faucet() -> pycspr.PublicKey: 76 | return _get_asymmetric_key(AsymmetricKeyType.PUBLIC, AccountType.FAUCET, None) 77 | 78 | 79 | def get_private_key_of_faucet() -> pycspr.PrivateKey: 80 | return _get_asymmetric_key(AsymmetricKeyType.PRIVATE, AccountType.FAUCET, None) 81 | 82 | 83 | def get_public_key_of_user(account_idx: int) -> pycspr.PublicKey: 84 | return _get_asymmetric_key(AsymmetricKeyType.PUBLIC, AccountType.USER, account_idx) 85 | 86 | 87 | def get_private_key_of_user(account_idx: int) -> pycspr.PrivateKey: 88 | return _get_asymmetric_key(AsymmetricKeyType.PRIVATE, AccountType.USER, account_idx) 89 | 90 | 91 | def get_public_key_of_validator(account_idx: int) -> pycspr.PublicKey: 92 | return _get_asymmetric_key(AsymmetricKeyType.PUBLIC, AccountType.VALIDATOR, account_idx) 93 | 94 | 95 | def get_private_key_of_validator(account_idx: int) -> pycspr.PrivateKey: 96 | return _get_asymmetric_key(AsymmetricKeyType.PRIVATE, AccountType.VALIDATOR, account_idx) 97 | 98 | 99 | def _get_asymmetric_key( 100 | key_type: AsymmetricKeyType, 101 | account_type: AccountType, 102 | account_idx: None 103 | ) -> pathlib.Path: 104 | cache_key = f"{key_type}-{account_type}-{account_idx}" 105 | if cache_key not in _cache["keys"]: 106 | _cache["keys"][cache_key] = \ 107 | _get_parsed_asymmetric_key( 108 | key_type, 109 | _get_path_to_asymmetric_key(key_type, account_type, account_idx) 110 | ) 111 | 112 | return _cache["keys"][cache_key] 113 | 114 | 115 | def _get_path_to_asymmetric_key( 116 | key_type: AsymmetricKeyType, 117 | account_type: AccountType, 118 | account_idx: None 119 | ) -> pathlib.Path: 120 | path = get_path_to_assets() 121 | if account_type == AccountType.FAUCET: 122 | path = path / "faucet" 123 | elif account_type == AccountType.USER: 124 | path = path / "users" / f"user-{account_idx}" 125 | elif account_type == AccountType.VALIDATOR: 126 | path = path / "nodes" / f"node-{account_idx}" / "keys" 127 | 128 | return path / KEY_FILE_NAME_BY_KEY_TYPE[key_type] 129 | 130 | 131 | def _get_parsed_asymmetric_key(key_type: AsymmetricKeyType, path_to_key: pathlib.Path): 132 | return \ 133 | pycspr.parse_private_key(path_to_key) \ 134 | if key_type == AsymmetricKeyType.PRIVATE else \ 135 | pycspr.parse_public_key(path_to_key) 136 | --------------------------------------------------------------------------------