├── snet ├── __init__.py └── cli │ ├── utils │ ├── __init__.py │ ├── token2cogs.py │ ├── proto_utils.py │ ├── config.py │ ├── ipfs_utils.py │ └── utils.py │ ├── commands │ ├── __init__.py │ ├── sdk_command.py │ ├── mpe_account.py │ └── mpe_treasurer.py │ ├── metadata │ ├── __init__.py │ └── organization.py │ ├── test │ ├── functional_tests │ │ ├── service_spec1 │ │ │ ├── test │ │ │ └── ExampleService.proto │ │ ├── script8_networks.sh │ │ ├── script2_deposit_transfer.sh │ │ ├── script15_sdk_generate_client_library.sh │ │ ├── script10_claim_timeout_all.sh │ │ ├── script14_full_stateless_logic.sh │ │ ├── mint │ │ │ └── mint.py │ │ ├── script5_identity1_rpc_mnemonic.sh │ │ ├── test_entry_point.py │ │ ├── script7_contracts.sh │ │ ├── script13_call_reinitialize.sh │ │ ├── script12_extend_add_for_service.sh │ │ ├── script11_update_metadata.sh │ │ ├── script9_treasurer.sh │ │ ├── script3_without_addresses.sh │ │ ├── func_tests.py │ │ ├── script6_organization.sh │ │ ├── script4_only_with_networks.sh │ │ ├── simple_daemon │ │ │ └── test_simple_daemon.py │ │ └── script1_twogroups.sh │ ├── utils │ │ ├── run_all_functional.sh │ │ └── reset_environment.sh │ ├── unit_tests │ │ ├── test_url_validator.py │ │ ├── test_mpe_service_metadata_1.py │ │ └── test_mpe_service_metadata_2.py │ └── README.md │ ├── __init__.py │ ├── contract.py │ └── resources │ ├── proto │ ├── control_service.proto │ ├── token_service.proto │ ├── state_service.proto │ ├── training.proto │ └── training │ │ └── training.proto │ ├── org_schema.json │ └── service_schema.json ├── version.py ├── scripts ├── package-pip ├── package-namespace-pip └── blockchain ├── docs ├── source │ ├── img │ │ └── singularityNET.png │ ├── subcommand_template.tpl │ ├── index_template.tpl │ ├── generate_rst.py │ ├── generate_markdown.py │ └── conf.py ├── requirements.txt ├── build-docs.sh ├── build-docs.ps1 └── Makefile ├── MANIFEST.in ├── .gitignore ├── requirements.txt ├── LICENSE ├── setup.py ├── .github └── workflows │ ├── master.yml │ └── dev.yml └── README.md /snet/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /snet/cli/utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /snet/cli/commands/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /snet/cli/metadata/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /version.py: -------------------------------------------------------------------------------- 1 | __version__ = "3.0.0" 2 | -------------------------------------------------------------------------------- /snet/cli/test/functional_tests/service_spec1/test: -------------------------------------------------------------------------------- 1 | test 2 | -------------------------------------------------------------------------------- /scripts/package-pip: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | python3.11 setup.py sdist bdist_wheel 4 | -------------------------------------------------------------------------------- /scripts/package-namespace-pip: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | python3.10 setup-namespace.py sdist bdist_wheel 4 | -------------------------------------------------------------------------------- /docs/source/img/singularityNET.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/singnet/snet-cli/HEAD/docs/source/img/singularityNET.png -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | Sphinx~=8.1.3 2 | sphinx-argparse~=0.5.2 3 | sphinx-rtd-theme~=3.0.1 4 | bs4~=0.0.2 5 | html2text~=2024.2.26 6 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include snet/cli/resources * 2 | 3 | recursive-exclude * .git 4 | recursive-exclude * node_modules 5 | recursive-exclude * __pycache__ 6 | recursive-exclude * *.py[co] 7 | -------------------------------------------------------------------------------- /docs/source/subcommand_template.tpl: -------------------------------------------------------------------------------- 1 | <> 2 | *********************** 3 | 4 | .. argparse:: 5 | :module: snet.cli.arguments 6 | :func: get_parser 7 | :prog: snet 8 | :path: <> -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | .venv 3 | env/ 4 | venv/ 5 | .idea/ 6 | __pycache__ 7 | blockchain/node_modules 8 | snet_egg-info/ 9 | snet_cli.egg-info/ 10 | snet/cli/resources/node_modules 11 | snet/cli/resources/proto/*.py 12 | build/ 13 | *.rst 14 | dist/ 15 | *.pyi 16 | -------------------------------------------------------------------------------- /docs/build-docs.sh: -------------------------------------------------------------------------------- 1 | cd source 2 | python generate_rst.py 3 | cd .. 4 | 5 | make clean 6 | make html 7 | cp source/snet-cli-static/theme.css build/html/_static/css/ 8 | 9 | cd source 10 | python generate_markdown.py ../build/html/ ../build/html/clean ../build/markdown 11 | cd .. 12 | rm -rf build/html/clean 13 | -------------------------------------------------------------------------------- /docs/build-docs.ps1: -------------------------------------------------------------------------------- 1 | cd source 2 | python generate_rst.py 3 | cd .. 4 | 5 | make clean 6 | make html 7 | Copy-Item source/snet-cli-static/theme.css build/html/_static/css/ 8 | 9 | cd source 10 | python generate_markdown.py ../build/html/ ../build/html/clean ../build/markdown 11 | cd .. 12 | Remove-Item -Path build/html/clean -Recurse 13 | -------------------------------------------------------------------------------- /docs/source/index_template.tpl: -------------------------------------------------------------------------------- 1 | 2 | Welcome to snet-cli's documentation! 3 | ==================================== 4 | 5 | .. argparse:: 6 | :module: snet.cli.arguments 7 | :func: get_parser 8 | :prog: snet 9 | :path: 10 | :nosubcommands: 11 | 12 | .. toctree:: 13 | :maxdepth: 1 14 | :caption: Commands: 15 | :hidden: 16 | -------------------------------------------------------------------------------- /snet/cli/test/functional_tests/service_spec1/ExampleService.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | service ExampleService { 4 | rpc classify(ClassifyRequest) returns (ClassifyResponse); 5 | } 6 | 7 | message ClassifyRequest { 8 | string image_type = 1; 9 | string image = 2; 10 | } 11 | 12 | message ClassifyResponse { 13 | repeated string predictions = 1; 14 | repeated float confidences = 2; 15 | bytes binary_field = 3; 16 | } 17 | 18 | -------------------------------------------------------------------------------- /snet/cli/test/functional_tests/script8_networks.sh: -------------------------------------------------------------------------------- 1 | # Test "snet network" 2 | 3 | snet network create local_bad http://localhost:8080 && exit 1 || echo "fail as expected" 4 | 5 | # create network without check 6 | snet network create local_bad http://localhost:8080 --skip-check 7 | 8 | #switch to this network 9 | snet network local_bad 10 | 11 | #switch to mainnet 12 | snet network mainnet 13 | 14 | #switch to goerli 15 | snet network goerli 16 | 17 | -------------------------------------------------------------------------------- /snet/cli/test/utils/run_all_functional.sh: -------------------------------------------------------------------------------- 1 | for f in snet/cli/test/functional_tests/script?_* 2 | do 3 | bash -ex ./snet/cli/test/utils/reset_environment.sh --i-no-what-i-am-doing 4 | bash -ex -c "cd snet/cli/test/functional_tests; bash -ex `basename $f`" 5 | done 6 | 7 | for f in snet/cli/test/functional_tests/script??_* 8 | do 9 | bash -ex ./snet/cli/test/utils/reset_environment.sh --i-no-what-i-am-doing 10 | bash -ex -c "cd snet/cli/test/functional_tests; bash -ex `basename $f`" 11 | done 12 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | protobuf==4.21.6 2 | grpcio-tools==1.59.0 3 | wheel==0.41.2 4 | jsonrpcclient==4.0.3 5 | eth-hash==0.5.2 6 | rlp==3.0.0 7 | eth-rlp==0.3.0 8 | web3==6.11.1 9 | mnemonic==0.20 10 | pycoin==0.92.20230326 11 | pyyaml==6.0.1 12 | ipfshttpclient==0.4.13.2 13 | pymultihash==0.8.2 14 | base58==2.1.1 15 | argcomplete==3.1.2 16 | grpcio-health-checking==1.59.0 17 | jsonschema==4.0.0 18 | eth-account==0.9.0 19 | trezor==0.13.8 20 | ledgerblue==0.1.48 21 | snet-contracts==1.0.0 22 | lighthouseweb3==0.1.4 23 | cryptography==44.0.1 24 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SOURCEDIR = source 8 | BUILDDIR = build 9 | 10 | # Put it first so that "make" without argument is like "make help". 11 | help: 12 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 13 | 14 | .PHONY: help Makefile 15 | 16 | # Catch-all target: route all unknown targets to Sphinx using the new 17 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 18 | %: Makefile 19 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /snet/cli/test/functional_tests/script2_deposit_transfer.sh: -------------------------------------------------------------------------------- 1 | # test deposit and transfer functions in snet client 2 | 3 | # initial balance should be 0 4 | MPE_BALANCE=$(snet account balance|grep MPE) 5 | test ${MPE_BALANCE##*:} = "0" 6 | 7 | snet account deposit 12345.678 -y -q 8 | 9 | MPE_BALANCE=$(snet account balance|grep MPE) 10 | test ${MPE_BALANCE##*:} = "12345.678" 11 | 12 | snet account transfer 0x0067b427E299Eb2A4CBafc0B04C723F77c6d8a18 42.314 -y -q 13 | 14 | MPE_BALANCE=$(snet account balance --account 0x0067b427E299Eb2A4CBafc0B04C723F77c6d8a18|grep MPE) 15 | test ${MPE_BALANCE##*:} = "42.314" 16 | 17 | 18 | MPE_BALANCE=$(snet account balance|grep MPE) 19 | test ${MPE_BALANCE##*:} = "12303.364" 20 | 21 | 22 | snet account withdraw 1.42 -y -q 23 | MPE_BALANCE=$(snet account balance|grep MPE) 24 | test ${MPE_BALANCE##*:} = "12301.944" 25 | -------------------------------------------------------------------------------- /snet/cli/utils/token2cogs.py: -------------------------------------------------------------------------------- 1 | """ Safe conversion between token(string) and cogs(int) """ 2 | import decimal 3 | 4 | TOKEN_DECIMALS = 18 5 | 6 | 7 | def strtoken2cogs(str_token): 8 | if type(str_token) != str: 9 | raise Exception("Parameter should be string") 10 | 11 | # in case user write something stupid we set very big precision 12 | decimal.getcontext().prec = 1000 13 | cogs_decimal = decimal.Decimal(str_token) * 10 ** TOKEN_DECIMALS 14 | cogs_int = int(cogs_decimal) 15 | if cogs_int != cogs_decimal: 16 | raise Exception("ASI(FET) token has only %i decimals" % TOKEN_DECIMALS) 17 | return cogs_int 18 | 19 | 20 | def cogs2strtoken(cogs_int): 21 | # presicison should be higer then INITIAL_SUPPLY + 1, we set it to 1000 be consistent with strtoken2cogs 22 | decimal.getcontext().prec = 1000 23 | token_decimal = decimal.Decimal(cogs_int) / 10 ** TOKEN_DECIMALS 24 | return format(token_decimal, 'f') 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 SingularityNET 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /snet/cli/test/functional_tests/script15_sdk_generate_client_library.sh: -------------------------------------------------------------------------------- 1 | # simple case of one group 2 | snet service metadata-init ./service_spec1/ ExampleService --group-name group1 --fixed-price 0.0001 --endpoints 8.8.8.8:2020 3 | snet organization metadata-init org1 testo individual 4 | snet organization add-group group1 0x42A605c07EdE0E1f648aB054775D6D4E38496144 127.0.0.1:50051 5 | snet organization add-group group2 0x42A605c07EdE0E1f648aB054775D6D4E38496144 127.0.0.1:50051 6 | snet organization create testo -y -q 7 | snet service publish testo tests -y -q 8 | 9 | snet sdk generate-client-library python testo tests 10 | test -f client_libraries/testo/tests/python/ExampleService_pb2_grpc.py 11 | 12 | snet sdk generate-client-library nodejs testo tests 13 | test -f client_libraries/testo/tests/nodejs/ExampleService_grpc_pb.js 14 | 15 | # test relative path (and using already installed compiler) 16 | snet sdk generate-client-library nodejs testo tests snet_output 17 | test -f snet_output/testo/tests/nodejs/ExampleService_grpc_pb.js 18 | 19 | # test absolute path 20 | snet sdk generate-client-library nodejs testo tests /tmp/snet_output 21 | test -f /tmp/snet_output/testo/tests/nodejs/ExampleService_grpc_pb.js 22 | -------------------------------------------------------------------------------- /snet/cli/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # PYTHON_ARGCOMPLETE_OK 3 | 4 | import sys 5 | import warnings 6 | 7 | import argcomplete 8 | 9 | with warnings.catch_warnings(): 10 | # Suppress the eth-typing package`s warnings related to some new networks 11 | warnings.filterwarnings("ignore", "Network .* does not have a valid ChainId. eth-typing should be " 12 | "updated with the latest networks.", UserWarning) 13 | from snet.cli import arguments 14 | 15 | from snet.cli.config import Config 16 | 17 | 18 | def main(): 19 | try: 20 | argv = sys.argv[1:] 21 | conf = Config() 22 | parser = arguments.get_root_parser(conf) 23 | argcomplete.autocomplete(parser) 24 | 25 | try: 26 | args = parser.parse_args(argv) 27 | except TypeError: 28 | args = parser.parse_args(argv + ["-h"]) 29 | 30 | getattr(args.cmd(conf, args), args.fn)() 31 | except Exception as e: 32 | if sys.argv[1] == "--print-traceback": 33 | raise 34 | else: 35 | print("Error:", e) 36 | print("If you want to see full Traceback then run:") 37 | print("snet --print-traceback [parameters]") 38 | sys.exit(42) 39 | -------------------------------------------------------------------------------- /snet/cli/contract.py: -------------------------------------------------------------------------------- 1 | from web3.logs import DISCARD 2 | 3 | 4 | class Contract: 5 | def __init__(self, w3, address, abi): 6 | self.w3 = w3 7 | self.contract = self.w3.eth.contract(address=self.w3.to_checksum_address(address), abi=abi) 8 | self.abi = abi 9 | 10 | def call(self, function_name, *positional_inputs, **named_inputs): 11 | return getattr(self.contract.functions, function_name)(*positional_inputs, **named_inputs).call() 12 | 13 | def build_transaction(self, function_name, from_address, gas_price, *positional_inputs, **named_inputs): 14 | nonce = self.w3.eth.get_transaction_count(from_address) 15 | chain_id = self.w3.net.version 16 | return getattr(self.contract.functions, function_name)(*positional_inputs, **named_inputs).build_transaction({ 17 | "from": from_address, 18 | "nonce": nonce, 19 | "gasPrice": gas_price, 20 | "chainId": int(chain_id) 21 | }) 22 | 23 | def process_receipt(self, receipt): 24 | events = [] 25 | 26 | contract_events = map(lambda e: e["name"], filter(lambda e: e["type"] == "event", self.abi)) 27 | for contract_event in contract_events: 28 | events.extend(getattr(self.contract.events, contract_event)().process_receipt(receipt, errors=DISCARD)) 29 | 30 | return events 31 | -------------------------------------------------------------------------------- /snet/cli/test/unit_tests/test_url_validator.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from snet.cli.utils.utils import is_valid_url 4 | 5 | 6 | class TestURLValidator(unittest.TestCase): 7 | 8 | def test_valid_urls(self): 9 | valid_urls = [ 10 | "https://www.example.com", 11 | "http://example.com", 12 | "ftp://ftp.example.com", 13 | "https://localhost", 14 | "http://127.0.0.1", 15 | "https://[::1]", 16 | "https://www.example.com", 17 | "http://localhost:5432", 18 | "https://192.168.20.20", 19 | "https://192.168.20.20:8000", 20 | "https://192.168.20.20:8001/get_objects", 21 | "http://0.0.0.0:8000" 22 | ] 23 | for url in valid_urls: 24 | with self.subTest(url=url): 25 | self.assertTrue(is_valid_url(url)) 26 | 27 | def test_invalid_urls(self): 28 | invalid_urls = [ 29 | "www.example.com", 30 | "http//example.com", 31 | "://missing.scheme.com", 32 | "http://", 33 | "http://invalid_domain", 34 | "ftp://-invalid.com", 35 | "http://http://example.com" 36 | ] 37 | for url in invalid_urls: 38 | with self.subTest(url=url): 39 | self.assertFalse(is_valid_url(url)) 40 | 41 | 42 | if __name__ == '__main__': 43 | unittest.main() 44 | -------------------------------------------------------------------------------- /snet/cli/test/README.md: -------------------------------------------------------------------------------- 1 | # Tests for snet-cli 2 | 3 | We have three layers of tests for snet-cli 4 | * classical unit tests: [unit_tests](unit_tests) 5 | * functional tests: [functional_tests](functional_tests) 6 | * integration test for the whole system. This test can be found in the 7 | separate repository: [platform-pipeline](https://github.com/singnet/platform-pipeline) 8 | 9 | ### Unit tests 10 | 11 | You can simply add your unit test in [unit_tests](unit_tests), it will be run 12 | automatically in circleci. In the same directory you can find an 13 | example of unit tests. 14 | 15 | ### Functional tests 16 | 17 | Our functional tests are *de facto* integration tests with 18 | [platform-contracts](https://github.com/singnet/platform-contracts). 19 | 20 | Functional tests are excecuted in the following environment (see 21 | [utils/reset_environment.sh](utils/reset_environment.sh)): 22 | 23 | * IPFS is running and snet-cli is correctly configured to use it 24 | * local ethereum network (ganache-cli with mnemonics "gauge enact biology destroy normal tunnel 25 | slight slide wide sauce ladder produce") is running, and snet-cli is 26 | configured to use it by default 27 | * There is already one identity in snet-cli. It is "rcp" identity 28 | called "snet-user", which by default corresponds to the first ganache identity. 29 | * snet-cli already know correct addreses for SingularityNetToken, Registry and MultiPartyEscrow contracts, so you shoundn't set them manualy 30 | 31 | Examples of tests can be found here: [functional_tests](functional_tests). You should add your tests in 32 | this directory. 33 | -------------------------------------------------------------------------------- /snet/cli/test/unit_tests/test_mpe_service_metadata_1.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from snet.cli.metadata.service import MPEServiceMetadata 4 | 5 | class TestStringMethods(unittest.TestCase): 6 | 7 | def test_init(self): 8 | metadata = MPEServiceMetadata() 9 | ipfs_hash = "Qmb6ZkZrVUU6A2VW3PDsrRVT7etPnAD24bv3jr7MwQCWQG" 10 | mpe_address = "0x592E3C0f3B038A0D673F19a18a773F993d4b2610" 11 | display_name = "Display Name" 12 | encoding = "grpc" 13 | service_type = "grpc" 14 | payment_expiration_threshold = 31415 15 | metadata.set_simple_field("model_ipfs_hash", ipfs_hash) 16 | metadata.set_simple_field("mpe_address", mpe_address) 17 | metadata.set_simple_field("display_name", display_name) 18 | metadata.set_simple_field("encoding", encoding) 19 | metadata.set_simple_field("service_type", service_type) 20 | metadata.set_simple_field("payment_expiration_threshold", payment_expiration_threshold) 21 | 22 | self.assertEqual(metadata["model_ipfs_hash"], ipfs_hash) 23 | self.assertEqual(metadata["mpe_address"], mpe_address) 24 | self.assertEqual(metadata["display_name"], display_name) 25 | self.assertEqual(metadata["encoding"], encoding) 26 | self.assertEqual(metadata["service_type"], service_type) 27 | self.assertEqual(metadata["payment_expiration_threshold"], payment_expiration_threshold) 28 | 29 | if __name__ == '__main__': 30 | unittest.main() 31 | 32 | -------------------------------------------------------------------------------- /snet/cli/test/functional_tests/script10_claim_timeout_all.sh: -------------------------------------------------------------------------------- 1 | snet service metadata-init ./service_spec1/ ExampleService --group-name group0 --fixed-price 0.0001 --endpoints 8.8.8.8:2020 9.8.9.8:8080 2 | snet service metadata-init ./service_spec1/ ExampleService 3 | snet service metadata-add-group group1 4 | snet service metadata-add-endpoints group1 8.8.8.8:22 1.2.3.4:8080 5 | 6 | snet service metadata-add-group group2 7 | snet service metadata-add-endpoints group2 8.8.8.8:2 1.2.3.4:800 8 | 9 | snet organization metadata-init org1 testo individual 10 | snet organization add-group group0 0x42A605c07EdE0E1f648aB054775D6D4E38496144 5.5.6.7:8089 11 | snet organization add-group group1 0x0067b427E299Eb2A4CBafc0B04C723F77c6d8a18 1.2.1.1:8089 12 | snet organization add-group group2 0x32267d505B1901236508DcDa64C1D0d5B9DF639a 1.2.1.1:8089 13 | snet organization create testo -y -q 14 | snet service metadata-remove-group group0 15 | snet service publish testo tests -y -q 16 | 17 | EXPIRATION0=$(($(snet channel block-number) - 1)) 18 | EXPIRATION1=$(($(snet channel block-number) - 1)) 19 | EXPIRATION2=$(($(snet channel block-number) + 100000)) 20 | 21 | snet account deposit 100 -y -q 22 | 23 | assert_balance() { 24 | MPE_BALANCE=$(snet account balance | grep MPE) 25 | test ${MPE_BALANCE##*:} = $1 26 | } 27 | 28 | # should file because group_name has not been specified 29 | snet channel open-init testo tests 1 $EXPIRATION0 -yq && exit 1 || echo "fail as expected" 30 | 31 | snet channel open-init testo group0 0.1 $EXPIRATION0 -yq 32 | snet channel open-init testo group1 1 $EXPIRATION1 -yq 33 | snet channel open-init testo group2 10 $EXPIRATION2 -yq 34 | 35 | assert_balance 88.9 36 | 37 | # should claim channels 0 and 1, but not 2 38 | snet channel claim-timeout-all -y 39 | assert_balance 90 40 | -------------------------------------------------------------------------------- /snet/cli/resources/proto/control_service.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package escrow; 4 | 5 | service ProviderControlService { 6 | 7 | //get list of all unclaimed "payments". 8 | //in PaymentsSignatureReply signatures MUST be omited 9 | //if signature field is present then response should be considered as invalid 10 | rpc GetListUnclaimed(GetPaymentsListRequest) returns (PaymentsListReply) {} 11 | 12 | //get list of all payments in progress 13 | rpc GetListInProgress(GetPaymentsListRequest) returns (PaymentsListReply) {} 14 | 15 | //initilize claim for specific channel 16 | rpc StartClaim(StartClaimRequest) returns (PaymentReply) {} 17 | } 18 | 19 | 20 | message GetPaymentsListRequest { 21 | //address of MultiPartyEscrow contract 22 | string mpe_address = 1; 23 | //current block number (signature will be valid only for short time around this block number) 24 | uint64 current_block = 2; 25 | //signature of the following message: 26 | //for GetListUnclaimed ("__list_unclaimed", mpe_address, current_block_number) 27 | //for GetListInProgress ("__list_in_progress", mpe_address, current_block_number) 28 | bytes signature = 3; 29 | } 30 | 31 | message StartClaimRequest { 32 | //address of MultiPartyEscrow contract 33 | string mpe_address = 1; 34 | //channel_id contains id of the channel which state is requested. 35 | bytes channel_id = 2; 36 | //signature of the following message ("__start_claim", mpe_address, channel_id, channel_nonce) 37 | bytes signature = 3; 38 | } 39 | 40 | message PaymentReply { 41 | bytes channel_id = 1; 42 | 43 | bytes channel_nonce = 2; 44 | 45 | bytes signed_amount = 3; 46 | 47 | //this filed must be OMITED in GetListUnclaimed request 48 | bytes signature = 4; 49 | } 50 | 51 | message PaymentsListReply { 52 | repeated PaymentReply payments = 1; 53 | } 54 | 55 | 56 | -------------------------------------------------------------------------------- /docs/source/generate_rst.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import argparse 4 | 5 | sys.path.insert(0, os.path.abspath('../../snet/cli')) 6 | 7 | from snet.cli import arguments 8 | 9 | 10 | def create_subcommand_rst(subcommand_contents, name): 11 | contents = subcommand_contents.replace("<>", name.title()).replace("<>", name) 12 | with open(name+".rst","w") as fp: 13 | fp.write(contents) 14 | fp.flush() 15 | fp.close() 16 | 17 | 18 | def generate_index_rst(index_contents, subcommand_contents): 19 | actions_list = arguments.get_parser().__getattribute__('_actions') 20 | sub_commands = list() 21 | for action in actions_list: 22 | if type(action) == argparse._SubParsersAction: 23 | choices = action.choices 24 | for key in choices: 25 | sub_commands.append(str(key).strip()) 26 | 27 | sub_commands.sort() 28 | for key in sub_commands: 29 | index_contents += " " + key + "\n" 30 | if not os.path.exists(key+".rst"): 31 | print("File does not exist " + key) 32 | create_subcommand_rst(subcommand_contents, key) 33 | 34 | with open("index.rst","w") as fp: 35 | fp.write(index_contents) 36 | fp.flush() 37 | fp.close() 38 | 39 | 40 | if not os.path.exists("index_template.tpl"): 41 | print("index template not found. Unable to proceed") 42 | exit(1); 43 | 44 | if not os.path.exists("subcommand_template.tpl"): 45 | print("subcommand_template template not found. Unable to proceed") 46 | exit(1); 47 | 48 | with open("index_template.tpl", "r") as fp: 49 | index_contents = fp.read() 50 | index_contents += "\n" 51 | 52 | with open("subcommand_template.tpl", "r") as fp: 53 | subcommand_contents = fp.read() 54 | 55 | generate_index_rst(index_contents, subcommand_contents) 56 | -------------------------------------------------------------------------------- /snet/cli/commands/sdk_command.py: -------------------------------------------------------------------------------- 1 | import os 2 | from pathlib import Path, PurePath 3 | 4 | from snet.cli.utils.utils import compile_proto, download_and_safe_extract_proto, check_training_in_proto 5 | from snet.cli.commands.mpe_service import MPEServiceCommand 6 | 7 | 8 | class SDKCommand(MPEServiceCommand): 9 | def generate_client_library(self): 10 | 11 | if os.path.isabs(self.args.protodir): 12 | client_libraries_base_dir_path = PurePath(self.args.protodir) 13 | else: 14 | cur_dir_path = PurePath(os.getcwd()) 15 | client_libraries_base_dir_path = cur_dir_path.joinpath(self.args.protodir) 16 | 17 | os.makedirs(client_libraries_base_dir_path, exist_ok=True) 18 | 19 | # Create service client libraries path 20 | library_org_id = self.args.org_id 21 | library_service_id = self.args.service_id 22 | 23 | library_dir_path = client_libraries_base_dir_path.joinpath(library_org_id, library_service_id, "python") 24 | 25 | metadata = self._get_service_metadata_from_registry() 26 | service_api_source = metadata.get("service_api_source") or metadata.get("model_ipfs_hash") 27 | 28 | # Receive proto files 29 | download_and_safe_extract_proto(service_api_source, library_dir_path, self._get_ipfs_client()) 30 | 31 | training_added = check_training_in_proto(library_dir_path) 32 | 33 | # Compile proto files 34 | compile_proto(Path(library_dir_path), library_dir_path, add_training = training_added) 35 | 36 | self._printout( 37 | 'client libraries for service with id "{}" in org with id "{}" generated at {}'.format(library_service_id, 38 | library_org_id, 39 | library_dir_path)) 40 | -------------------------------------------------------------------------------- /snet/cli/test/functional_tests/script14_full_stateless_logic.sh: -------------------------------------------------------------------------------- 1 | # Test get-channel-state 2 | # run daemon 3 | cd simple_daemon 4 | python test_simple_daemon.py & 5 | DAEMON=$! 6 | cd .. 7 | 8 | snet service metadata-init ./service_spec1/ ExampleService --fixed-price 0.0001 --endpoints 127.0.0.1:50051 --group-name group1 9 | snet account deposit 12345 -y -q 10 | snet organization metadata-init org1 testo individual 11 | snet organization add-group group1 0x52653A9091b5d5021bed06c5118D24b23620c529 127.0.0.1:50051 12 | 13 | snet organization create testo -y -q 14 | 15 | snet service publish testo tests -y -q 16 | snet channel open-init testo group1 1 +10days -yq 17 | snet channel print-initialized 18 | 19 | test_get_channel_state() { 20 | MPE_BALANCE=$(snet client get-channel-state 0 localhost:50051 | grep current_unspent_amount_in_cogs) 21 | test ${MPE_BALANCE##*=} = $1 22 | } 23 | 24 | test_get_channel_state 100000000 25 | 26 | snet client call testo tests group1 classify {} -y 27 | 28 | test_get_channel_state 99990000 29 | 30 | snet channel print-initialized 31 | snet --print-traceback treasurer claim-all --endpoint localhost:50051 --wallet-index 9 -yq 32 | 33 | test_get_channel_state 99990000 34 | 35 | snet client call testo tests group1 classify {} -y 36 | snet client call testo tests group1 classify {} -y 37 | 38 | test_get_channel_state 99970000 39 | 40 | # we will start claim of all channels but will not write them to blockchain 41 | echo n | snet treasurer claim-all --endpoint 127.0.0.1:50051 --wallet-index 9 && exit 1 || echo "fail as expected" 42 | 43 | test_get_channel_state 99970000 44 | snet client call testo tests group1 classify {} -y 45 | test_get_channel_state 99960000 46 | 47 | snet treasurer claim-all --endpoint 127.0.0.1:50051 --wallet-index 9 -yq 48 | test_get_channel_state 99960000 49 | snet client call testo tests group1 classify {} -y 50 | 51 | test_get_channel_state 99950000 52 | 53 | kill $DAEMON 54 | -------------------------------------------------------------------------------- /snet/cli/commands/mpe_account.py: -------------------------------------------------------------------------------- 1 | from snet.cli.commands.commands import BlockchainCommand 2 | from snet.cli.utils.token2cogs import cogs2strtoken 3 | 4 | 5 | class MPEAccountCommand(BlockchainCommand): 6 | 7 | def print_account(self): 8 | self.check_ident() 9 | self._printout(self.ident.address) 10 | 11 | def print_token_and_mpe_balances(self): 12 | """ Print balance of ETH, ASI(FET), and MPE wallet """ 13 | self.check_ident() 14 | if self.args.account: 15 | account = self.args.account 16 | else: 17 | account = self.ident.address 18 | eth_wei = self.w3.eth.get_balance(account) 19 | token_cogs = self.call_contract_command("FetchToken", "balanceOf", [account]) 20 | mpe_cogs = self.call_contract_command("MultiPartyEscrow", "balances", [account]) 21 | 22 | # we cannot use _pprint here because it doesn't conserve order yet 23 | self._printout(" account: %s"%account) 24 | self._printout(" ETH: %s"%self.w3.from_wei(eth_wei, 'ether')) 25 | self._printout(" ASI(FET): %s"%cogs2strtoken(token_cogs)) 26 | self._printout(" MPE: %s"%cogs2strtoken(mpe_cogs)) 27 | 28 | def deposit_to_mpe(self): 29 | self.check_ident() 30 | amount = self.args.amount 31 | mpe_address = self.get_mpe_address() 32 | 33 | already_approved = self.call_contract_command("FetchToken", "allowance", [self.ident.address, mpe_address]) 34 | if already_approved < amount: 35 | self.transact_contract_command("FetchToken", "approve", [mpe_address, amount]) 36 | self.transact_contract_command("MultiPartyEscrow", "deposit", [amount]) 37 | 38 | def withdraw_from_mpe(self): 39 | self.check_ident() 40 | self.transact_contract_command("MultiPartyEscrow", "withdraw", [self.args.amount]) 41 | 42 | def transfer_in_mpe(self): 43 | self.check_ident() 44 | self.transact_contract_command("MultiPartyEscrow", "transfer", [self.args.receiver, self.args.amount]) 45 | -------------------------------------------------------------------------------- /snet/cli/test/functional_tests/mint/mint.py: -------------------------------------------------------------------------------- 1 | from snet.contracts import get_contract_object 2 | 3 | from packages.snet_cli.snet.snet_cli.utils.utils import get_web3 4 | from web3.gas_strategies.time_based import medium_gas_price_strategy 5 | 6 | TRANSACTION_TIMEOUT = 500 7 | DEFAULT_GAS = 300000 8 | HTTP_PROVIDER = "http://localhost:8545" 9 | 10 | wallet_address_1 = "0x592E3C0f3B038A0D673F19a18a773F993d4b2610" 11 | contract_address = "0x6e5f20669177f5bdf3703ec5ea9c4d4fe3aabd14" 12 | signer_private_key = ( 13 | "0xc71478a6d0fe44e763649de0a0deb5a080b788eefbbcf9c6f7aef0dd5dbd67e0" 14 | ) 15 | 16 | initialNonce = 0 17 | mint_amount = 10000000000000000 18 | 19 | 20 | def _get_nonce(web3, address): 21 | nonce = web3.eth.get_transaction_count(address) 22 | if initialNonce >= nonce: 23 | nonce = initialNonce + 1 24 | nonce = nonce 25 | return nonce 26 | 27 | 28 | def send_transaction(web3, contract_fn, *args): 29 | txn_hash = _send_signed_transaction(web3, contract_fn, *args) 30 | return web3.eth.wait_for_transaction_receipt(txn_hash, TRANSACTION_TIMEOUT) 31 | 32 | 33 | def _send_signed_transaction(web3, wallet_address, contract_fn, *args): 34 | transaction = contract_fn(*args).buildTransaction( 35 | { 36 | "chainId": int(web3.version.network), 37 | "gas": DEFAULT_GAS, 38 | "gasPrice": web3.eth.gas_price * 4 / 3, 39 | "nonce": _get_nonce(web3, wallet_address), 40 | } 41 | ) 42 | 43 | signed_txn = web3.eth.account.sign_transaction( 44 | transaction, private_key=signer_private_key 45 | ) 46 | return web3.to_hex(web3.eth.send_raw_transaction(signed_txn.rawTransaction)) 47 | 48 | 49 | def mint_token(): 50 | w3 = get_web3(HTTP_PROVIDER) 51 | address_1 = w3.to_checksum_address(wallet_address_1) 52 | contract = get_contract_object( 53 | w3, contract_file="SingularityNetToken.json", address=contract_address 54 | ) 55 | 56 | send_transaction( 57 | w3, wallet_address_1, contract.functions.mint, address_1, int(mint_amount) 58 | ) 59 | 60 | 61 | if __name__ == "__main__": 62 | mint_token() 63 | -------------------------------------------------------------------------------- /snet/cli/test/functional_tests/script5_identity1_rpc_mnemonic.sh: -------------------------------------------------------------------------------- 1 | # test itentities managment. 2 | 3 | # This is first and second ganache identity 4 | A0=0x592E3C0f3B038A0D673F19a18a773F993d4b2610 5 | A1=0x3b2b3C2e2E7C93db335E69D827F3CC4bC2A2A2cB 6 | 7 | assert_mpe_balance () { 8 | MPE_BALANCE=$(snet account balance --account $1 |grep MPE) 9 | test ${MPE_BALANCE##*:} = $2 10 | } 11 | 12 | 13 | # this should fail because snet-user should be in use now 14 | snet identity delete snet-user && exit 1 || echo "fail as expected" 15 | 16 | snet identity create key0 key --private-key 0xc71478a6d0fe44e763649de0a0deb5a080b788eefbbcf9c6f7aef0dd5dbd67e0 --network local 17 | 18 | snet identity create rpc0 rpc --network local 19 | snet identity rpc0 20 | 21 | # switch to main net (rpc0 is bind to the local network!) 22 | snet network mainnet 23 | snet account balance && exit 1 || echo "fail as expected" 24 | snet network local 25 | 26 | 27 | snet account deposit 100 -y 28 | assert_mpe_balance $A0 100 29 | 30 | # A0 -> A1 42 31 | snet account transfer $A1 42 -y 32 | assert_mpe_balance $A1 42 33 | 34 | snet identity create rpc1 rpc --network local --wallet-index 1 35 | snet identity rpc1 36 | 37 | # A1 -> A0 1 38 | snet account transfer $A0 1 -y 39 | assert_mpe_balance $A1 41 40 | 41 | # A0 -> A1 2 42 | snet account transfer $A1 2 --wallet-index 0 -y 43 | assert_mpe_balance $A1 43 44 | 45 | snet identity create mne0 mnemonic --network local --mnemonic "a b c d" 46 | snet identity mne0 47 | M0=`snet account print` 48 | M1=`snet account print --wallet-index 1` 49 | 50 | snet identity create mne1 mnemonic --wallet-index 1 --network local --mnemonic "a b c d" 51 | snet identity mne1 52 | M11=`snet account print` 53 | 54 | test $M1 = $M11 55 | 56 | snet identity rpc0 57 | 58 | # A0 -> M1 0.1 59 | snet account transfer $M1 0.1 -y 60 | assert_mpe_balance $M1 0.1 61 | 62 | # A0 -> M1 0.2 63 | snet identity key0 64 | snet account transfer $M1 0.1 -y 65 | assert_mpe_balance $M1 0.2 66 | 67 | snet identity snet-user 68 | snet identity delete rpc0 69 | snet identity rpc0 && exit 1 || echo "fail as expected" 70 | 71 | snet identity delete rpc1 72 | snet identity delete mne0 73 | snet identity delete mne1 74 | -------------------------------------------------------------------------------- /snet/cli/test/functional_tests/test_entry_point.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import os 3 | 4 | 5 | class TestEntryPoint(unittest.TestCase): 6 | def test_help(self): 7 | exit_status = os.system('snet --help') 8 | self.assertEqual(0, exit_status) 9 | 10 | def test_identity(self): 11 | exit_status = os.system('snet identity list') 12 | self.assertEqual(0, exit_status) 13 | 14 | def test_account(self): 15 | exit_status = os.system('snet account -h') 16 | self.assertEqual(0, exit_status) 17 | 18 | def test_channel(self): 19 | exit_status = os.system('snet channel -h') 20 | self.assertEqual(0, exit_status) 21 | 22 | def test_client(self): 23 | exit_status = os.system('snet client -h') 24 | self.assertEqual(0, exit_status) 25 | 26 | def test_contract(self): 27 | exit_status = os.system('snet contract -h') 28 | self.assertEqual(0, exit_status) 29 | 30 | def test_network(self): 31 | exit_status = os.system('snet network list') 32 | self.assertEqual(0, exit_status) 33 | 34 | def test_organization(self): 35 | exit_status = os.system('snet organization -h') 36 | self.assertEqual(0, exit_status) 37 | 38 | def test_sdk(self): 39 | exit_status = os.system('snet sdk -h') 40 | self.assertEqual(0, exit_status) 41 | 42 | def test_service(self): 43 | exit_status = os.system('snet service -h') 44 | self.assertEqual(0, exit_status) 45 | 46 | def test_session(self): 47 | exit_status = os.system('snet session -h') 48 | self.assertEqual(0, exit_status) 49 | 50 | def test_set(self): 51 | exit_status = os.system('snet set -h') 52 | self.assertEqual(0, exit_status) 53 | 54 | def test_treasurer(self): 55 | exit_status = os.system('snet treasurer -h') 56 | self.assertEqual(0, exit_status) 57 | 58 | def test_unset(self): 59 | exit_status = os.system('snet unset -h') 60 | self.assertEqual(0, exit_status) 61 | 62 | def test_version(self): 63 | exit_status = os.system('snet version') 64 | self.assertEqual(0, exit_status) 65 | 66 | 67 | if __name__ == '__main__': 68 | unittest.main() 69 | -------------------------------------------------------------------------------- /snet/cli/test/functional_tests/script7_contracts.sh: -------------------------------------------------------------------------------- 1 | # Test "snet contracts" 2 | 3 | snet contract Registry --at 0x4e74fefa82e83e0964f0d9f53c68e03f7298a8b2 createOrganization ExampleOrganization1 ExampleOrganization1 '["0x3b2b3C2e2E7C93db335E69D827F3CC4bC2A2A2cB"]' --transact -y 4 | snet contract Registry createOrganization ExampleOrganization2 ExampleOrganization2 '["0x3b2b3C2e2E7C93db335E69D827F3CC4bC2A2A2cB","0x0067b427E299Eb2A4CBafc0B04C723F77c6d8a18"]' --transact -y 5 | snet organization list 2>&1 | grep ExampleOrganization1 6 | snet organization list 2>&1 | grep ExampleOrganization2 7 | 8 | # organization ExampleOrganization2 should alread have two given members 9 | snet organization add-members ExampleOrganization2 0x3b2b3C2e2E7C93db335E69D827F3CC4bC2A2A2cB,0x0067b427E299Eb2A4CBafc0B04C723F77c6d8a18 -y 2>&1 | grep "No member was added" 10 | 11 | # deposit 1000000 cogs to MPE from the first address (0x592E3C0f3B038A0D673F19a18a773F993d4b2610) 12 | snet contract SingularityNetToken --at 0x6e5f20669177f5bdf3703ec5ea9c4d4fe3aabd14 approve 0x5C7a4290F6F8FF64c69eEffDFAFc8644A4Ec3a4E 1000000 --transact -y 13 | snet contract MultiPartyEscrow --at 0x5C7a4290F6F8FF64c69eEffDFAFc8644A4Ec3a4E deposit 1000000 --transact -y 14 | 15 | # deposit 1000000 cogs to MPE from the first address (0x592E3C0f3B038A0D673F19a18a773F993d4b2610) 16 | snet contract SingularityNetToken approve 0x5C7a4290F6F8FF64c69eEffDFAFc8644A4Ec3a4E 1000000 --transact -y 17 | snet contract MultiPartyEscrow deposit 1000000 --transact -y 18 | 19 | # check balance of the First account in MPE (it should be 2000000 cogs) 20 | REZ=`snet contract MultiPartyEscrow --at 0x5c7a4290f6f8ff64c69eeffdfafc8644a4ec3a4e balances 0x592E3C0f3B038A0D673F19a18a773F993d4b2610` 21 | test $REZ = 2000000 22 | 23 | # We set expiration as current_block - 1 24 | EXPIRATION=$((`snet channel block-number` - 1)) 25 | snet contract MultiPartyEscrow --at 0x5c7a4290f6f8ff64c69eeffdfafc8644a4ec3a4e openChannel 0x3b2b3C2e2E7C93db335E69D827F3CC4bC2A2A2cB 0x0067b427E299Eb2A4CBafc0B04C723F77c6d8a18 0 420000 $EXPIRATION --transact -y 26 | 27 | REZ=`snet contract MultiPartyEscrow nextChannelId` 28 | test $REZ = 1 29 | 30 | #We can immediately claim timeout because expiration was set in the past 31 | snet contract MultiPartyEscrow --at 0x5c7a4290f6f8ff64c69eeffdfafc8644a4ec3a4e channelClaimTimeout 0 --transact -y -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | from pathlib import Path 3 | from setuptools import find_namespace_packages, setup 4 | from setuptools.command.develop import develop as _develop 5 | from setuptools.command.install import install as _install 6 | 7 | from snet.cli.utils.utils import compile_proto 8 | from version import __version__ 9 | 10 | PACKAGE_NAME = 'snet-cli' 11 | 12 | 13 | this_directory = os.path.abspath(os.path.dirname(__file__)) 14 | with open(os.path.join(this_directory, 'README.md'), encoding='utf-8') as f: 15 | long_description = f.read() 16 | 17 | 18 | with open("./requirements.txt") as f: 19 | requirements_str = f.read() 20 | requirements = requirements_str.split("\n") 21 | 22 | 23 | def install_and_compile_proto(): 24 | proto_dir = Path(__file__).absolute().parent.joinpath( 25 | "snet", "cli", "resources", "proto") 26 | print(proto_dir, "->", proto_dir) 27 | for fn in proto_dir.glob('*.proto'): 28 | print("Compiling protobuf", fn) 29 | compile_proto(proto_dir, proto_dir, proto_file=fn) 30 | 31 | 32 | class develop(_develop): 33 | """Post-installation for development mode.""" 34 | 35 | def run(self): 36 | _develop.run(self) 37 | self.execute(install_and_compile_proto, (), 38 | msg="Compile protocol buffers") 39 | 40 | 41 | class install(_install): 42 | """Post-installation for installation mode.""" 43 | 44 | def run(self): 45 | _install.run(self) 46 | self.execute(install_and_compile_proto, (), 47 | msg="Compile protocol buffers") 48 | 49 | 50 | setup( 51 | name=PACKAGE_NAME, 52 | version=__version__, 53 | packages=find_namespace_packages(include=['snet*']), 54 | url='https://github.com/singnet/snet-cli', 55 | author="SingularityNET Foundation", 56 | author_email="info@singularitynet.io", 57 | description="SingularityNET CLI", 58 | long_description=long_description, 59 | long_description_content_type='text/markdown', 60 | license="MIT", 61 | python_requires='>=3.10', 62 | install_requires=requirements, 63 | include_package_data=True, 64 | cmdclass={ 65 | 'develop': develop, 66 | 'install': install, 67 | }, 68 | entry_points={ 69 | 'console_scripts': [ 70 | 'snet = snet.cli:main' 71 | ], 72 | } 73 | ) 74 | -------------------------------------------------------------------------------- /snet/cli/test/utils/reset_environment.sh: -------------------------------------------------------------------------------- 1 | # This is a part of circleci functional tests 2 | # This script does following: 3 | # - restart ipfs 4 | # - restart ganache and remigrate platform-contracts 5 | # - set correct networks/*json for Registry and MultiPartyEscrow (but not for SingularityNetToken !) 6 | # - reset .snet configuration 7 | # - add snet-user to snet-cli with first ganache idenity 8 | 9 | if [ ! $1 = "--i-no-what-i-am-doing" ]; then 10 | echo "This script is intended to be run from circleci" 11 | exit 1 12 | fi 13 | 14 | cwd=$(pwd) 15 | 16 | # I. restart ipfs 17 | ipfs shutdown || echo "supress an error" 18 | 19 | rm -rf ~/.ipfs 20 | ipfs init 21 | ipfs bootstrap rm --all 22 | ipfs config Addresses.API /ip4/127.0.0.1/tcp/5002 23 | ipfs config Addresses.Gateway /ip4/0.0.0.0/tcp/8081 24 | nohup ipfs daemon >ipfs.log 2>&1 & 25 | 26 | # II. restart ganache and remigrate platform-contracts 27 | killall node || echo "supress an error" 28 | 29 | # cd ../platform-contracts 30 | # nohup ./node_modules/.bin/ganache-cli --mnemonic 'gauge enact biology destroy normal tunnel slight slide wide sauce ladder produce' --networkId 829257324 >/dev/null & 31 | # ./node_modules/.bin/truffle migrate --network local 32 | 33 | # III. remove old snet-cli configuration 34 | rm -rf ~/.snet 35 | 36 | # IV. Configure SNET-CLI. 37 | 38 | # set correct ipfs endpoint 39 | # (the new new configuration file with default values will be created automatically) 40 | # snet set default_ipfs_endpoint http://localhost:5002 41 | 42 | # Add local network and switch to it 43 | # snet network create local http://localhost:8545 44 | 45 | # swith to local network 46 | # snet network local 47 | 48 | # Configure contract addresses for local network (it will not be necessary for goerli or mainnet! ) 49 | # snet set current_singularitynettoken_at 0x6e5f20669177f5bdf3703ec5ea9c4d4fe3aabd14 50 | # snet set current_registry_at 0x4e74fefa82e83e0964f0d9f53c68e03f7298a8b2 51 | # snet set current_multipartyescrow_at 0x5c7a4290f6f8ff64c69eeffdfafc8644a4ec3a4e 52 | 53 | # Create First identity (snet-user = first ganache). 54 | # (snet will automatically swith to this new identity) 55 | # snet identity create snet-user rpc --network local 56 | snet identity create --private-key "$SNET_TEST_WALLET_PRIVATE_KEY" test key --network sepolia 57 | sed -i "s/$FORMER_SNET_TEST_INFURA_KEY/$SNET_TEST_INFURA_KEY/g" ~/.snet/config 58 | snet session 59 | export PYTHONPATH=$cwd 60 | python $cwd"./snet/cli/test/functional_tests/mint/mint.py" 61 | snet account balance 62 | -------------------------------------------------------------------------------- /.github/workflows/master.yml: -------------------------------------------------------------------------------- 1 | name: tests_master 2 | on: 3 | # push: 4 | # branches: [ "master" ] 5 | pull_request: 6 | branches: [ "master" ] 7 | workflow_dispatch: 8 | 9 | jobs: 10 | run_tests_master: 11 | runs-on: ubuntu-latest 12 | container: node:20-bookworm 13 | steps: 14 | 15 | - name: install packs 16 | run: | 17 | apt update 18 | apt install -y libudev-dev libusb-1.0-0-dev curl jq 19 | apt install -y python3-pip python3.11-venv 20 | 21 | # - name: install ipfs 22 | # run: | 23 | # wget https://dist.ipfs.io/go-ipfs/v0.9.0/go-ipfs_v0.9.0_linux-amd64.tar.gz 24 | # tar -xvzf go-ipfs_v0.9.0_linux-amd64.tar.gz 25 | # bash go-ipfs/install.sh 26 | # ipfs --version 27 | # node --version 28 | 29 | - name: clone repo 30 | uses: actions/checkout@v3 31 | 32 | - name: install pip packages 33 | run: | 34 | pip3 install -r requirements.txt --break-system-packages 35 | # pip3 install nose --break-system-packages 36 | # pip3 uninstall pyreadline --break-system-packages 37 | # pip3 install pyreadline3 --break-system-packages 38 | 39 | - name: install snet-cli 40 | run: | 41 | # ./scripts/blockchain install 42 | pip3 install . --break-system-packages 43 | 44 | # - name: install platform-contracts 45 | # run: | 46 | # cd .. 47 | # git clone https://github.com/singnet/platform-contracts.git 48 | # cd platform-contracts 49 | # npm install 50 | # npm install ganache-cli 51 | # npm run-script compile 52 | 53 | # - name: build example service 54 | # run: | 55 | # git clone https://github.com/singnet/example-service.git 56 | # cd example-service 57 | # pip3 install -r requirements.txt --break-system-packages 58 | # sh buildproto.sh 59 | 60 | # - name: unit tests 61 | # run: | 62 | # cd ./snet/cli/test 63 | # nosetests -v --with-doctest 64 | 65 | - name: functional tests for cli 66 | run: | 67 | export SNET_TEST_WALLET_PRIVATE_KEY=${{ secrets.PRIV_KEY }} 68 | export SNET_TEST_INFURA_KEY=${{ secrets.INF_KEY }} 69 | export FORMER_SNET_TEST_INFURA_KEY=${{ secrets.FORM_INF_KEY }} 70 | export PIP_BREAK_SYSTEM_PACKAGES=1 71 | export PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python 72 | # sh -ex ./snet/cli/test/utils/run_all_functional.sh 73 | python3 ./snet/cli/test/functional_tests/test_entry_point.py 74 | -------------------------------------------------------------------------------- /scripts/blockchain: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import pathlib 5 | import shutil 6 | import subprocess 7 | import sys 8 | 9 | 10 | def main(): 11 | assert len(sys.argv) > 1, "please select a target from 'install', 'uninstall'" 12 | target = sys.argv[1] 13 | cur_dir = pathlib.Path(__file__).absolute().parent 14 | blockchain_dir = cur_dir.parent.joinpath("blockchain") 15 | node_modules_dir = blockchain_dir.joinpath("node_modules") 16 | platform_json_src_dir = node_modules_dir.joinpath("singularitynet-platform-contracts") 17 | token_json_src_dir = node_modules_dir.joinpath("singularitynet-token-contracts") 18 | token_contract_name = "SingularityNetToken" 19 | contract_json_dest_dir = cur_dir.parent.joinpath("snet", "cli", "resources", "contracts") 20 | abi_contract_names = ["Registry", "MultiPartyEscrow"] 21 | networks_contract_names = ["Registry", "MultiPartyEscrow"] 22 | 23 | npm_location = shutil.which('npm') 24 | if not npm_location: 25 | raise Exception("This script requires 'npm' to be installed and in your PATH") 26 | 27 | if target == "install": 28 | #shutil.rmtree(contract_json_dest_dir) 29 | 30 | subprocess.call([npm_location, "install"], cwd=blockchain_dir) 31 | 32 | os.makedirs(contract_json_dest_dir.joinpath("abi"), exist_ok=True) 33 | os.makedirs(contract_json_dest_dir.joinpath("networks"), exist_ok=True) 34 | 35 | for contract_name in abi_contract_names: 36 | shutil.copy(platform_json_src_dir.joinpath("abi", "{}.json".format(contract_name)), 37 | contract_json_dest_dir.joinpath("abi", "{}.json".format(contract_name))) 38 | for contract_name in networks_contract_names: 39 | shutil.copy(platform_json_src_dir.joinpath("networks", "{}.json".format(contract_name)), 40 | contract_json_dest_dir.joinpath("networks", "{}.json".format(contract_name))) 41 | 42 | shutil.copy(token_json_src_dir.joinpath("abi", "{}.json".format(token_contract_name)), 43 | contract_json_dest_dir.joinpath("abi", "{}.json".format(token_contract_name))) 44 | shutil.copy(token_json_src_dir.joinpath("networks", "{}.json".format(token_contract_name)), 45 | contract_json_dest_dir.joinpath("networks", "{}.json".format(token_contract_name))) 46 | elif target == "uninstall": 47 | try: 48 | shutil.rmtree(node_modules_dir) 49 | shutil.rmtree(contract_json_dest_dir.joinpath("abi")) 50 | shutil.rmtree(contract_json_dest_dir.joinpath("networks")) 51 | except FileNotFoundError: 52 | pass 53 | 54 | 55 | if __name__ == "__main__": 56 | main() 57 | -------------------------------------------------------------------------------- /.github/workflows/dev.yml: -------------------------------------------------------------------------------- 1 | name: tests_development 2 | on: 3 | # push: 4 | # branches: [ "development" ] 5 | pull_request: 6 | branches: [ "development" ] 7 | workflow_dispatch: 8 | 9 | jobs: 10 | run_tests_development: 11 | runs-on: ubuntu-latest 12 | container: node:20-bookworm 13 | steps: 14 | 15 | - name: install packs 16 | run: | 17 | apt update 18 | apt install -y libudev-dev libusb-1.0-0-dev curl jq 19 | apt install -y python3-pip python3.11-venv 20 | 21 | # - name: install ipfs 22 | # run: | 23 | # wget https://dist.ipfs.io/go-ipfs/v0.9.0/go-ipfs_v0.9.0_linux-amd64.tar.gz 24 | # tar -xvzf go-ipfs_v0.9.0_linux-amd64.tar.gz 25 | # bash go-ipfs/install.sh 26 | # ipfs --version 27 | # node --version 28 | 29 | - name: clone repo 30 | uses: actions/checkout@v3 31 | 32 | - name: install pip packages 33 | run: | 34 | pip3 install -r requirements.txt --break-system-packages 35 | # pip3 install nose --break-system-packages 36 | # pip3 uninstall pyreadline --break-system-packages 37 | # pip3 install pyreadline3 --break-system-packages 38 | 39 | - name: install snet-cli 40 | run: | 41 | # ./scripts/blockchain install 42 | pip3 install . --break-system-packages 43 | 44 | # - name: install platform-contracts 45 | # run: | 46 | # cd .. 47 | # git clone https://github.com/singnet/platform-contracts.git 48 | # cd platform-contracts 49 | # npm install 50 | # npm install ganache-cli 51 | # npm run-script compile 52 | 53 | # - name: build example service 54 | # run: | 55 | # git clone https://github.com/singnet/example-service.git 56 | # cd example-service 57 | # pip3 install -r requirements.txt --break-system-packages 58 | # sh buildproto.sh 59 | 60 | # - name: unit tests 61 | # run: | 62 | # cd ./snet/cli/test 63 | # nosetests -v --with-doctest 64 | 65 | - name: functional tests for cli 66 | run: | 67 | export SNET_TEST_WALLET_PRIVATE_KEY=${{ secrets.PRIV_KEY }} 68 | export SNET_TEST_INFURA_KEY=${{ secrets.INF_KEY }} 69 | export FORMER_SNET_TEST_INFURA_KEY=${{ secrets.FORM_INF_KEY }} 70 | export PIP_BREAK_SYSTEM_PACKAGES=1 71 | export PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=python 72 | # sh -ex ./snet/cli/test/utils/run_all_functional.sh 73 | python3 ./snet/cli/test/functional_tests/test_entry_point.py 74 | python3 ./snet/cli/test/functional_tests/func_tests.py 75 | -------------------------------------------------------------------------------- /snet/cli/test/functional_tests/script13_call_reinitialize.sh: -------------------------------------------------------------------------------- 1 | # script13 2 | 3 | # run daemon 4 | cd simple_daemon 5 | python test_simple_daemon.py & 6 | DAEMON=$! 7 | cd .. 8 | 9 | snet service metadata-init ./service_spec1/ ExampleService --fixed-price 0.0001 --endpoints 127.0.0.1:50051 --group-name group1 10 | snet account deposit 12345 -y -q 11 | snet organization metadata-init org1 testo individual 12 | snet organization add-group group1 0x52653A9091b5d5021bed06c5118D24b23620c529 127.0.0.1:50051 13 | 14 | snet organization create testo -y -q 15 | 16 | snet service publish testo tests -y -q 17 | snet --print-traceback service print-service-status testo tests 18 | snet --print-traceback channel open-init testo group1 1 +10days -yq 19 | 20 | snet --print-traceback client call testo tests group1 classify {} -y 21 | 22 | rm -rf ~/.snet/mpe_client 23 | 24 | snet client call testo tests group1 classify {} -y 25 | snet --print-traceback client call testo tests group1 classify {} -y --skip-update-check 26 | 27 | # we will corrupt initialized channel 28 | rm -rf ~/.snet/mpe_client/*/testo/tests/service/*py 29 | rm -rf ~/.snet/mpe_client/*/testo/channel* 30 | # in the current version snet-cli cannot detect this problem, so it should fail 31 | # and it is ok, because it shoudn't update service at each call 32 | snet client call testo tests classify group1 {} -y && exit 1 || echo "fail as expected" 33 | 34 | snet service metadata-add-endpoints group1 localhost:50051 35 | 36 | # this should still fail because we skip registry check 37 | snet client call testo tests group1 classify {} -y --skip-update-check && exit 1 || echo "fail as expected" 38 | 39 | snet service update-metadata testo tests -yq 40 | 41 | # no snet-cli should automatically update service, because metadataURI has changed 42 | snet --print-traceback client call testo tests group1 classify {} -y 43 | 44 | # multiply payment groups case 45 | # multiply payment groups case 46 | snet service metadata-init ./service_spec1/ ExampleService --fixed-price 0.0001 --endpoints 127.0.0.1:50051 --group-name group1 47 | 48 | snet organization add-group group2 0x52653A9091b5d5021bed06c5118D24b23620c529 127.0.0.1:50051 49 | snet organization update-metadata testo -yq 50 | snet --print-traceback service publish testo tests2 -y -q 51 | snet service print-service-status testo tests2 52 | snet --print-traceback client call testo tests2 group2 classify {} -y && exit 1 || echo "fail as expected" 53 | 54 | snet service metadata-add-group group2 55 | snet service metadata-set-fixed-price group2 0.0001 56 | snet service metadata-add-endpoints group2 127.0.0.1:50051 57 | snet service update-metadata testo tests2 -y 58 | 59 | snet --print-traceback channel open-init testo group2 1 +10days -yq 60 | snet --print-traceback client call testo tests2 group2 classify {} -y 61 | 62 | rm -rf ~/.snet/mpe_client 63 | 64 | kill $DAEMON 65 | -------------------------------------------------------------------------------- /snet/cli/resources/proto/token_service.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package escrow; 4 | 5 | option java_package = "io.singularitynet.daemon.escrow"; 6 | //It is expected that the user would call the GetChannelState to Determine the Current state of the Channel 7 | //Based on the usage forecast, the user/client will have to sign for an amount L + U , where L is the last amount Signed 8 | //and U is the amount based on expected usage. 9 | //Please be aware that the Signing up an amount upfront ( Pre Paid) does come with a risk and hence the 10 | //user must exercise caution on the amount signed specially with new service providers. 11 | //If there is no need of making concurrent calls then you may consider pay per mode. 12 | //Using a Token, the Client can now make concurrent calls, which was not supported previously with the pay per mode. 13 | //However the pay per mode is a lot secure than the pre-paid mode. 14 | service TokenService { 15 | // GetToken method checks the Signature sent and returns a Token 16 | // 1) The Signature is valid and has to be signed in the below format 17 | //"__MPE_claim_message"+MpeContractAddress+ChannelID+ChannelNonce+SignedAmount 18 | //Signature is to let the Service Provider make a claim 19 | // 2) Signed amount >= Last amount Signed. 20 | // if Signed amount == Last Signed amount , then check if planned_amount < used_amount 21 | // if Signed amount > Last Signed amount , then update the planned amount = Signed Amount 22 | // GetToken method in a way behaves as a renew Token too!. 23 | rpc GetToken(TokenRequest) returns (TokenReply) {} 24 | 25 | 26 | 27 | } 28 | 29 | // TokenRequest is a request for getting a valid token. 30 | message TokenRequest { 31 | // channel_id contains id of the channel which state is requested. 32 | uint64 channel_id = 1; 33 | // current_nonce is a latest nonce of the payment channel. 34 | uint64 current_nonce = 2; 35 | //signed_amount is the amount signed by client with current_nonce 36 | uint64 signed_amount = 3; 37 | // Signature is a client signature of the message which contains 2 parts 38 | //Part 1 : MPE Signature "__MPE_claim_message"+MpeContractAddress+ChannelID+ChannelNonce+SignedAmount 39 | //Part 2 : Current Block Number 40 | bytes signature = 4; 41 | //current block number (signature will be valid only for short time around this block number) 42 | uint64 current_block = 5; 43 | 44 | bytes claim_signature = 6; 45 | 46 | } 47 | 48 | // TokenReply message contains a latest channel state. current_nonce and 49 | message TokenReply { 50 | // current_nonce is a latest nonce of the payment channel. 51 | uint64 channel_id = 1; 52 | 53 | //it could be absent if none message was signed with current_nonce 54 | string token = 2; 55 | 56 | //If the client / user chooses to sign upfront , the planned amount in cogs will be indicative of this. 57 | uint64 planned_amount = 3; 58 | 59 | //If the client / user chooses to sign upfront , the used amount in cogs will be indicative of how much of the 60 | //planned amount has actually been used. 61 | uint64 used_amount = 4; 62 | 63 | } 64 | -------------------------------------------------------------------------------- /snet/cli/resources/proto/state_service.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package escrow; 4 | 5 | option java_package = "io.singularitynet.daemon.escrow"; 6 | 7 | // PaymentChannelStateService contains methods to get the MultiPartyEscrow 8 | // payment channel state. 9 | // channel_id, channel_nonce, value and amount fields below in fact are 10 | // Solidity uint256 values. Which are big-endian integers, see 11 | // https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI#formal-specification-of-the-encoding 12 | // These values may be or may be not padded by zeros, service supports both 13 | // options. 14 | service PaymentChannelStateService { 15 | // GetChannelState method returns a channel state by channel id. 16 | rpc GetChannelState(ChannelStateRequest) returns (ChannelStateReply) {} 17 | } 18 | 19 | // ChanelStateRequest is a request for channel state. 20 | message ChannelStateRequest { 21 | // channel_id contains id of the channel which state is requested. 22 | bytes channel_id = 1; 23 | 24 | // signature is a client signature of the message which contains 25 | // channel_id. It is used for client authorization. 26 | bytes signature = 2; 27 | 28 | //current block number (signature will be valid only for short time around this block number) 29 | uint64 current_block = 3; 30 | } 31 | 32 | // ChannelStateReply message contains a latest channel state. current_nonce and 33 | // current_value fields can be different from ones stored in the blockchain if 34 | // server started withdrawing funds froms channel but transaction is still not 35 | // finished. 36 | message ChannelStateReply { 37 | // current_nonce is a latest nonce of the payment channel. 38 | bytes current_nonce = 1; 39 | 40 | // current_signed_amount is a last amount which were signed by client with current_nonce 41 | //it could be absent if none message was signed with current_nonce 42 | bytes current_signed_amount = 2; 43 | 44 | // current_signature is a last signature sent by client with current_nonce 45 | // it could be absent if none message was signed with current nonce 46 | bytes current_signature = 3; 47 | 48 | // last amount which was signed by client with nonce=current_nonce - 1 49 | bytes old_nonce_signed_amount = 4; 50 | 51 | // last signature sent by client with nonce = current_nonce - 1 52 | bytes old_nonce_signature = 5; 53 | } 54 | 55 | //Used to determine free calls available for a given user. 56 | service FreeCallStateService { 57 | rpc GetFreeCallsAvailable(FreeCallStateRequest) returns (FreeCallStateReply) {} 58 | } 59 | 60 | message FreeCallStateRequest { 61 | //Has the user email id 62 | string user_id = 1; 63 | //signer-token = (user@mail, user-public-key, token_issue_date), this is generated my Market place Dapp 64 | //to leverage free calls from SDK/ snet-cli, you will need this signer-token to be downloaded from Dapp 65 | bytes token_for_free_call = 2; 66 | //Token expiration date in Block number 67 | uint64 token_expiry_date_block = 3 ; 68 | //Signature is made up of the below, user signs with the private key corresponding with the public key used to generate the authorized token 69 | //free-call-metadata = ("__prefix_free_trial",user_id,organization_id,service_id,group_id,current_block,authorized_token) 70 | bytes signature = 4; 71 | //current block number (signature will be valid only for short time around this block number) 72 | uint64 current_block = 5; 73 | 74 | } 75 | 76 | message FreeCallStateReply { 77 | //Has the user email id 78 | string user_id = 1; 79 | //Balance number of free calls available 80 | uint64 free_calls_available = 2; 81 | } 82 | -------------------------------------------------------------------------------- /snet/cli/utils/proto_utils.py: -------------------------------------------------------------------------------- 1 | """ Utils related to protobuf """ 2 | import sys 3 | from pathlib import Path 4 | import os 5 | 6 | from google.protobuf import json_format 7 | 8 | 9 | def import_protobuf_from_dir(proto_dir, method_name, service_name=None): 10 | """ 11 | Dynamic import of grpc-protobuf from given directory (proto_dir) 12 | service_name should be provided only in the case of conflicting method names (two methods with the same name in difference services). 13 | Return stub_class, request_class, response_class 14 | ! We need response_class only for json payload encoding ! 15 | """ 16 | proto_dir = Path(proto_dir) 17 | # _pb2_grpc.py import _pb2.py so we are forced to add proto_dir to path 18 | sys.path.append(str(proto_dir)) 19 | grpc_py_files = [str(os.path.basename(p)) for p in proto_dir.glob("*_pb2_grpc.py")] 20 | 21 | good_rez = [] 22 | for grpc_py_file in grpc_py_files: 23 | is_found, rez = _import_protobuf_from_file(grpc_py_file, method_name, service_name) 24 | if is_found: 25 | good_rez.append(rez) 26 | if len(good_rez) == 0: 27 | raise Exception("Error while loading protobuf. Cannot find method=%s" % method_name) 28 | if len(good_rez) > 1: 29 | if service_name: 30 | raise Exception("Error while loading protobuf. Found method %s.%s in multiply .proto files. " 31 | "We don't support packages yet!" % (service_name, method_name)) 32 | else: 33 | raise Exception( 34 | "Error while loading protobuf. Found method %s in multiply .proto files. " 35 | "You could try to specify service_name." % method_name) 36 | return good_rez[0] 37 | 38 | 39 | def _import_protobuf_from_file(grpc_py_file, method_name, service_name=None): 40 | """ 41 | helper function which try to import method from the given _pb2_grpc.py file 42 | service_name should be provided only in case of name conflict 43 | return (False, None) in case of failure 44 | return (True, (stub_class, request_class, response_class)) in case of success 45 | """ 46 | 47 | prefix = grpc_py_file[:-12] 48 | pb2 = __import__("%s_pb2" % prefix) 49 | pb2_grpc = __import__("%s_pb2_grpc" % prefix) 50 | 51 | # we take all objects from pb2_grpc module which endswith "Stub", and we remove this postfix to get service_name 52 | all_service_names = [stub_name[:-4] for stub_name in dir(pb2_grpc) if stub_name.endswith("Stub")] 53 | 54 | # if service_name was specified we take only this service_name 55 | if service_name: 56 | if service_name not in all_service_names: 57 | return False, None 58 | all_service_names = [service_name] 59 | 60 | found_services = [] 61 | for service_name in all_service_names: 62 | service_descriptor = getattr(pb2, "DESCRIPTOR").services_by_name[service_name] 63 | for method in service_descriptor.methods: 64 | if method.name == method_name: 65 | request_class = method.input_type._concrete_class 66 | response_class = method.output_type._concrete_class 67 | stub_class = getattr(pb2_grpc, "%sStub" % service_name) 68 | 69 | found_services.append(service_name) 70 | if len(found_services) == 0: 71 | return False, None 72 | if len(found_services) > 1: 73 | raise Exception("Error while loading protobuf. We found methods %s in multiply services [%s]." 74 | " You should specify service_name." % (method_name, ", ".join(found_services))) 75 | return True, (stub_class, request_class, response_class) 76 | 77 | 78 | def switch_to_json_payload_encoding(call_fn, response_class): 79 | """ Switch payload encoding to JSON for GRPC call """ 80 | 81 | def json_serializer(*args, **kwargs): 82 | return bytes(json_format.MessageToJson(args[0], True, preserving_proto_field_name=True), "utf-8") 83 | 84 | def json_deserializer(*args, **kwargs): 85 | resp = response_class() 86 | json_format.Parse(args[0], resp, True) 87 | return resp 88 | 89 | call_fn._request_serializer = json_serializer 90 | call_fn._response_deserializer = json_deserializer 91 | -------------------------------------------------------------------------------- /snet/cli/utils/config.py: -------------------------------------------------------------------------------- 1 | from snet.contracts import get_contract_def 2 | 3 | from cryptography.fernet import Fernet 4 | from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC 5 | from cryptography.hazmat.backends import default_backend 6 | from cryptography.hazmat.primitives import hashes 7 | from base64 import urlsafe_b64encode, urlsafe_b64decode 8 | import os 9 | 10 | 11 | def get_contract_address(cmd, contract_name, error_message=None): 12 | """ 13 | We try to get config address from the different sources. 14 | The order of priorioty is following: 15 | - command line argument (at) 16 | - command line argument (_at) 17 | - current session configuration (current__at) 18 | - networks/*json 19 | """ 20 | 21 | # try to get from command line argument at or contractname_at 22 | a = "at" 23 | if hasattr(cmd.args, a) and getattr(cmd.args, a): 24 | return cmd.w3.to_checksum_address(getattr(cmd.args, a)) 25 | 26 | # try to get from command line argument contractname_at 27 | a = "%s_at" % contract_name.lower() 28 | if hasattr(cmd.args, a) and getattr(cmd.args, a): 29 | return cmd.w3.to_checksum_address(getattr(cmd.args, a)) 30 | 31 | # try to get from current session configuration 32 | rez = cmd.config.get_session_field("current_%s_at" % (contract_name.lower()), exception_if_not_found=False) 33 | if rez: 34 | return cmd.w3.to_checksum_address(rez) 35 | 36 | error_message = error_message or "Fail to read %s address from \"networks\", you should " \ 37 | "specify address by yourself via --%s_at parameter" % ( 38 | contract_name, contract_name.lower()) 39 | # try to take address from networks 40 | return read_default_contract_address(w3=cmd.w3, contract_name=contract_name) 41 | 42 | 43 | def read_default_contract_address(w3, contract_name): 44 | chain_id = w3.net.version # this will raise exception if endpoint is invalid 45 | contract_def = get_contract_def(contract_name) 46 | networks = contract_def["networks"] 47 | contract_address = networks.get(chain_id, {}).get("address", None) 48 | if not contract_address: 49 | raise Exception() 50 | contract_address = w3.to_checksum_address(contract_address) 51 | return contract_address 52 | 53 | 54 | def get_field_from_args_or_session(config, args, field_name): 55 | """ 56 | We try to get field_name from diffent sources: 57 | The order of priorioty is following: 58 | read_default_contract_address - command line argument (--) 59 | - current session configuration (default_) 60 | """ 61 | rez = getattr(args, field_name, None) 62 | # type(rez) can be int in case of wallet-index, so we cannot make simply if(rez) 63 | if rez is not None: 64 | return rez 65 | rez = config.get_session_field("default_%s" % field_name, exception_if_not_found=False) 66 | if rez: 67 | return rez 68 | raise Exception("Fail to get default_%s from config, should specify %s via --%s parameter" % ( 69 | field_name, field_name, field_name.replace("_", "-"))) 70 | 71 | 72 | def encrypt_secret(secret, password): 73 | salt = os.urandom(16) 74 | kdf = PBKDF2HMAC( 75 | algorithm=hashes.SHA256(), 76 | length=32, 77 | salt=salt, 78 | iterations=100000, 79 | backend=default_backend() 80 | ) 81 | key = urlsafe_b64encode(kdf.derive(password.encode())) 82 | cipher_suite = Fernet(key) 83 | encrypted_secret = cipher_suite.encrypt(secret.encode()) + salt 84 | return '::' + str(urlsafe_b64encode(encrypted_secret))[2:-1] 85 | 86 | def decrypt_secret(secret, password): 87 | secret = urlsafe_b64decode(secret[2:]) 88 | salt = secret[-16:] 89 | secret = secret[:-16] 90 | kdf = PBKDF2HMAC( 91 | algorithm=hashes.SHA256(), 92 | length=32, 93 | salt=salt, 94 | iterations=100000, 95 | backend=default_backend() 96 | ) 97 | key = urlsafe_b64encode(kdf.derive(password.encode())) 98 | cipher_suite = Fernet(key) 99 | decrypted_secret = cipher_suite.decrypt(secret).decode() 100 | return decrypted_secret 101 | -------------------------------------------------------------------------------- /snet/cli/resources/proto/training.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | import "google/protobuf/descriptor.proto"; // Required for indicators to work 3 | package training; 4 | option go_package = "github.com/singnet/snet-daemon/v5/training"; 5 | 6 | // Methods that the service provider must implement 7 | service Model { 8 | 9 | // Free 10 | // Can pass the address of the model creator 11 | rpc create_model(NewModel) returns (ModelID) {} 12 | 13 | // Free 14 | rpc validate_model_price(ValidateRequest) returns (PriceInBaseUnit) {} 15 | 16 | // Paid 17 | rpc upload_and_validate(stream UploadInput) returns (StatusResponse) {} 18 | 19 | // Paid 20 | rpc validate_model(ValidateRequest) returns (StatusResponse) {} 21 | 22 | // Free, one signature for both train_model_price & train_model methods 23 | rpc train_model_price(ModelID) returns (PriceInBaseUnit) {} 24 | 25 | // Paid 26 | rpc train_model(ModelID) returns (StatusResponse) {} 27 | 28 | // Free 29 | rpc delete_model(ModelID) returns (StatusResponse) { 30 | // After model deletion, the status becomes DELETED in etcd 31 | } 32 | 33 | // Free 34 | rpc get_model_status(ModelID) returns (StatusResponse) {} 35 | } 36 | 37 | message ModelResponse { 38 | string model_id = 1; 39 | Status status = 2; 40 | string created_date = 3; 41 | string updated_date = 4; 42 | string name = 5; 43 | string description = 6; 44 | string grpc_method_name = 7; 45 | string grpc_service_name = 8; 46 | 47 | // List of all addresses that will have access to this model 48 | repeated string address_list = 9; 49 | 50 | // Access to the model is granted only for use and viewing 51 | bool is_public = 10; 52 | 53 | string training_data_link = 11; 54 | 55 | string created_by_address = 12; 56 | string updated_by_address = 13; 57 | } 58 | 59 | // Used as input for new_model requests 60 | // The service provider decides whether to use these fields; returning model_id is mandatory 61 | message NewModel { 62 | string name = 1; 63 | string description = 2; 64 | string grpc_method_name = 3; 65 | string grpc_service_name = 4; 66 | 67 | // List of all addresses that will have access to this model 68 | repeated string address_list = 5; 69 | 70 | // Set this to true if you want your model to be accessible by other AI consumers 71 | bool is_public = 6; 72 | 73 | // These parameters will be passed by the daemon 74 | string organization_id = 7; 75 | string service_id = 8; 76 | string group_id = 9; 77 | } 78 | 79 | // This structure must be used by the service provider 80 | message ModelID { 81 | string model_id = 1; 82 | } 83 | 84 | // This structure must be used by the service provider 85 | // Used in the train_model_price method to get the training/validation price 86 | message PriceInBaseUnit { 87 | uint64 price = 1; // cogs, weis, afet, aasi, etc. 88 | } 89 | 90 | enum Status { 91 | CREATED = 0; 92 | VALIDATING = 1; 93 | VALIDATED = 2; 94 | TRAINING = 3; 95 | READY_TO_USE = 4; // After training is completed 96 | ERRORED = 5; 97 | DELETED = 6; 98 | } 99 | 100 | message StatusResponse { 101 | Status status = 1; 102 | } 103 | 104 | message UploadInput { 105 | string model_id = 1; 106 | bytes data = 2; 107 | string file_name = 3; 108 | uint64 file_size = 4; // in bytes 109 | uint64 batch_size = 5; 110 | uint64 batch_number = 6; 111 | uint64 batch_count = 7; 112 | } 113 | 114 | message ValidateRequest { 115 | string model_id = 2; 116 | string training_data_link = 3; 117 | } 118 | 119 | extend google.protobuf.MethodOptions { 120 | string default_model_id = 50001; 121 | uint64 max_models_per_user = 50002; // max models per method & user 122 | uint64 dataset_max_size_mb = 50003; // max size of dataset 123 | uint64 dataset_max_count_files = 50004; // maximum number of files in the dataset 124 | uint64 dataset_max_size_single_file_mb = 50005; // maximum size of a single file in the dataset 125 | string dataset_files_type = 50006; // allowed files types in dataset. string with array or single value, example: jpg, png, mp3 126 | string dataset_type = 50007; // string with array or single value, example: zip, tar.gz, tar 127 | string dataset_description = 50008; // additional free-form requirements 128 | } 129 | -------------------------------------------------------------------------------- /snet/cli/resources/proto/training/training.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | import "google/protobuf/descriptor.proto"; // Required for indicators to work 3 | package training; 4 | option go_package = "github.com/singnet/snet-daemon/v5/training"; 5 | 6 | // Methods that the service provider must implement 7 | service Model { 8 | 9 | // Free 10 | // Can pass the address of the model creator 11 | rpc create_model(NewModel) returns (ModelID) {} 12 | 13 | // Free 14 | rpc validate_model_price(ValidateRequest) returns (PriceInBaseUnit) {} 15 | 16 | // Paid 17 | rpc upload_and_validate(stream UploadInput) returns (StatusResponse) {} 18 | 19 | // Paid 20 | rpc validate_model(ValidateRequest) returns (StatusResponse) {} 21 | 22 | // Free, one signature for both train_model_price & train_model methods 23 | rpc train_model_price(ModelID) returns (PriceInBaseUnit) {} 24 | 25 | // Paid 26 | rpc train_model(ModelID) returns (StatusResponse) {} 27 | 28 | // Free 29 | rpc delete_model(ModelID) returns (StatusResponse) { 30 | // After model deletion, the status becomes DELETED in etcd 31 | } 32 | 33 | // Free 34 | rpc get_model_status(ModelID) returns (StatusResponse) {} 35 | } 36 | 37 | message ModelResponse { 38 | string model_id = 1; 39 | Status status = 2; 40 | string created_date = 3; 41 | string updated_date = 4; 42 | string name = 5; 43 | string description = 6; 44 | string grpc_method_name = 7; 45 | string grpc_service_name = 8; 46 | 47 | // List of all addresses that will have access to this model 48 | repeated string address_list = 9; 49 | 50 | // Access to the model is granted only for use and viewing 51 | bool is_public = 10; 52 | 53 | string training_data_link = 11; 54 | 55 | string created_by_address = 12; 56 | string updated_by_address = 13; 57 | } 58 | 59 | // Used as input for new_model requests 60 | // The service provider decides whether to use these fields; returning model_id is mandatory 61 | message NewModel { 62 | string name = 1; 63 | string description = 2; 64 | string grpc_method_name = 3; 65 | string grpc_service_name = 4; 66 | 67 | // List of all addresses that will have access to this model 68 | repeated string address_list = 5; 69 | 70 | // Set this to true if you want your model to be accessible by other AI consumers 71 | bool is_public = 6; 72 | 73 | // These parameters will be passed by the daemon 74 | string organization_id = 7; 75 | string service_id = 8; 76 | string group_id = 9; 77 | } 78 | 79 | // This structure must be used by the service provider 80 | message ModelID { 81 | string model_id = 1; 82 | } 83 | 84 | // This structure must be used by the service provider 85 | // Used in the train_model_price method to get the training/validation price 86 | message PriceInBaseUnit { 87 | uint64 price = 1; // cogs, weis, afet, aasi, etc. 88 | } 89 | 90 | enum Status { 91 | CREATED = 0; 92 | VALIDATING = 1; 93 | VALIDATED = 2; 94 | TRAINING = 3; 95 | READY_TO_USE = 4; // After training is completed 96 | ERRORED = 5; 97 | DELETED = 6; 98 | } 99 | 100 | message StatusResponse { 101 | Status status = 1; 102 | } 103 | 104 | message UploadInput { 105 | string model_id = 1; 106 | bytes data = 2; 107 | string file_name = 3; 108 | uint64 file_size = 4; // in bytes 109 | uint64 batch_size = 5; 110 | uint64 batch_number = 6; 111 | uint64 batch_count = 7; 112 | } 113 | 114 | message ValidateRequest { 115 | string model_id = 2; 116 | string training_data_link = 3; 117 | } 118 | 119 | extend google.protobuf.MethodOptions { 120 | string default_model_id = 50001; 121 | uint64 max_models_per_user = 50002; // max models per method & user 122 | uint64 dataset_max_size_mb = 50003; // max size of dataset 123 | uint64 dataset_max_count_files = 50004; // maximum number of files in the dataset 124 | uint64 dataset_max_size_single_file_mb = 50005; // maximum size of a single file in the dataset 125 | string dataset_files_type = 50006; // allowed files types in dataset. string with array or single value, example: jpg, png, mp3 126 | string dataset_type = 50007; // string with array or single value, example: zip, tar.gz, tar 127 | string dataset_description = 50008; // additional free-form requirements 128 | } 129 | -------------------------------------------------------------------------------- /snet/cli/test/functional_tests/script12_extend_add_for_service.sh: -------------------------------------------------------------------------------- 1 | snet organization metadata-init org1 testo individual 2 | snet organization add-group group1 0x52653A9091b5d5021bed06c5118D24b23620c529 5.5.6.7:8089 3 | snet organization add-group group2 0x0067b427E299Eb2A4CBafc0B04C723F77c6d8a18 1.2.1.1:8089 4 | snet organization create test0 -y -q 5 | 6 | snet service metadata-init ./service_spec1/ ExampleService --fixed-price 0.0001 --endpoints 8.8.8.8:2020 --group-name group1 7 | 8 | snet organization create testo -y -q 9 | snet service publish testo tests -y -q 10 | 11 | snet account deposit 100000000 -yq 12 | 13 | snet channel open-init testo group1 123.123 1 -yq 14 | 15 | snet channel print-initialized | grep 123.223 && exit 1 || echo "fail as expected" 16 | 17 | snet --print-traceback channel extend-add-for-org testo group1 --amount 0.1 --expiration 314 -yq 18 | snet channel extend-add-for-org testo group1 --amount 0.2 -yq 19 | snet --print-traceback channel extend-add-for-org testo group1 --expiration 315 -yq 20 | snet channel print-initialized | grep 123.423 21 | snet channel print-initialized | grep 315 22 | 23 | rm -rf ~/.snet/mpe_client 24 | snet --print-traceback channel extend-add-for-org testo group1 --amount 0.1 --expiration 315 -yq 25 | snet channel extend-add-for-org testo group1 --amount 0.1 -yq 26 | snet channel extend-add-for-org testo group1 --expiration 31415 -yq 27 | snet channel print-initialized | grep 123.623 28 | snet channel print-initialized | grep 31415 29 | 30 | snet --print-traceback channel open-init testo group1 7777.8888 1 -yq --open-new-anyway 31 | snet channel extend-add-for-org testo group1 --amount 0.01 --expiration 314 -yq && exit 1 || echo "fail as expected" 32 | snet channel extend-add-for-org testo group1 --amount 0.01 -yq && exit 1 || echo "fail as expected" 33 | snet channel extend-add-for-org testo group1 --expiration 31477 -yq && exit 1 || echo "fail as expected" 34 | 35 | snet channel extend-add-for-org testo group1 --amount 0.01 --expiration 314 -yq --channel-id 1 36 | snet channel extend-add-for-org testo group1 --amount 0.01 --channel-id 1 -yq 37 | snet channel extend-add-for-org testo group1 --expiration 31477 -yq --channel-id 1 38 | snet channel print-initialized | grep 7777.9088 39 | snet channel print-initialized | grep 31477 40 | 41 | # multiply payment groups case 42 | #snet service metadata-init ./service_spec1/ ExampleService 0x52653A9091b5d5021bed06c5118D24b23620c529 --fixed-price 0.0001 --endpoints 8.8.8.8:2020 --group-name group2 43 | snet service metadata-add-group group2 44 | snet service metadata-add-endpoints group2 8.8.8.8:20202 9.10.9.8:8080 45 | 46 | snet service update-metadata testo tests -yq 47 | 48 | snet channel open-init testo group2 2222.33333 1 -yq 49 | 50 | snet --print-traceback channel extend-add-for-org testo group1 --amount 0.001 --expiration 314 -yq && exit 1 || echo "fail as expected" 51 | snet channel extend-add-for-org testo group1 --amount 0.001 -yq && exit 1 || echo "fail as expected" 52 | snet channel extend-add-for-org testo group1 --expiration 4321 -yq && exit 1 || echo "fail as expected" 53 | 54 | snet channel extend-add-for-org testo group2 --amount 0.001 --expiration 4321 -yq 55 | snet channel extend-add-for-org testo group2 --amount 0.001 -yq 56 | snet channel extend-add-for-org testo group2 --expiration 4321 -yq 57 | 58 | snet channel print-initialized 59 | snet channel print-initialized | grep 2222.33533 60 | snet channel print-initialized | grep 4321 61 | 62 | #reinitializing the channel use the existing chnanels. 63 | snet channel open-init testo group2 2222.33333 1 -yq 64 | 65 | snet channel print-initialized 66 | snet channel extend-add-for-org testo group2 --amount 0.0001 --expiration 4444 -yq 67 | snet channel extend-add-for-org testo group2 --amount 0.0001 -yq 68 | snet channel extend-add-for-org testo group2 --expiration 5643 -yq 69 | snet channel print-initialized 70 | snet channel print-initialized | grep 2222.33553 71 | snet channel print-initialized | grep 5643 72 | 73 | rm -rf ~/.snet/mpe_client 74 | 75 | snet channel extend-add-for-org testo group2 --amount 0.00001 --expiration 7654 -yq 76 | snet channel extend-add-for-org testo group2 --amount 0.00001 -yq 77 | snet channel extend-add-for-org testo group2 --expiration 7655 -yq 78 | snet channel print-initialized 79 | snet channel print-initialized | grep 2222.33555 80 | snet channel print-initialized | grep 7655 81 | -------------------------------------------------------------------------------- /snet/cli/test/functional_tests/script11_update_metadata.sh: -------------------------------------------------------------------------------- 1 | # simple case of one group 2 | snet service metadata-init ./service_spec1/ ExampleService --fixed-price 0.0001 --endpoints 8.8.8.8:2020 --group-name group1 3 | 4 | snet --print-traceback organization metadata-init org1 testo individual 5 | snet --print-traceback organization add-group group1 0x52653A9091b5d5021bed06c5118D24b23620c529 5.5.6.7:8089 6 | snet --print-traceback organization add-group group2 0x52653A9091b5d5021bed06c5118D24b23620c529 1.2.1.1:8089 7 | snet --print-traceback organization create testo -y -q 8 | snet --print-traceback service publish testo tests -y -q 9 | 10 | # change group_id 11 | 12 | # case with several groups 13 | snet --print-traceback service metadata-init ./service_spec1/ ExampleService --group-name group0 --fixed-price 0.0001 --endpoints 8.8.8.8:2020 9.8.9.8:8080 14 | 15 | snet --print-traceback service metadata-add-daemon-addresses group0 0x123 16 | test "$(< service_metadata.json jq '.groups[0].daemon_addresses | length')" = 1 && echo "succes" || exit 1 17 | snet --print-traceback service metadata-add-daemon-addresses group0 0x234 18 | 19 | snet --print-traceback service metadata-update-daemon-addresses group0 0x123 20 | test "$(< service_metadata.json jq '.groups[0].daemon_addresses | length')" = 1 && echo "succes" || exit 1 21 | 22 | snet --print-traceback service metadata-add-daemon-addresses group10 0x123 || echo "fail as expected" 23 | 24 | snet --print-traceback service metadata-add-group group1 25 | snet --print-traceback service metadata-add-endpoints group1 8.8.8.8:22 1.2.3.4:8080 26 | 27 | 28 | snet --print-traceback service metadata-add-group group2 29 | snet --print-traceback service metadata-add-endpoints group2 8.8.8.8:2 1.2.3.4:800 30 | 31 | # this should fail as group0 is not in organization 32 | snet --print-traceback service update-metadata testo tests -yq && exit 1 || echo "fail as expected" 33 | snet --print-traceback service metadata-remove-group group0 34 | snet --print-traceback service update-metadata testo tests -yq 35 | 36 | 37 | 38 | #add assets with single value 39 | 40 | snet --print-traceback service metadata-init ./service_spec1/ ExampleService --metadata-file=service_asset_metadata.json 41 | #cp service_metadata.json service_asset_metadata.json 42 | snet --print-traceback service metadata-add-assets ./service_spec1/test hero_image --metadata-file=service_asset_metadata.json 43 | snet --print-traceback service metadata-add-assets ./service_spec1/test terms_of_use --metadata-file=service_asset_metadata.json 44 | result=$(cat service_asset_metadata.json | jq '.assets.hero_image') 45 | test $result = '"QmWQhwwnvK4YHvEarEguTDhz8o2kwvyfPhv5favs1VS4xm/test"' && echo "add asset with single value test case passed " || exit 1 46 | 47 | #add assets with multiple values 48 | snet --print-traceback service metadata-add-assets ./service_spec1/test images --metadata-file=service_asset_metadata.json 49 | snet --print-traceback service metadata-add-assets ./service_spec1/test images --metadata-file=service_asset_metadata.json 50 | result=$(cat service_asset_metadata.json | jq '.assets.images[1]') 51 | test $result = '"QmWQhwwnvK4YHvEarEguTDhz8o2kwvyfPhv5favs1VS4xm/test"' && echo "add asset with multiple value test case passed " || exit 1 52 | 53 | #remove assets 54 | snet --print-traceback service metadata-remove-assets hero_image --metadata-file=service_asset_metadata.json 55 | result=$(cat service_asset_metadata.json | jq '.assets.hero_image') 56 | test $result = '""' && echo "metadata-remove-assets test case passed " || exit 1 57 | 58 | #remove all assets 59 | snet --print-traceback service metadata-remove-all-assets --metadata-file=service_asset_metadata.json 60 | result=$(cat service_asset_metadata.json | jq '.assets') 61 | test $result = '{}' && echo "metadata-remove-all-assets test case passed " || exit 1 62 | rm service_asset_metadata.json 63 | 64 | snet --print-traceback service metadata-init ./service_spec1/ ExampleService --group-name group1 --fixed-price 0.0001 --endpoints 8.8.8.8:2020 9.8.9.8:8080 65 | snet --print-traceback service metadata-set-free-calls group1 12 66 | snet --print-traceback service metadata-set-freecall-signer-address group1 0x123 67 | test "$(< service_metadata.json jq '.groups[0].free_calls')" = 12 \ 68 | && test "$(< service_metadata.json jq '.groups[0].free_call_signer_address')" = '"0x123"' \ 69 | && echo "free call test passed" || exit 1 70 | -------------------------------------------------------------------------------- /snet/cli/test/functional_tests/script9_treasurer.sh: -------------------------------------------------------------------------------- 1 | # Test 'snet treasurer' 2 | 3 | # run daemon 4 | cd simple_daemon 5 | python test_simple_daemon.py & 6 | DAEMON=$! 7 | cd .. 8 | 9 | 10 | snet account deposit 12345 -y -q 11 | 12 | # service provider has --wallet-index==9 (0x52653A9091b5d5021bed06c5118D24b23620c529) 13 | # make two endpoints (both are actually valid) 14 | 15 | snet organization metadata-init org1 testo individual 16 | snet organization add-group group0 0x52653A9091b5d5021bed06c5118D24b23620c529 5.5.6.7:8089 17 | snet organization create testo -y -q 18 | 19 | 20 | 21 | 22 | snet service metadata-init ./service_spec1/ ExampleService --group-name group0 --fixed-price 0.0001 --endpoints 127.0.0.1:50051 23 | snet service publish testo tests -y -q 24 | 25 | 26 | 27 | assert_balance () { 28 | MPE_BALANCE=$(snet account balance --account 0x52653A9091b5d5021bed06c5118D24b23620c529 |grep MPE) 29 | test ${MPE_BALANCE##*:} = $1 30 | } 31 | 32 | EXPIRATION0=$((`snet channel block-number` + 100)) 33 | EXPIRATION1=$((`snet channel block-number` + 100000)) 34 | EXPIRATION2=$((`snet channel block-number` + 100000)) 35 | 36 | snet channel open-init-metadata testo group0 1 $EXPIRATION0 -yq #channe id =0 37 | 38 | # add second endpoint to metadata (in order to test case with two endpoints in metadata) 39 | snet service metadata-add-endpoints group0 localhost:50051 40 | 41 | # formally we will have two open chanels (channnel_id = 1,2) for the same group (testo/group0) 42 | snet channel open-init-metadata testo group0 1 $EXPIRATION1 -yq --open-new-anyway #channel id =1 43 | snet channel open-init-metadata testo group0 1 $EXPIRATION2 -yq --open-new-anyway #channel id =2 44 | 45 | #low level equivalent to "snet client call testo tests0 classify {}" 46 | snet --print-traceback client call-lowlevel testo tests group0 0 0 10000 classify {} 47 | # should fail because nonce is incorect 48 | snet client call-lowlevel testo tests group0 0 1 20000 classify {} && exit 1 || echo "fail as expected" 49 | # should fail because amount is incorect 50 | snet client call-lowlevel testo tests group0 0 0 10000 classify {} && exit 1 || echo "fail as expected" 51 | snet client call testo tests group0 classify {} --save-response response.pb --channel-id 1 --endpoint http://127.0.0.1:50051 -y 52 | snet client call testo tests group0 classify {} --save-field binary_field out.bin --channel-id 2 --endpoint http://localhost:50051 -y 53 | snet client call testo tests group0 classify {} --save-field predictions out.txt --channel-id 2 -y 54 | rm -f response.pb out.bin out.txt 55 | snet treasurer claim-all --endpoint 127.0.0.1:50051 --wallet-index 9 -yq 56 | snet treasurer claim-all --endpoint 127.0.0.1:50051 --wallet-index 9 -yq 57 | assert_balance 0.0004 58 | snet client call testo tests group0 classify {} -y --channel-id 0 59 | snet client call testo tests group0 classify {} -y --channel-id 1 60 | snet client get-channel-state 0 http://localhost:50051 61 | snet client get-channel-state 1 http://127.0.0.1:50051 62 | 63 | # low level equivalent to "snet client call testo tests1 classify {} --channel-id 1" 64 | snet client call-lowlevel testo tests group0 1 1 20000 classify {} 65 | 66 | snet client call testo tests group0 classify {} --channel-id 2 -y 67 | 68 | #only channel 0 should be claimed 69 | snet treasurer claim-expired --expiration-threshold 1000 --endpoint 127.0.0.1:50051 --wallet-index 9 -yq 70 | assert_balance 0.0005 71 | snet treasurer claim 1 2 --endpoint 127.0.0.1:50051 --wallet-index 9 -yq 72 | assert_balance 0.0008 73 | 74 | echo y | snet client call testo tests group0 classify {} --channel-id 0 75 | snet client call testo tests group0 classify {} --channel-id 0 -y 76 | snet client call testo tests group0 classify {} --channel-id 1 -y 77 | snet client call testo tests group0 classify {} --channel-id 2 -y 78 | 79 | # we will start claim of all channels but will not write then to blockchain 80 | echo n | snet treasurer claim-all --endpoint 127.0.0.1:50051 --wallet-index 9 && exit 1 || echo "fail as expected" 81 | assert_balance 0.0008 82 | 83 | snet client call testo tests group0 classify {} --channel-id 1 -y 84 | snet client call testo tests group0 classify {} --channel-id 2 -y 85 | 86 | # and now we should claim everything (including pending payments) 87 | snet treasurer claim-all --endpoint 127.0.0.1:50051 --wallet-index 9 -yq 88 | assert_balance 0.0014 89 | 90 | kill $DAEMON 91 | -------------------------------------------------------------------------------- /snet/cli/utils/ipfs_utils.py: -------------------------------------------------------------------------------- 1 | """ Utilities related to ipfs """ 2 | import tarfile 3 | import glob 4 | import io 5 | import os 6 | 7 | import base58 8 | import multihash 9 | 10 | 11 | def publish_file_in_ipfs(ipfs_client, filepath, wrap_with_directory=True): 12 | """ 13 | push a file to ipfs given its path 14 | """ 15 | try: 16 | with open(filepath, 'r+b') as file: 17 | result = ipfs_client.add( 18 | file, pin=True, wrap_with_directory=wrap_with_directory) 19 | if wrap_with_directory: 20 | return result[1]['Hash']+'/'+result[0]['Name'] 21 | return result['Hash'] 22 | except Exception as err: 23 | print("File error ", err) 24 | 25 | 26 | def publish_file_in_filecoin(filecoin_client, filepath): 27 | """ 28 | Push a file to Filecoin given its path. 29 | """ 30 | try: 31 | response = filecoin_client.upload(filepath) 32 | return response['data']['Hash'] 33 | except Exception as err: 34 | print("File upload error: ", err) 35 | 36 | 37 | def publish_proto_in_ipfs(ipfs_client, protodir): 38 | """ 39 | make tar from protodir/*proto, and publish this tar in ipfs 40 | return base58 encoded ipfs hash 41 | """ 42 | 43 | if not os.path.isdir(protodir): 44 | raise Exception("Directory %s doesn't exists" % protodir) 45 | 46 | files = glob.glob(os.path.join(protodir, "*.proto")) 47 | 48 | if len(files) == 0: 49 | raise Exception("Cannot find any %s files" % 50 | (os.path.join(protodir, "*.proto"))) 51 | 52 | # We are sorting files before we add them to the .tar since an archive containing the same files in a different 53 | # order will produce a different content hash; 54 | files.sort() 55 | 56 | tarbytes = io.BytesIO() 57 | tar = tarfile.open(fileobj=tarbytes, mode="w:gz") 58 | for f in files: 59 | tar.add(f, os.path.basename(f)) 60 | tar.close() 61 | return ipfs_client.add_bytes(tarbytes.getvalue()) 62 | 63 | 64 | def publish_proto_in_filecoin(filecoin_client, protodir): 65 | """ 66 | Create a tar archive from protodir/*.proto, and publish this tar archive to Lighthouse. 67 | Return the hash (CID) of the uploaded archive. 68 | """ 69 | 70 | if not os.path.isdir(protodir): 71 | raise Exception("Directory %s doesn't exist" % protodir) 72 | 73 | files = glob.glob(os.path.join(protodir, "*.proto")) 74 | 75 | if len(files) == 0: 76 | raise Exception("Cannot find any .proto files in %s" % protodir) 77 | 78 | files.sort() 79 | 80 | tarbytes = io.BytesIO() 81 | 82 | with tarfile.open(fileobj=tarbytes, mode="w:gz") as tar: 83 | for f in files: 84 | tar.add(f, os.path.basename(f)) 85 | tarbytes.seek(0) 86 | 87 | temp_tar_path = os.path.join(protodir, "proto_files.tar") 88 | with open(temp_tar_path, 'wb') as temp_tar_file: 89 | temp_tar_file.write(tarbytes.getvalue()) 90 | response = filecoin_client.upload(source=temp_tar_path, tag="") 91 | 92 | os.remove(temp_tar_path) 93 | 94 | return response['data']['Hash'] 95 | 96 | 97 | def get_from_ipfs_and_checkhash(ipfs_client, ipfs_hash_base58, validate=True): 98 | """ 99 | Get file from IPFS. If validate is True, verify the integrity of the file using its hash. 100 | """ 101 | 102 | data = ipfs_client.cat(ipfs_hash_base58) 103 | 104 | if validate: 105 | block_data = ipfs_client.block.get(ipfs_hash_base58) 106 | 107 | # print(f"IPFS hash (Base58): {ipfs_hash_base58}") 108 | # print(f"Block data length: {len(block_data)}") 109 | 110 | # Decode Base58 bash to multihash 111 | try: 112 | mh = multihash.decode(ipfs_hash_base58.encode('ascii'), "base58") 113 | except Exception as e: 114 | raise ValueError(f"Invalid multihash for IPFS hash: {ipfs_hash_base58}. Error: {str(e)}") from e 115 | 116 | if not mh.verify(block_data): # Correctly using mh instance for verification 117 | raise Exception("IPFS hash mismatch with data") 118 | 119 | return data 120 | 121 | 122 | def hash_to_bytesuri(s, storage_type="ipfs", to_encode=True): 123 | """ 124 | Convert in and from bytes uri format used in Registry contract 125 | """ 126 | # TODO: we should pad string with zeros till closest 32 bytes word because of a bug in processReceipt (in snet_cli.contract.process_receipt) 127 | if storage_type == "ipfs": 128 | s = "ipfs://" + s 129 | elif storage_type == "filecoin": 130 | s = "filecoin://" + s 131 | 132 | if to_encode: 133 | return s.encode("ascii").ljust(32 * (len(s)//32 + 1), b"\0") 134 | else: 135 | return s # for 'service_api_source' metadata field 136 | -------------------------------------------------------------------------------- /docs/source/generate_markdown.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import re 4 | from bs4 import BeautifulSoup 5 | 6 | 7 | def process_html_file(file_path, output_dir, markdown_dir): 8 | with open(file_path, 'r', encoding='utf-8') as file: 9 | html_content = file.read() 10 | 11 | soup = BeautifulSoup(html_content, 'html.parser') 12 | 13 | footers = soup.find_all('footer') 14 | for footer in footers: 15 | footer.decompose() 16 | 17 | navigations = soup.find_all('nav') 18 | for navigation in navigations: 19 | navigation.decompose() 20 | 21 | hr = soup.find('hr') 22 | if hr: 23 | hr.decompose() 24 | 25 | ul = soup.find('ul', class_='wy-breadcrumbs') 26 | if ul: 27 | ul.decompose() 28 | 29 | headerlinks = soup.find_all('a', class_='headerlink') 30 | for headerlink in headerlinks: 31 | headerlink.decompose() 32 | 33 | code_blocks = soup.find_all('div', class_='highlight-default notranslate') 34 | for block in code_blocks: 35 | pre_tag = soup.new_tag("p") 36 | pre_tag.append("start ") 37 | pre_tag.append(block.get_text().strip()) 38 | pre_tag.append(" finish") 39 | block.replace_with(pre_tag) 40 | 41 | clean_html = str(soup) 42 | 43 | base_filename = os.path.basename(file_path) 44 | output_path = os.path.join(output_dir, base_filename) 45 | 46 | with open(output_path, 'w', encoding='utf-8') as file: 47 | file.write(clean_html) 48 | print(f"Processed: {file_path} -> {output_path}") 49 | 50 | clean_filename = os.path.splitext(base_filename)[0] + ".md" 51 | md_output_path = os.path.join(markdown_dir, clean_filename) 52 | 53 | os.system(f"html2text --ignore-images {output_path} > {md_output_path}") 54 | 55 | if sys.platform.startswith('win'): 56 | with open(md_output_path, 'r') as file: 57 | md_content = file.read() 58 | else: 59 | with open(md_output_path, 'r', encoding='utf-8') as file: 60 | md_content = file.read() 61 | 62 | clean_md = format_code_elements(md_content) 63 | clean_md = clean_md.replace("\n### ", "\n## ") 64 | clean_md = clean_md.replace("<", "\<") # fix tags errors 65 | clean_md = clean_md.replace(">", "\>") # fix tags errors 66 | clean_md = clean_md.replace("````", "```") 67 | clean_md = delete_beginning(clean_md) 68 | 69 | with open(md_output_path, 'w', encoding='utf-8') as file: 70 | file.write(clean_md) 71 | 72 | print(f"Processed: {output_path} -> {md_output_path}\n") 73 | 74 | 75 | def format_code_elements(text: str): 76 | substrings = [] 77 | start_index = 0 78 | while True: 79 | start_index = text.find("start", start_index) 80 | if start_index == -1: 81 | break 82 | 83 | end_index = text.find("finish", start_index + 5) 84 | if end_index == -1: 85 | break 86 | 87 | substrings.append(text[start_index + 5:end_index]) 88 | start_index = end_index + 6 89 | 90 | results = [] 91 | for code in substrings: 92 | res = re.sub(r'\s+', ' ', code).strip() 93 | 94 | res_split = list(res.split()) 95 | length = len(res_split[0]) + len(res_split[1]) + len(res_split[2]) + 3 96 | ind = ' ' * length 97 | res = res.replace('] [', ']\n' + ind + '[') 98 | 99 | results.append(res) 100 | 101 | for i in range(len(results)): 102 | text = text.replace("start" + substrings[i] + "finish", "```sh\n" + results[i] + "\n```") 103 | 104 | return text 105 | 106 | 107 | def delete_beginning(text: str): 108 | start_index = text.find("## Commands") 109 | end_index = text.find("## Sub-commands") 110 | if start_index == -1 or end_index == -1: 111 | return text 112 | 113 | return text.replace(text[start_index + 11:end_index + 15], "") 114 | 115 | 116 | def process_html_files_in_directory(directory, output_dir, markdown_dir): 117 | if not os.path.exists(output_dir): 118 | os.makedirs(output_dir) 119 | 120 | if not os.path.exists(markdown_dir): 121 | os.makedirs(markdown_dir) 122 | 123 | for file in os.listdir(directory): 124 | if file.endswith('.html'): 125 | file_path = os.path.join(directory, file) 126 | process_html_file(file_path, output_dir, markdown_dir) 127 | 128 | 129 | def main(): 130 | if len(sys.argv) == 4: 131 | input_html_directory: str = sys.argv[1] 132 | output_html_directory: str = sys.argv[2] 133 | output_md_directory: str = sys.argv[3] 134 | else: 135 | raise Exception(""" 136 | You can only pass 3 parameters: 137 | - input_html_directory 138 | - output_html_directory 139 | - output_md_directory 140 | """) 141 | 142 | process_html_files_in_directory( 143 | input_html_directory, 144 | output_html_directory, 145 | output_md_directory 146 | ) 147 | 148 | 149 | if __name__ == "__main__": 150 | main() 151 | -------------------------------------------------------------------------------- /snet/cli/test/functional_tests/script3_without_addresses.sh: -------------------------------------------------------------------------------- 1 | # service side 2 | # check how snet-cli works if we pass contract address via command line interface 3 | 4 | # remove networks 5 | rm -rf ../../snet/snet_cli/resources/contracts/networks/*.json 6 | 7 | #unset addresses 8 | snet unset current_singularitynettoken_at || echo "could fail if hasn't been set (it is ok)" 9 | snet unset current_registry_at || echo "could fail if hasn't been set (it is ok)" 10 | snet unset current_multipartyescrow_at || echo "could fail if hasn't been set (it is ok)" 11 | 12 | # now snet-cli will work only if we pass contract addresses as commandline arguments 13 | 14 | # this should fail without addresses 15 | snet account balance && exit 1 || echo "fail as expected" 16 | snet organization create testo --org-id testo -y -q && exit 1 || echo "fail as expected" 17 | 18 | snet --print-traceback organization metadata-init org1 testo individual --registry-at 0x4e74fefa82e83e0964f0d9f53c68e03f7298a8b2 19 | snet --print-traceback organization add-group group1 0x42A605c07EdE0E1f648aB054775D6D4E38496144 5.5.6.7:8089 20 | snet --print-traceback organization add-group group2 0x42A605c07EdE0E1f648aB054775D6D4E38496144 1.2.1.1:8089 21 | snet --print-traceback organization create testo -y -q --registry-at 0x4e74fefa82e83e0964f0d9f53c68e03f7298a8b2 22 | 23 | snet service metadata-init ./service_spec1/ ExampleService --encoding json --service-type jsonrpc --group-name group1 --mpe 0x5c7a4290f6f8ff64c69eeffdfafc8644a4ec3a4e 24 | snet service metadata-add-group group2 25 | snet service metadata-add-endpoints group1 8.8.8.8:2020 9.8.9.8:8080 26 | snet service metadata-add-endpoints group2 8.8.8.8:22 1.2.3.4:8080 27 | snet service metadata-set-fixed-price group1 0.0001 28 | snet service metadata-set-fixed-price group2 0.0001 29 | 30 | snet service metadata-add-tags tag1 tag2 tag3 31 | grep "tag1" service_metadata.json 32 | grep "tag2" service_metadata.json 33 | grep "tag3" service_metadata.json 34 | grep "tag4" service_metadata.json && exit 1 || echo "fail as expected" 35 | 36 | snet service metadata-remove-tags tag2 tag1 37 | grep "tag2" service_metadata.json && exit 1 || echo "fail as expected" 38 | grep "tag1" service_metadata.json && exit 1 || echo "fail as expected" 39 | grep "tag3" service_metadata.json 40 | 41 | IPFS_HASH=$(snet service publish-in-ipfs --mpe 0x5c7a4290f6f8ff64c69eeffdfafc8644a4ec3a4e) 42 | ipfs cat $IPFS_HASH >service_metadata2.json 43 | 44 | # compare service_metadata.json and service_metadata2.json 45 | cmp <(jq -S . service_metadata.json) <(jq -S . service_metadata2.json) 46 | 47 | snet service publish testo tests -y -q --registry-at 0x4e74fefa82e83e0964f0d9f53c68e03f7298a8b2 --mpe 0x5c7a4290f6f8ff64c69eeffdfafc8644a4ec3a4e 48 | snet service update-add-tags testo tests tag1 tag2 tag3 -y -q --registry-at 0x4e74fefa82e83e0964f0d9f53c68e03f7298a8b2 49 | snet service update-remove-tags testo tests tag2 tag1 -y -q --registry-at 0x4e74fefa82e83e0964f0d9f53c68e03f7298a8b2 50 | snet service print-tags testo tests --registry-at 0x4e74fefa82e83e0964f0d9f53c68e03f7298a8b2 51 | snet organization list --registry-at 0x4e74fefa82e83e0964f0d9f53c68e03f7298a8b2 52 | # it should have only tag3 now 53 | cmp <(echo "tag3") <(snet service print-tags testo tests --registry-at 0x4e74fefa82e83e0964f0d9f53c68e03f7298a8b2) 54 | 55 | snet service print-metadata testo tests --registry-at 0x4e74fefa82e83e0964f0d9f53c68e03f7298a8b2 | grep -v "We must check that hash in IPFS is correct" >service_metadata3.json 56 | 57 | # compare service_metadata.json and service_metadata3.json 58 | cmp <(jq -S . service_metadata.json) <(jq -S . service_metadata3.json) 59 | 60 | # client side 61 | snet account balance --snt 0x6e5f20669177f5bdf3703ec5ea9c4d4fe3aabd14 --mpe 0x5c7a4290f6f8ff64c69eeffdfafc8644a4ec3a4e 62 | snet account deposit 12345 -y -q --snt 0x6e5f20669177f5bdf3703ec5ea9c4d4fe3aabd14 --mpe 0x5c7a4290f6f8ff64c69eeffdfafc8644a4ec3a4e 63 | snet account transfer 0x0067b427E299Eb2A4CBafc0B04C723F77c6d8a18 42 -y -q --mpe 0x5c7a4290f6f8ff64c69eeffdfafc8644a4ec3a4e 64 | snet account withdraw 1 -y -q --mpe 0x5c7a4290f6f8ff64c69eeffdfafc8644a4ec3a4e 65 | snet channel open-init-metadata testo group1 42 1 -y -q --mpe 0x5c7a4290f6f8ff64c69eeffdfafc8644a4ec3a4e --registry 0x4e74fefa82e83e0964f0d9f53c68e03f7298a8b2 66 | snet channel claim-timeout 0 -y -q --mpe 0x5c7a4290f6f8ff64c69eeffdfafc8644a4ec3a4e 67 | snet channel extend-add 0 --expiration 10000 --amount 42 -y -q --mpe 0x5c7a4290f6f8ff64c69eeffdfafc8644a4ec3a4e 68 | snet channel open-init testo group2 1 1000000 -y -q --mpe 0x5c7a4290f6f8ff64c69eeffdfafc8644a4ec3a4e --registry 0x4e74fefa82e83e0964f0d9f53c68e03f7298a8b2 69 | snet channel print-initialized --mpe 0x5c7a4290f6f8ff64c69eeffdfafc8644a4ec3a4e --registry 0x4e74fefa82e83e0964f0d9f53c68e03f7298a8b2 70 | snet channel print-all-filter-sender --mpe 0x5c7a4290f6f8ff64c69eeffdfafc8644a4ec3a4e 71 | rm -rf ~/.snet/mpe_client/ 72 | snet channel init-metadata testo group1 0 --mpe 0x5c7a4290f6f8ff64c69eeffdfafc8644a4ec3a4e --registry 0x4e74fefa82e83e0964f0d9f53c68e03f7298a8b2 73 | snet channel init testo group1 1 --mpe 0x5c7a4290f6f8ff64c69eeffdfafc8644a4ec3a4e --registry 0x4e74fefa82e83e0964f0d9f53c68e03f7298a8b2 74 | snet channel print-initialized --mpe 0x5c7a4290f6f8ff64c69eeffdfafc8644a4ec3a4e --registry 0x4e74fefa82e83e0964f0d9f53c68e03f7298a8b2 75 | snet channel print-all-filter-sender --mpe 0x5c7a4290f6f8ff64c69eeffdfafc8644a4ec3a4e 76 | snet service delete testo tests -y -q --registry-at 0x4e74fefa82e83e0964f0d9f53c68e03f7298a8b2 77 | snet organization list-services testo --registry-at 0x4e74fefa82e83e0964f0d9f53c68e03f7298a8b2 78 | -------------------------------------------------------------------------------- /snet/cli/test/functional_tests/func_tests.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | import argcomplete 3 | import unittest 4 | import unittest.mock as mock 5 | import shutil 6 | import os 7 | 8 | from snet.cli.commands.commands import BlockchainCommand 9 | 10 | with warnings.catch_warnings(): 11 | # Suppress the eth-typing package`s warnings related to some new networks 12 | warnings.filterwarnings("ignore", "Network .* does not have a valid ChainId. eth-typing should be " 13 | "updated with the latest networks.", UserWarning) 14 | from snet.cli import arguments 15 | 16 | from snet.cli.config import Config 17 | 18 | 19 | class StringOutput: 20 | def __init__(self): 21 | self.text = "" 22 | 23 | def write(self, text): 24 | self.text += text 25 | 26 | 27 | def execute(args_list, parser, conf): 28 | try: 29 | argv = args_list 30 | try: 31 | args = parser.parse_args(argv) 32 | except TypeError: 33 | args = parser.parse_args(argv + ["-h"]) 34 | f = StringOutput() 35 | getattr(args.cmd(conf, args, out_f = f), args.fn)() 36 | return f.text 37 | except Exception as e: 38 | raise 39 | 40 | class BaseTest(unittest.TestCase): 41 | def setUp(self): 42 | self.conf = Config() 43 | self.parser = arguments.get_root_parser(self.conf) 44 | argcomplete.autocomplete(self.parser) 45 | 46 | 47 | class TestCommands(BaseTest): 48 | def test_balance_output(self): 49 | result = execute(["account", "balance"], self.parser, self.conf) 50 | assert len(result.split("\n")) >= 4 51 | 52 | def test_balance_address(self): 53 | result = execute(["account", "balance"], self.parser, self.conf) 54 | assert result.split("\n")[0].split()[1] == "0xe5D1fA424DE4689F9d2687353b75D7a8987900fD" 55 | 56 | class TestDepositWithdraw(BaseTest): 57 | def setUp(self): 58 | super().setUp() 59 | self.balance_1: int 60 | self.balance_2: int 61 | self.amount = 0.1 62 | 63 | def test_deposit(self): 64 | result = execute(["account", "balance"], self.parser, self.conf) 65 | self.balance_1 = float(result.split("\n")[3].split()[1]) 66 | execute(["account", "deposit", f"{self.amount}", "-y", "-q"], self.parser, self.conf) 67 | result = execute(["account", "balance"], self.parser, self.conf) 68 | self.balance_2 = float(result.split("\n")[3].split()[1]) 69 | assert self.balance_2 == self.balance_1 + self.amount 70 | 71 | def test_withdraw(self): 72 | result = execute(["account", "balance"], self.parser, self.conf) 73 | self.balance_1 = float(result.split("\n")[3].split()[1]) 74 | execute(["account", "withdraw", f"{self.amount}", "-y", "-q"], self.parser, self.conf) 75 | result = execute(["account", "balance"], self.parser, self.conf) 76 | self.balance_2 = float(result.split("\n")[3].split()[1]) 77 | assert self.balance_2 == self.balance_1 - self.amount 78 | 79 | 80 | class TestGenerateLibrary(BaseTest): 81 | def setUp(self): 82 | super().setUp() 83 | self.path = './temp_files' 84 | self.org_id = '26072b8b6a0e448180f8c0e702ab6d2f' 85 | self.service_id = 'Exampleservice' 86 | 87 | def test_generate(self): 88 | execute(["sdk", "generate-client-library", self.org_id, self.service_id, self.path], self.parser, self.conf) 89 | assert os.path.exists(f'{self.path}/{self.org_id}/{self.service_id}/python/') 90 | 91 | def tearDown(self): 92 | shutil.rmtree(self.path) 93 | 94 | 95 | class TestEncryptionKey(BaseTest): 96 | def setUp(self): 97 | super().setUp() 98 | self.key = "1234567890123456789012345678901234567890123456789012345678901234" 99 | self.password = "some_pass" 100 | self.name = "some_name" 101 | self.default_name = "default_name" 102 | result = execute(["identity", "list"], self.parser, self.conf) 103 | if self.default_name not in result: 104 | execute(["identity", "create", self.default_name, "key", "--private-key", self.key, "-de"], 105 | self.parser, 106 | self.conf) 107 | 108 | def test_1_create_identity_with_encryption_key(self): 109 | with mock.patch('getpass.getpass', return_value=self.password): 110 | execute(["identity", "create", self.name, "key", "--private-key", self.key], 111 | self.parser, 112 | self.conf) 113 | result = execute(["identity", "list"], self.parser, self.conf) 114 | assert self.name in result 115 | 116 | def test_2_get_encryption_key(self): 117 | with mock.patch('getpass.getpass', return_value=self.password): 118 | execute(["identity", self.name], self.parser, self.conf) 119 | cmd = BlockchainCommand(self.conf, self.parser.parse_args(['session'])) 120 | enc_key = cmd.config.get_session_field("private_key") 121 | res_key = cmd._get_decrypted_secret(enc_key) 122 | assert res_key == self.key 123 | 124 | def test_3_delete_identity(self): 125 | with mock.patch('getpass.getpass', return_value=self.password): 126 | execute(["identity", self.default_name], self.parser, self.conf) 127 | execute(["identity", "delete", self.name], self.parser, self.conf) 128 | result = execute(["identity", "list"], self.parser, self.conf) 129 | assert self.name not in result 130 | 131 | 132 | if __name__ == "__main__": 133 | unittest.main() 134 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SingularityNET CLI 2 | 3 | ## Package 4 | 5 | The package is published in PyPI at the following link: 6 | 7 | | Package |Description | 8 | |------------------------------------------------|---------------------------------------------------------------------| 9 | | [snet-cli](https://pypi.org/project/snet-cli/) |Command line interface to interact with the SingularityNET platform | 10 | 11 | ## License 12 | 13 | This project is licensed under the MIT License - see the 14 | [LICENSE](https://github.com/singnet/snet-cli/blob/master/LICENSE) file for details. 15 | 16 | ## Getting Started 17 | 18 | The instruction down below describes the installation of the SingularityNET CLI. 19 | Please check our full [Documentation](https://dev.singularitynet.io/docs/products/DecentralizedAIPlatform/CLI/). 20 | 21 | ### Prerequisites 22 | 23 | You should have Python>=3.10 with pip installed. 24 | 25 | Additionally, you should make sure that you have the following packages in your system: 26 | 27 | * libudev 28 | * libusb 1.0 29 | 30 | If you use Ubuntu (or any Linux distribution with APT support) you could run the following: 31 | 32 | ```bash 33 | sudo apt-get install libudev-dev libusb-1.0-0-dev 34 | ``` 35 | 36 | ### Install snet-cli using pip 37 | 38 | ```bash 39 | $ pip3 install snet-cli 40 | ``` 41 | 42 | 43 | ### Enabling commands autocompletion 44 | If you want to enable the autocompletion of commands, you should install the 45 | * python-argcomplete 46 | 47 | On ubuntu (or any Linux distribution with APT support), you could run the following 48 | 49 | ```bash 50 | sudo apt install python3-argcomplete 51 | ``` 52 | After the package is installed, activate autocomplete 53 | 54 | ##### for all python commands (which includes snet commands as well) 55 | 56 | ```bash 57 | sudo activate-global-python-argcomplete3 58 | ``` 59 | Note: Changes will not take effect until shell is restarted. 60 | 61 | ##### OR 62 | 63 | ##### only for snet commands, then you should run the following 64 | ```bash 65 | echo 'eval "$(register-python-argcomplete3 snet)"' >> ~/.bashrc 66 | ``` 67 | then 68 | ```bash 69 | source ~/.bashrc 70 | ``` 71 | 72 | ## Usage 73 | 74 | Complete documentation is available [here](http://snet-cli-docs.singularitynet.io/) 75 | 76 | ### Example service call via the CLI 77 | We will use the Sepolia testnet for this example: 78 | ```bash 79 | snet network sepolia 80 | ``` 81 | Create the identity: 82 | ```bash 83 | snet identity create example_identity key 84 | ``` 85 | and enter your private key when asked. 86 | OR 87 | you can pass the private key directly: 88 | ```bash 89 | snet identity create --private-key "a7638fd785fdb5cf13df0a1d7b5584cc20d4e8526403f0df105eedf23728f538" test key 90 | ``` 91 | You can also use other identity options. See [documentation](http://snet-cli-docs.singularitynet.io/identity.html). 92 | You can check your balance using the 93 | ```bash 94 | snet account balance 95 | ``` 96 | Deposit 70 tokens: 97 | ```bash 98 | snet account deposit 70 99 | ``` 100 | Press y to confirm. 101 | You can check your balance again to ensure that the transaction was successfull. 102 | Before making a call we need to open the payment channel. In this example we will use the organization with id= 26072b8b6a0e448180f8c0e702ab6d2f and group_name= default_group. We will transfer there 70 tokens for 4 weeks: 103 | ```bash 104 | snet channel open-init 26072b8b6a0e448180f8c0e702ab6d2f default_group 70 +4weeks 105 | ``` 106 | And now we can call the "Exampleservice" service: 107 | ```bash 108 | snet client call 26072b8b6a0e448180f8c0e702ab6d2f Exampleservice default_group add '{"a":10,"b":32}' 109 | ``` 110 | Press 'Y' to confirm and get service`s response: 111 | >value: 42 112 | 113 | ## Development 114 | 115 | ### Installation 116 | 117 | #### Prerequisites 118 | 119 | * [Python 3.10](https://www.python.org/downloads/release/python-31012/) 120 | 121 | Backward compatibility for other Python versions is not guaranteed. 122 | 123 | --- 124 | 125 | ## Install the package in development mode 126 | 127 | ### Clone the git repository 128 | 129 | ```bash 130 | git clone https://github.com/singnet/snet-cli.git 131 | cd snet-cli 132 | ``` 133 | 134 | ```bash 135 | pip install -r requirements.txt 136 | ``` 137 | 138 | ```bash 139 | pip3 install . 140 | ``` 141 | 142 | ```bash 143 | snet -h 144 | ``` 145 | 146 | ### Building the Documentation in Markdown files 147 | 148 | * Clone repository and install dependencies 149 | 150 | ```bash 151 | $ git clone https://github.com/singnet/snet-cli.git 152 | $ cd snet-cli 153 | $ pip install -r docs/requirements.txt 154 | ``` 155 | 156 | #### On Linux 157 | 158 | * Run the build-docs.sh in the docs directory 159 | 160 | ```bash 161 | $ cd docs 162 | $ sh build-docs.sh 163 | ``` 164 | 165 | #### On Windows 166 | 167 | * Install `make` utility and run the build-docs.ps1 in the docs directory 168 | 169 | ```powershell 170 | choco install make # install choco if it is not already installed 171 | cd docs 172 | powershell -file build-docs.ps1 173 | ``` 174 | 175 | The documentation is generated under the docs/build/markdown folder 176 | 177 | ### Release 178 | 179 | This project is published to [PyPI](https://pypi.org/project/snet-cli/). 180 | 181 | ### Versioning 182 | 183 | We use [SemVer](http://semver.org/) for versioning. For the versions available, see the 184 | [tags on this repository](https://github.com/singnet/snet-cli/tags). 185 | 186 | ## License 187 | 188 | This project is licensed under the MIT License - see the 189 | [LICENSE](https://github.com/singnet/snet-cli/blob/master/snet_cli/LICENSE) file for details. 190 | -------------------------------------------------------------------------------- /snet/cli/test/unit_tests/test_mpe_service_metadata_2.py: -------------------------------------------------------------------------------- 1 | """Unit test cases for the media query commands in snet-cli. 2 | 3 | This module contains the unit test cases for media methods of the MPEServiceMetadata class, 4 | mainly add_media, remove_media, remove_all_media, swap_media_order, and change_media_order. 5 | """ 6 | import unittest 7 | from unittest.mock import patch 8 | 9 | from snet.cli.metadata.service import MPEServiceMetadata 10 | 11 | 12 | class TestMediaMethods(unittest.TestCase): 13 | def setUp(self): 14 | """Runs before every unit test case. 15 | 16 | Mock fixtures are created as they are used multiple times in each test case. Ensure consistent format: 17 | { 18 | "asset_type": String, 19 | "order": Integer, 20 | "url": String, 21 | "file_type": String, 22 | "alt_text": String 23 | } 24 | 25 | Other common checks during start of test case: 26 | 1) `media` present in metadata. 27 | 2) `media` should be of type list. 28 | 3) `media` should be empty. 29 | """ 30 | self.metadata = MPEServiceMetadata() 31 | self.mock_fixture_1 = dict(order=1, 32 | asset_type="hero_image", 33 | url="www.example1.com", 34 | file_type="image", 35 | alt_text="hover_on_the_image_text") 36 | self.mock_fixture_2 = dict(order=2, 37 | url="www.example2.com", 38 | file_type="video", 39 | alt_text="hover_on_the_video_url") 40 | self.assertIn('media', self.metadata.m, msg="`media` not initialized in metadata") 41 | self.assertIsInstance(self.metadata.m['media'], list, msg="`media` in metadata should be a list") 42 | self.assertListEqual(self.metadata.m['media'], [], msg="Media array should be empty during initialization") 43 | 44 | def test_add_media(self): 45 | self.metadata.add_media('www.example1.com', 'image', True) 46 | self.assertEqual(self.metadata.m['media'][0], self.mock_fixture_1, msg="Media addition unsuccessful") 47 | with self.assertRaises(AssertionError, msg="Multiple hero images constraint not handled"): 48 | self.metadata.add_media('www.example2.com', 'image', True) 49 | self.metadata.add_media('www.example2.com', 'video', False) 50 | self.assertEqual(self.metadata.m['media'][1], self.mock_fixture_2, 51 | msg='Probable issue with Order ID sequencing') 52 | 53 | def test_remove_media(self): 54 | with self.assertRaises(AssertionError, msg="Media removal from empty list not handled"): 55 | self.metadata.remove_media(1) 56 | with self.assertRaises(AssertionError, msg="Out of bounds Order ID not handled"): 57 | self.metadata.remove_media(-1) 58 | self.metadata.add_media('www.example2.com', 'video', hero_img=False) 59 | self.metadata.add_media('www.example1.com', 'image', hero_img=True) 60 | with self.assertRaises(Exception, msg="Non-existent Order ID not handled"): 61 | self.metadata.remove_media(100) 62 | self.metadata.remove_media(1) 63 | self.assertDictEqual(self.metadata.m['media'][0], self.mock_fixture_1, 64 | msg="Order ID sequencing after individual media deletion not handled") 65 | self.metadata.remove_media(1) 66 | self.assertListEqual(self.metadata.m['media'], [], msg="Individual media removal not handled") 67 | 68 | def test_remove_all_media(self): 69 | for _ in range(5): 70 | self.metadata.m['media'].append(self.mock_fixture_1) 71 | self.metadata.remove_all_media() 72 | self.assertListEqual(self.metadata['media'], [], msg="Issue with removal of all individual media") 73 | 74 | def test_swap_media_order(self): 75 | self.metadata.add_media('www.example2.com', 'video', hero_img=False) 76 | self.metadata.add_media('www.example1.com', 'image', hero_img=True) 77 | with self.assertRaises(AssertionError, msg="Order supposed to be out of bounds"): 78 | self.metadata.swap_media_order(1, 3) 79 | self.metadata.swap_media_order(3, 1) 80 | self.metadata.swap_media_order(1, 2) 81 | self.assertSequenceEqual(self.metadata.m['media'], [self.mock_fixture_1, self.mock_fixture_2], 82 | msg="Issue with media swapping", seq_type=list) 83 | 84 | def test_change_media_order(self): 85 | """Tests the REPL for changing multiple individual media order. 86 | 87 | Mock inputs through side effects are passed to the input stream using `builtins.input`, 88 | and StopIteration assertion should be raised for incorrect inputs. 89 | 90 | Context Manager is used to limit the scope of the patch to test different side effects. 91 | Read more at https://docs.python.org/3/library/unittest.mock.html#unittest.mock.Mock.side_effect 92 | """ 93 | self.metadata.add_media('www.example2.com', 'video', hero_img=False) 94 | self.metadata.add_media('www.example1.com', 'image', hero_img=True) 95 | with patch('builtins.input', side_effect=['3', '1']): 96 | self.assertRaises(StopIteration, self.metadata.change_media_order) 97 | with patch('builtins.input', side_effect=['1', '3']): 98 | self.assertRaises(StopIteration, self.metadata.change_media_order) 99 | with patch('builtins.input', side_effect=['2', '1']): 100 | self.metadata.change_media_order() 101 | self.assertSequenceEqual(self.metadata.m['media'], [self.mock_fixture_1, self.mock_fixture_2], 102 | msg="Issue with media swapping", seq_type=list) 103 | 104 | 105 | if __name__ == '__main__': 106 | unittest.main() 107 | -------------------------------------------------------------------------------- /snet/cli/test/functional_tests/script6_organization.sh: -------------------------------------------------------------------------------- 1 | # Test "snet organization" 2 | 3 | snet organization metadata-init org1 testo individual 4 | snet organization add-group group1 0x42A605c07EdE0E1f648aB054775D6D4E38496144 5.5.6.7:8089 5 | snet organization add-group group2 0x42A605c07EdE0E1f648aB054775D6D4E38496144 1.2.1.1:8089 6 | snet organization create test0 -y -q 7 | 8 | # fail to create organization with the same id 9 | snet organization create test0 -y -q && exit 1 || echo "fail as expected" 10 | 11 | ## check if this feature is required --org-id and --auto are mutually exclusive 12 | snet organization create test1 --org-id test1 --auto -y -q && exit 1 || echo "fail as expected" 13 | 14 | ## create organization with random id 15 | snet organization create test0 --auto -y -q && exit 1 || echo "fail as expected" 16 | 17 | snet organization create test1 --members 0x3b2b3C2e2E7C93db335E69D827F3CC4bC2A2A2cB -y -q 18 | snet organization create test2 --members 0x3b2b3C2e2E7C93db335E69D827F3CC4bC2A2A2cB,0x0067b427E299Eb2A4CBafc0B04C723F77c6d8a18 -y -q 19 | snet organization add-members test2 0x32267d505B1901236508DcDa64C1D0d5B9DF639a -y 20 | snet organization add-members test2 0x5c1011aB3C7f46EC5E78073D61DF6d002983F04a,0x42A605c07EdE0E1f648aB054775D6D4E38496144 -y 21 | snet organization add-members test2 0x5c1011aB3C7f46EC5E78073D61DF6d002983F04a,0x42A605c07EdE0E1f648aB054775D6D4E38496144 -y | grep "No member was added" 22 | snet organization add-members test2 0x5c1011aB3C7f46EC5E78073D61DF6d002983F04a,0x42A605c07EdE0E1f648aB054775D6D4E38496144,0xc990EEAad8c943E3C6bA4cbcd8a54a949Fb83f78 -y 23 | 24 | snet --print-traceback service metadata-init ./service_spec1/ ExampleService --encoding json --service-type jsonrpc --group-name group1 25 | snet service metadata-add-endpoints group1 8.8.8.8:22 1.2.3.4:8080 26 | snet service metadata-set-fixed-price group1 0.0001 27 | snet service publish test2 tests -y -q 28 | snet service publish test2 tests2 -y -q 29 | snet organization info test2 30 | 31 | snet organization rem-members test2 0x32267d505B1901236508DcDa64C1D0d5B9DF639a,0x5c1011aB3C7f46EC5E78073D61DF6d002983F04a -y 32 | snet organization rem-members test2 0x42A605c07EdE0E1f648aB054775D6D4E38496144 -y 33 | snet organization rem-members test2 0x42A605c07EdE0E1f648aB054775D6D4E38496144,0xc990EEAad8c943E3C6bA4cbcd8a54a949Fb83f78 -y 34 | 35 | # second time shoudn't remove 36 | snet organization rem-members test2 0x42A605c07EdE0E1f648aB054775D6D4E38496144,0xc990EEAad8c943E3C6bA4cbcd8a54a949Fb83f78 -y 2>&1 | grep "No member was removed" 37 | 38 | snet organization list 39 | snet organization list-org-names 40 | # test2 should be found here 41 | snet organization list-my | grep test2 42 | snet organization info test2 43 | 44 | snet --print-traceback organization metadata-add-assets ./service_spec1/test hero_image 45 | result=$(< organization_metadata.json jq '.assets.hero_image') 46 | test $result = '"QmWQhwwnvK4YHvEarEguTDhz8o2kwvyfPhv5favs1VS4xm/test"' && echo "add asset with single value test case passed " || exit 1 47 | 48 | #remove assets 49 | snet --print-traceback organization metadata-remove-assets hero_image 50 | test "$(< organization_metadata.json jq '.assets.hero_image')" = '""' && echo "metadata-remove-assets test case passed " || exit 1 51 | 52 | snet --print-traceback organization metadata-remove-all-assets 53 | test "$(< organization_metadata.json jq '.assets')" = '{}' && echo "metadata-remove-all-assets test case passed " || exit 1 54 | 55 | # description test 56 | snet --print-traceback organization metadata-add-description --description "this is the dummy description of my org" \ 57 | --short-description "this is short description" --url "dummy.com" 58 | 59 | (test "$(< organization_metadata.json jq '.description.description')" = '"this is the dummy description of my org"' \ 60 | && test "$(< organization_metadata.json jq '.description.short_description')" = '"this is short description"' \ 61 | && test "$(< organization_metadata.json jq '.description.url')" = '"dummy.com"' \ 62 | && echo "passed") || exit 1 63 | 64 | # contacts test 65 | # add contact 66 | snet --print-traceback organization metadata-add-contact support --email dummy@dummy.io --phone 1234567890 67 | (test "$(< organization_metadata.json jq '.contacts | length')" = 1 \ 68 | && test "$(< organization_metadata.json jq '.contacts[0].contact_type')" = '"support"' \ 69 | && test "$(< organization_metadata.json jq '.contacts[0].phone')" = '"1234567890"' \ 70 | && test "$(< organization_metadata.json jq '.contacts[0].email_id')" = '"dummy@dummy.io"' \ 71 | && echo "passed") || exit 1 72 | 73 | # add contact without email and phone 74 | test "$(snet --print-traceback organization metadata-add-contact support)" = "email and phone both can not be empty" \ 75 | || exit 1 76 | 77 | # remove contact by type 78 | snet --print-traceback organization metadata-add-contact support --email support@dummy.io --phone 0987654321 79 | snet --print-traceback organization metadata-add-contact dummy --email dummy@dummy.io --phone 6789012345 80 | snet --print-traceback organization metadata-remove-contacts dummy 81 | test "$(< organization_metadata.json jq '.contacts | length')" = 2 && echo "passed" || exit 1 82 | 83 | # remove all contacts 84 | snet --print-traceback organization metadata-remove-all-contacts 85 | test "$(< organization_metadata.json jq '.contacts | length')" = 0 && echo "passed" || exit 1 86 | 87 | ## change test2 organization name to NEW_TEST2_NAME 88 | #snet organization change-name test2 NEW_TEST2_NAME -y 89 | #snet organization change-name test2 NEW_TEST2_NAME -y && exit 1 || echo "fail as expected" 90 | ## change test2 organization name back to test2 91 | #snet organization change-name test2 test2 -y 92 | 93 | snet organization change-owner test2 0x3b2b3C2e2E7C93db335E69D827F3CC4bC2A2A2cB -y 94 | snet organization change-owner test2 0x3b2b3C2e2E7C93db335E69D827F3CC4bC2A2A2cB -y && exit 1 || echo "fail as expected" 95 | snet organization add-members test2 0x32267d505B1901236508DcDa64C1D0d5B9DF639a -y && exit 1 || echo "fail as expected" 96 | 97 | # this should work because owner is the second account 98 | snet organization add-members test2 0x32267d505B1901236508DcDa64C1D0d5B9DF639a --wallet-index 1 -y 99 | 100 | snet organization delete test2 -y && exit 1 || echo "fail as expected" 101 | 102 | snet organization delete test2 --wallet-index 1 -y 103 | 104 | snet organization info test2 || echo "fail as expected" 105 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Configuration file for the Sphinx documentation builder. 4 | # 5 | # This file does only contain a selection of the most common options. For a 6 | # full list see the documentation: 7 | # http://www.sphinx-doc.org/en/master/config 8 | 9 | # -- Path setup -------------------------------------------------------------- 10 | 11 | # If extensions (or modules to document with autodoc) are in another directory, 12 | # add these directories to sys.path here. If the directory is relative to the 13 | # documentation root, use os.path.abspath to make it absolute, like shown here. 14 | # 15 | import os 16 | import sys 17 | sys.path.insert(0, os.path.abspath('../../snet/cli')) 18 | print(os.path.abspath('../../snet/cli')) 19 | 20 | # -- Project information ----------------------------------------------------- 21 | 22 | project = 'snet-cli' 23 | copyright = '2024, SingularityNet' 24 | author = 'SingularityNet' 25 | 26 | # The short X.Y version 27 | version = '' 28 | # The full version, including alpha/beta/rc tags 29 | release = '' 30 | 31 | 32 | # -- General configuration --------------------------------------------------- 33 | 34 | # If your documentation needs a minimal Sphinx version, state it here. 35 | # 36 | # needs_sphinx = '1.0' 37 | 38 | # Add any Sphinx extension module names here, as strings. They can be 39 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 40 | # ones. 41 | extensions = [ 42 | 'sphinx.ext.autodoc', 43 | 'sphinx.ext.doctest', 44 | 'sphinx.ext.intersphinx', 45 | 'sphinx.ext.coverage', 46 | 'sphinx.ext.mathjax', 47 | 'sphinx.ext.ifconfig', 48 | 'sphinx.ext.viewcode', 49 | 'sphinx.ext.githubpages', 50 | 'sphinxarg.ext', 51 | ] 52 | 53 | # Add any paths that contain templates here, relative to this directory. 54 | templates_path = ['snet-cli-templates'] 55 | 56 | # The suffix(es) of source filenames. 57 | # You can specify multiple suffix as a list of string: 58 | # 59 | # source_suffix = ['.rst', '.md'] 60 | source_suffix = '.rst' 61 | 62 | # The master toctree document. 63 | master_doc = 'index' 64 | 65 | # The language for content autogenerated by Sphinx. Refer to documentation 66 | # for a list of supported languages. 67 | # 68 | # This is also used if you do content translation via gettext catalogs. 69 | # Usually you set "language" from the command line for these cases. 70 | language = 'en' 71 | 72 | # List of patterns, relative to source directory, that match files and 73 | # directories to ignore when looking for source files. 74 | # This pattern also affects html_static_path and html_extra_path. 75 | exclude_patterns = [] 76 | 77 | # The name of the Pygments (syntax highlighting) style to use. 78 | pygments_style = None 79 | 80 | 81 | # -- Options for HTML output ------------------------------------------------- 82 | 83 | # The theme to use for HTML and HTML Help pages. See the documentation for 84 | # a list of builtin themes. 85 | # 86 | #html_theme = 'alabaster' 87 | html_theme = "sphinx_rtd_theme" 88 | html_logo = "img/singularityNET.png" 89 | 90 | # Theme options are theme-specific and customize the look and feel of a theme 91 | # further. For a list of options available for each theme, see the 92 | # documentation. 93 | # 94 | # html_theme_options = {} 95 | 96 | # Add any paths that contain custom static files (such as style sheets) here, 97 | # relative to this directory. They are copied after the builtin static files, 98 | # so a file named "default.css" will overwrite the builtin "default.css". 99 | html_static_path = ['snet-cli-static'] 100 | 101 | # Custom sidebar templates, must be a dictionary that maps document names 102 | # to template names. 103 | # 104 | # The default sidebars (for documents that don't match any pattern) are 105 | # defined by theme itself. Builtin themes are using these templates by 106 | # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', 107 | # 'searchbox.html']``. 108 | # 109 | # html_sidebars = {} 110 | 111 | 112 | # -- Options for HTMLHelp output --------------------------------------------- 113 | 114 | # Output file base name for HTML help builder. 115 | htmlhelp_basename = 'snet-clidoc' 116 | 117 | 118 | # -- Options for LaTeX output ------------------------------------------------ 119 | 120 | latex_elements = { 121 | # The paper size ('letterpaper' or 'a4paper'). 122 | # 123 | # 'papersize': 'letterpaper', 124 | 125 | # The font size ('10pt', '11pt' or '12pt'). 126 | # 127 | # 'pointsize': '10pt', 128 | 129 | # Additional stuff for the LaTeX preamble. 130 | # 131 | # 'preamble': '', 132 | 133 | # Latex figure (float) alignment 134 | # 135 | # 'figure_align': 'htbp', 136 | } 137 | 138 | # Grouping the document tree into LaTeX files. List of tuples 139 | # (source start file, target name, title, 140 | # author, documentclass [howto, manual, or own class]). 141 | latex_documents = [ 142 | (master_doc, 'snet-cli.tex', 'snet-cli Documentation', 143 | 'SingularityNet', 'manual'), 144 | ] 145 | 146 | 147 | # -- Options for manual page output ------------------------------------------ 148 | 149 | # One entry per manual page. List of tuples 150 | # (source start file, name, description, authors, manual section). 151 | man_pages = [ 152 | (master_doc, 'snet-cli', 'snet-cli Documentation', 153 | [author], 1) 154 | ] 155 | 156 | 157 | # -- Options for Texinfo output ---------------------------------------------- 158 | 159 | # Grouping the document tree into Texinfo files. List of tuples 160 | # (source start file, target name, title, author, 161 | # dir menu entry, description, category) 162 | texinfo_documents = [ 163 | (master_doc, 'snet-cli', 'snet-cli Documentation', 164 | author, 'snet-cli', 'One line description of project.', 165 | 'Miscellaneous'), 166 | ] 167 | 168 | 169 | # -- Options for Epub output ------------------------------------------------- 170 | 171 | # Bibliographic Dublin Core info. 172 | epub_title = project 173 | 174 | # The unique identifier of the text. This can be a ISBN number 175 | # or the project homepage. 176 | # 177 | # epub_identifier = '' 178 | 179 | # A unique identification for the text. 180 | # 181 | # epub_uid = '' 182 | 183 | # A list of files that should not be packed into the epub file. 184 | epub_exclude_files = ['search.html'] 185 | 186 | 187 | # -- Extension configuration ------------------------------------------------- 188 | 189 | # -- Options for intersphinx extension --------------------------------------- 190 | 191 | # Example configuration for intersphinx: refer to the Python standard library. 192 | # intersphinx_mapping = {'https://docs.python.org/': None} 193 | -------------------------------------------------------------------------------- /snet/cli/test/functional_tests/script4_only_with_networks.sh: -------------------------------------------------------------------------------- 1 | # We try to get config address from the differnt sources. 2 | # The order of priorioty is following: 3 | # - command line argument (at or _at) 4 | # - current session configuration (current__at) 5 | # - networks/*json 6 | 7 | # In this test we check this priority 8 | 9 | rm -rf ../../snet/snet_cli/resources/contracts/networks/*.json 10 | 11 | #unset addresses 12 | snet unset current_singularitynettoken_at || echo "could fail if hasn't been set (it is ok)" 13 | snet unset current_registry_at || echo "could fail if hasn't been set (it is ok)" 14 | snet unset current_multipartyescrow_at || echo "could fail if hasn't been set (it is ok)" 15 | snet session 16 | # this should fail without addresses 17 | snet account balance && exit 1 || echo "fail as expected" 18 | snet --print-traceback organization metadata-init org1 testo individual && exit 1 || echo "fail as expected" 19 | snet organization create testo testo -y -q && exit 1 || echo "fail as expected" 20 | 21 | # set networks 22 | echo '{"829257324":{"events":{},"links":{},"address":"0x5c7a4290f6f8ff64c69eeffdfafc8644a4ec3a4e","transactionHash":""}}' >../../snet/snet_cli/resources/contracts/networks/MultiPartyEscrow.json 23 | echo '{"829257324":{"events":{},"links":{},"address":"0x4e74fefa82e83e0964f0d9f53c68e03f7298a8b2","transactionHash":""}}' >../../snet/snet_cli/resources/contracts/networks/Registry.json 24 | echo '{"829257324":{"events":{},"links":{},"address":"0x6e5f20669177f5bdf3703ec5ea9c4d4fe3aabd14","transactionHash":""}}' >../../snet/snet_cli/resources/contracts/networks/SingularityNetToken.json 25 | 26 | # this should work 27 | snet account balance 28 | snet --print-traceback organization metadata-init org1 testo individual 29 | snet --print-traceback organization add-group group1 0x42A605c07EdE0E1f648aB054775D6D4E38496144 5.5.6.7:8089 30 | snet --print-traceback organization add-group group2 0x42A605c07EdE0E1f648aB054775D6D4E38496144 1.2.1.1:8089 31 | snet organization create testo -y -q 32 | snet organization delete testo -y -q 33 | 34 | # this should fail (addresses are INVALID) 35 | snet organization create testo -y -q --registry-at 0x1e74fefa82e83e0964f0d9f53c68e03f7298a8b2 && exit 1 || echo "fail as expected" 36 | snet account balance --snt 0x1e5f20669177f5bdf3703ec5ea9c4d4fe3aabd14 --mpe 0x5c7a4290f6f8ff64c69eeffdfafc8644a4ec3a4e && exit 1 || echo "fail as expected" 37 | 38 | # set INVALID addresses 39 | snet set current_singularitynettoken_at 0x1e5f20669177f5bdf3703ec5ea9c4d4fe3aabd14 40 | snet set current_registry_at 0x1e74fefa82e83e0964f0d9f53c68e03f7298a8b2 41 | snet set current_multipartyescrow_at 0x1c7a4290f6f8ff64c69eeffdfafc8644a4ec3a4e 42 | 43 | # this should fail because INVALID address 44 | snet account balance && exit 1 || echo "fail as expected" 45 | snet --print-traceback organization metadata-init org1 testo individual && exit 1 || echo "fail as expected" 46 | snet organization create testo -y -q && exit 1 || echo "fail as expected" 47 | 48 | # this should work because command line has more priority 49 | snet account balance --snt 0x6e5f20669177f5bdf3703ec5ea9c4d4fe3aabd14 --mpe 0x5c7a4290f6f8ff64c69eeffdfafc8644a4ec3a4e 50 | snet --print-traceback organization metadata-init org1 testo individual --registry-at 0x4e74fefa82e83e0964f0d9f53c68e03f7298a8b2 51 | snet --print-traceback organization add-group group1 0x42A605c07EdE0E1f648aB054775D6D4E38496144 5.5.6.7:8089 --registry-at 0x4e74fefa82e83e0964f0d9f53c68e03f7298a8b2 52 | snet --print-traceback organization add-group group2 0x42A605c07EdE0E1f648aB054775D6D4E38496144 1.2.1.1:8089 --registry-at 0x4e74fefa82e83e0964f0d9f53c68e03f7298a8b2 53 | 54 | snet organization create testo -y -q --registry-at 0x4e74fefa82e83e0964f0d9f53c68e03f7298a8b2 55 | snet organization delete testo -y -q --registry-at 0x4e74fefa82e83e0964f0d9f53c68e03f7298a8b2 56 | 57 | # set INVALID networks 58 | echo '{"829257324":{"events":{},"links":{},"address":"0x1c7a4290f6f8ff64c69eeffdfafc8644a4ec3a4e","transactionHash":""}}' >../../snet/snet_cli/resources/contracts/networks/MultiPartyEscrow.json 59 | echo '{"829257324":{"events":{},"links":{},"address":"0x1e74fefa82e83e0964f0d9f53c68e03f7298a8b2","transactionHash":""}}' >../../snet/snet_cli/resources/contracts/networks/Registry.json 60 | echo '{"829257324":{"events":{},"links":{},"address":"0x1e5f20669177f5bdf3703ec5ea9c4d4fe3aabd14","transactionHash":""}}' >../../snet/snet_cli/resources/contracts/networks/SingularityNetToken.json 61 | 62 | # this should fail (because addresses in networks are invalid ) 63 | snet account balance && exit 1 || echo "fail as expected" 64 | snet --print-traceback organization metadata-init org1 testo individual && exit 1 || echo "fail as expected" 65 | snet organization create testo -y -q && exit 1 || echo "fail as expected" 66 | 67 | # set VALID session 68 | snet set current_singularitynettoken_at 0x6e5f20669177f5bdf3703ec5ea9c4d4fe3aabd14 69 | snet set current_registry_at 0x4e74fefa82e83e0964f0d9f53c68e03f7298a8b2 70 | snet set current_multipartyescrow_at 0x5c7a4290f6f8ff64c69eeffdfafc8644a4ec3a4e 71 | 72 | # this should work 73 | snet account balance 74 | snet --print-traceback organization metadata-init org1 testo individual 75 | snet --print-traceback organization add-group group1 0x42A605c07EdE0E1f648aB054775D6D4E38496144 5.5.6.7:8089 76 | snet --print-traceback organization add-group group2 0x42A605c07EdE0E1f648aB054775D6D4E38496144 1.2.1.1:8089 77 | snet organization create testo -y -q 78 | snet organization delete testo -y -q 79 | 80 | # set INVALID addresses 81 | snet set current_singularitynettoken_at 0x1e5f20669177f5bdf3703ec5ea9c4d4fe3aabd14 82 | snet set current_registry_at 0x1e74fefa82e83e0964f0d9f53c68e03f7298a8b2 83 | snet set current_multipartyescrow_at 0x1c7a4290f6f8ff64c69eeffdfafc8644a4ec3a4e 84 | 85 | # this should fail (because addresses in networks are invalid ) 86 | snet account balance && exit 1 || echo "fail as expected" 87 | snet --print-traceback organization metadata-init org1 testo individual && exit 1 || echo "fail as expected" 88 | snet organization create testo -y -q && exit 1 || echo "fail as expected" 89 | 90 | # this should work because command line has more priority 91 | snet account balance --snt 0x6e5f20669177f5bdf3703ec5ea9c4d4fe3aabd14 --mpe 0x5c7a4290f6f8ff64c69eeffdfafc8644a4ec3a4e 92 | snet --print-traceback organization metadata-init org1 testo individual --registry-at 0x4e74fefa82e83e0964f0d9f53c68e03f7298a8b2 93 | snet --print-traceback organization add-group group1 0x42A605c07EdE0E1f648aB054775D6D4E38496144 5.5.6.7:8089 --registry-at 0x4e74fefa82e83e0964f0d9f53c68e03f7298a8b2 94 | snet --print-traceback organization add-group group2 0x42A605c07EdE0E1f648aB054775D6D4E38496144 1.2.1.1:8089 --registry-at 0x4e74fefa82e83e0964f0d9f53c68e03f7298a8b2 95 | snet organization create testo -y -q --registry-at 0x4e74fefa82e83e0964f0d9f53c68e03f7298a8b2 96 | snet organization delete testo -y -q --registry-at 0x4e74fefa82e83e0964f0d9f53c68e03f7298a8b2 97 | -------------------------------------------------------------------------------- /snet/cli/resources/org_schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft-07/schema#", 3 | "title": "Organization Metadata", 4 | "description": "Schema of a correct organization metadata file", 5 | "type": "object", 6 | "properties": { 7 | "org_name": { 8 | "description": "Organization name", 9 | "type": "string", 10 | "minLength": 1 11 | }, 12 | "org_id": { 13 | "description": "Organization id", 14 | "type": "string", 15 | "minLength": 1 16 | }, 17 | "org_type": { 18 | "description": "Organization type [organization, individual]", 19 | "type": "string", 20 | "enum": [ "organization", "individual" ] 21 | }, 22 | "description": { 23 | "description": "Organization description", 24 | "type": "object", 25 | "properties": { 26 | "description": { 27 | "description": "Organization full description", 28 | "type": "string", 29 | "minLength": 1 30 | }, 31 | "short_description": { 32 | "description": "Organization short description", 33 | "type": "string", 34 | "minLength": 1, 35 | "maxLength": 160 36 | }, 37 | "url": { 38 | "description": "Organization url", 39 | "type": "string", 40 | "format": "url" 41 | } 42 | }, 43 | "additionalProperties": false, 44 | "required": ["description", "short_description", "url"] 45 | }, 46 | "assets": { 47 | "description": "Organization assets", 48 | "type": "object", 49 | "properties": { 50 | "hero_image": { 51 | "description": "Organization hero image", 52 | "type": "string", 53 | "minLength": 1 54 | } 55 | }, 56 | "additionalProperties": true 57 | }, 58 | "contacts": { 59 | "description": "Organization contacts", 60 | "type": "array", 61 | "items": { 62 | "type": "object", 63 | "properties": { 64 | "email": { 65 | "description": "Contact email", 66 | "type": "string" 67 | }, 68 | "phone": { 69 | "description": "Contact phone", 70 | "type": "string" 71 | }, 72 | "contact_type": { 73 | "description": "Contact type [general, support]", 74 | "type": "string", 75 | "enum": [ "general", "support" ] 76 | } 77 | }, 78 | "additionalProperties": false, 79 | "required": ["contact_type", "email", "phone"] 80 | } 81 | }, 82 | "groups": { 83 | "description": "Organization groups", 84 | "type": "array", 85 | "items": { 86 | "type": "object", 87 | "properties": { 88 | "group_name": { 89 | "description": "Group name", 90 | "type": "string", 91 | "minLength": 1 92 | }, 93 | "group_id": { 94 | "description": "Group id", 95 | "type": "string", 96 | "minLength": 1 97 | }, 98 | "payment": { 99 | "description": "Group payment", 100 | "type": "object", 101 | "properties": { 102 | "payment_address": { 103 | "description": "Payment address", 104 | "type": "string", 105 | "minLength": 1 106 | }, 107 | "payment_expiration_threshold": { 108 | "description": "Payment expiration threshold", 109 | "type": "integer", 110 | "minimum": 1 111 | }, 112 | "payment_channel_storage_type": { 113 | "description": "Payment channel storage type (only 'etcd' is supported)", 114 | "type": "string", 115 | "minLength": 1, 116 | "enum": [ 117 | "etcd" 118 | ] 119 | }, 120 | "payment_channel_storage_client": { 121 | "description": "Payment channel storage client", 122 | "type": "object", 123 | "properties": { 124 | "connection_timeout": { 125 | "description": "Payment channel storage connection timeout", 126 | "type": "string", 127 | "pattern": "^\\d{1,3}(s|ms)$" 128 | }, 129 | "request_timeout": { 130 | "description": "Payment channel storage request timeout", 131 | "type": "string", 132 | "pattern": "^\\d{1,3}(s|ms)$" 133 | }, 134 | "endpoints": { 135 | "description": "Payment channel storage endpoints", 136 | "type": "array", 137 | "items": { 138 | "type": "string", 139 | "minLength": 1, 140 | "format": "url" 141 | }, 142 | "minItems": 1 143 | } 144 | }, 145 | "additionalProperties": false, 146 | "required": [ 147 | "connection_timeout", 148 | "request_timeout", 149 | "endpoints" 150 | ] 151 | } 152 | }, 153 | "additionalProperties": false, 154 | "required": [ 155 | "payment_address", 156 | "payment_expiration_threshold", 157 | "payment_channel_storage_type", 158 | "payment_channel_storage_client" 159 | ] 160 | } 161 | }, 162 | "additionalProperties": false, 163 | "required": [ 164 | "group_name", 165 | "group_id", 166 | "payment" 167 | ] 168 | }, 169 | "minItems": 1 170 | } 171 | }, 172 | "additionalProperties": false, 173 | "required": [ 174 | "org_name", 175 | "org_type", 176 | "description", 177 | "assets", 178 | "contacts", 179 | "groups" 180 | ] 181 | } -------------------------------------------------------------------------------- /snet/cli/test/functional_tests/simple_daemon/test_simple_daemon.py: -------------------------------------------------------------------------------- 1 | from concurrent import futures 2 | import time 3 | import web3 4 | 5 | from snet_cli.config import Config 6 | 7 | from snet_cli.commands.mpe_channel import MPEChannelCommand 8 | from snet.snet_cli.utils.utils import compile_proto, DefaultAttributeObject 9 | 10 | 11 | compile_proto("../service_spec1", ".", proto_file = "ExampleService.proto") 12 | compile_proto("../../../snet/snet_cli/resources/proto/", ".", proto_file = "state_service.proto") 13 | compile_proto("../../../snet/snet_cli/resources/proto/", ".", proto_file = "control_service.proto") 14 | PRICE = 10000 15 | 16 | import grpc 17 | 18 | import ExampleService_pb2 19 | import ExampleService_pb2_grpc 20 | 21 | import state_service_pb2 22 | import state_service_pb2_grpc 23 | 24 | import control_service_pb2 25 | import control_service_pb2_grpc 26 | 27 | payments_unclaimed = dict() 28 | payments_in_progress = dict() 29 | 30 | # we use MPEChannelCommand._get_channel_state_from_blockchain to get channel state from blockchain 31 | # we need it to remove already claimed payments from payments_in_progress 32 | # remove all payments_in_progress with nonce < blockchain nonce 33 | def remove_already_claimed_payments(): 34 | conf = Config() 35 | cc = MPEChannelCommand(conf, DefaultAttributeObject()) 36 | to_remove = [] 37 | for channel_id in payments_in_progress: 38 | blockchain = cc._get_channel_state_from_blockchain(channel_id) 39 | if (blockchain["nonce"] > payments_in_progress[channel_id]["nonce"]): 40 | to_remove.append(channel_id) 41 | for channel_id in to_remove: 42 | print("remove payment for channel %i from payments_in_progress"%channel_id) 43 | del payments_in_progress[channel_id] 44 | 45 | def get_current_channel_state(channel_id): 46 | if (channel_id in payments_unclaimed): 47 | nonce = payments_unclaimed[channel_id]["nonce"] 48 | amount = payments_unclaimed[channel_id]["amount"] 49 | signature = payments_unclaimed[channel_id]["signature"] 50 | else: 51 | nonce = 0 52 | amount = 0 53 | signature = "".encode("ascii") 54 | return nonce, amount, signature 55 | 56 | 57 | class ExampleService(ExampleService_pb2_grpc.ExampleServiceServicer): 58 | def classify(self, request, context): 59 | metadata = dict(context.invocation_metadata()) 60 | channel_id = int(metadata["snet-payment-channel-id"]) 61 | nonce = int(metadata["snet-payment-channel-nonce"]) 62 | amount = int(metadata["snet-payment-channel-amount"]) 63 | signature = metadata["snet-payment-channel-signature-bin"] 64 | payment = {"channel_id": channel_id, "nonce": nonce, "amount": amount, "signature": signature} 65 | 66 | # we check nonce and amount, but we don't check signature 67 | current_nonce, current_signed_amount, _ = get_current_channel_state(channel_id) 68 | 69 | if (current_nonce != nonce): 70 | raise Exception("nonce is incorrect") 71 | 72 | if (current_signed_amount + PRICE != amount): 73 | raise Exception("Signed amount is incorrect %i vs %i"%(current_signed_amount + PRICE, amount)) 74 | 75 | payments_unclaimed[channel_id] = payment 76 | return ExampleService_pb2.ClassifyResponse(predictions=["prediction1", "prediction2" ], confidences=[0.42, 0.43], binary_field = int(12345**5).to_bytes(10,byteorder='big')) 77 | 78 | 79 | class PaymentChannelStateService(state_service_pb2_grpc.PaymentChannelStateServiceServicer): 80 | def GetChannelState(self, request, context): 81 | channel_id = int.from_bytes(request.channel_id, byteorder='big') 82 | nonce, amount, signature = get_current_channel_state(channel_id) 83 | if (channel_id in payments_in_progress): 84 | if (payments_in_progress[channel_id]["nonce"] != nonce - 1): 85 | raise Exception("Bad payment in payments_in_progress") 86 | return state_service_pb2.ChannelStateReply(current_nonce = web3.Web3.toBytes(nonce), 87 | current_signed_amount = web3.Web3.toBytes(amount), 88 | current_signature = signature, 89 | old_nonce_signed_amount = web3.Web3.toBytes(payments_in_progress[channel_id]["amount"]), 90 | old_nonce_signature = payments_in_progress[channel_id]["signature"]) 91 | return state_service_pb2.ChannelStateReply(current_nonce = web3.Web3.toBytes(nonce), 92 | current_signed_amount = web3.Web3.toBytes(amount), 93 | current_signature = signature) 94 | 95 | 96 | class ProviderControlService(control_service_pb2_grpc.ProviderControlServiceServicer): 97 | def GetListUnclaimed(self, request, context): 98 | payments = [] 99 | for channel_id in payments_unclaimed: 100 | nonce = payments_unclaimed[channel_id]["nonce"] 101 | amount = payments_unclaimed[channel_id]["amount"] 102 | payment = control_service_pb2.PaymentReply( 103 | channel_id = web3.Web3.toBytes(channel_id), 104 | channel_nonce = web3.Web3.toBytes(nonce), 105 | signed_amount = web3.Web3.toBytes(amount)) 106 | payments.append(payment) 107 | return control_service_pb2.PaymentsListReply(payments = payments) 108 | 109 | def GetListInProgress(self, request, context): 110 | remove_already_claimed_payments() 111 | 112 | payments = [] 113 | for channel_id in payments_in_progress: 114 | p = payments_in_progress[channel_id] 115 | payment = control_service_pb2.PaymentReply( 116 | channel_id = web3.Web3.toBytes(channel_id), 117 | channel_nonce = web3.Web3.toBytes(p["nonce"]), 118 | signed_amount = web3.Web3.toBytes(p["amount"]), 119 | signature = p["signature"]) 120 | payments.append(payment) 121 | return control_service_pb2.PaymentsListReply(payments = payments) 122 | 123 | def StartClaim(self, request, context): 124 | remove_already_claimed_payments() 125 | 126 | channel_id = int.from_bytes(request.channel_id, byteorder='big') 127 | 128 | if (channel_id not in payments_unclaimed): 129 | raise Exception("channel_id not in payments_unclaimed") 130 | 131 | p = payments_unclaimed[channel_id] 132 | nonce = p["nonce"] 133 | amount = p["amount"] 134 | signature = p["signature"] 135 | payments_in_progress[channel_id] = p 136 | payments_unclaimed[channel_id] = {"nonce" : nonce + 1, "amount" : 0, "signature" : bytes(0)} 137 | 138 | return control_service_pb2.PaymentReply( 139 | channel_id = web3.Web3.toBytes(channel_id), 140 | channel_nonce = web3.Web3.toBytes(nonce), 141 | signed_amount = web3.Web3.toBytes(amount), 142 | signature = signature) 143 | 144 | 145 | def serve(): 146 | server = grpc.server(futures.ThreadPoolExecutor(max_workers=1)) 147 | ExampleService_pb2_grpc.add_ExampleServiceServicer_to_server(ExampleService(), server) 148 | state_service_pb2_grpc.add_PaymentChannelStateServiceServicer_to_server(PaymentChannelStateService(), server) 149 | control_service_pb2_grpc.add_ProviderControlServiceServicer_to_server(ProviderControlService(), server) 150 | server.add_insecure_port('[::]:50051') 151 | server.start() 152 | try: 153 | while True: 154 | time.sleep(60*60*24) 155 | except KeyboardInterrupt: 156 | server.stop(0) 157 | 158 | 159 | if __name__ == '__main__': 160 | serve() 161 | 162 | -------------------------------------------------------------------------------- /snet/cli/commands/mpe_treasurer.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | import web3 4 | from snet.cli.utils.proto_utils import import_protobuf_from_dir 5 | from snet.cli.utils.utils import compile_proto, open_grpc_channel, int4bytes_big, RESOURCES_PATH 6 | from snet.cli.commands.mpe_client import MPEClientCommand 7 | from snet.cli.utils.token2cogs import cogs2strtoken 8 | 9 | 10 | class MPETreasurerCommand(MPEClientCommand): 11 | """ We inherit MPEChannelCommand because we need _get_channel_state_from_blockchain """ 12 | 13 | def _sign_message_list_unclaimed(self, mpe_address, current_block): 14 | message = self.w3.solidity_keccak( 15 | ["string", "address", "uint256"], 16 | ["__list_unclaimed", mpe_address, current_block]) 17 | return self.ident.sign_message_after_solidity_keccak(message) 18 | 19 | def _sign_message_list_in_progress(self, mpe_address, current_block): 20 | message = self.w3.solidity_keccak( 21 | ["string", "address", "uint256"], 22 | ["__list_in_progress", mpe_address, current_block]) 23 | return self.ident.sign_message_after_solidity_keccak(message) 24 | 25 | def _sign_message_start_claim(self, mpe_address, channel_id, channel_nonce): 26 | message = self.w3.solidity_keccak( 27 | ["string", "address", "uint256", "uint256"], 28 | ["__start_claim", mpe_address, channel_id, channel_nonce]) 29 | return self.ident.sign_message_after_solidity_keccak(message) 30 | 31 | def _get_stub_and_request_classes(self, service_name): 32 | """ import protobuf and return stub and request class """ 33 | # Compile protobuf if needed 34 | codegen_dir = Path.home().joinpath(".snet", "mpe_client", "control_service") 35 | proto_dir = RESOURCES_PATH.joinpath("proto") 36 | if not codegen_dir.joinpath("control_service_pb2.py").is_file(): 37 | compile_proto(proto_dir, codegen_dir, 38 | proto_file="control_service.proto") 39 | 40 | stub_class, request_class, _ = import_protobuf_from_dir( 41 | codegen_dir, service_name) 42 | return stub_class, request_class 43 | 44 | def _decode_PaymentReply(self, p): 45 | return {"channel_id": int4bytes_big(p.channel_id), "nonce": int4bytes_big(p.channel_nonce), "amount": int4bytes_big(p.signed_amount), "signature": p.signature} 46 | 47 | def _call_GetListUnclaimed(self, grpc_channel): 48 | stub_class, request_class = self._get_stub_and_request_classes( 49 | "GetListUnclaimed") 50 | stub = stub_class(grpc_channel) 51 | 52 | mpe_address = self.get_mpe_address() 53 | current_block = self.ident.w3.eth.block_number 54 | signature = self._sign_message_list_unclaimed( 55 | mpe_address, current_block) 56 | request = request_class( 57 | mpe_address=mpe_address, current_block=current_block, signature=bytes(signature)) 58 | response = getattr(stub, "GetListUnclaimed")(request) 59 | 60 | for p in response.payments: 61 | if len(p.signature) > 0: 62 | raise Exception( 63 | "Signature was set in GetListUnclaimed. Response is invalid") 64 | 65 | return [self._decode_PaymentReply(p) for p in response.payments] 66 | 67 | def _call_GetListInProgress(self, grpc_channel): 68 | stub_class, request_class = self._get_stub_and_request_classes( 69 | "GetListInProgress") 70 | stub = stub_class(grpc_channel) 71 | 72 | mpe_address = self.get_mpe_address() 73 | current_block = self.ident.w3.eth.block_number 74 | signature = self._sign_message_list_in_progress( 75 | mpe_address, current_block) 76 | request = request_class( 77 | mpe_address=mpe_address, current_block=current_block, signature=bytes(signature)) 78 | response = getattr(stub, "GetListInProgress")(request) 79 | return [self._decode_PaymentReply(p) for p in response.payments] 80 | 81 | def _call_StartClaim(self, grpc_channel, channel_id, channel_nonce): 82 | stub_class, request_class = self._get_stub_and_request_classes( 83 | "StartClaim") 84 | stub = stub_class(grpc_channel) 85 | mpe_address = self.get_mpe_address() 86 | signature = self._sign_message_start_claim( 87 | mpe_address, channel_id, channel_nonce) 88 | request = request_class(mpe_address=mpe_address, channel_id=web3.Web3.to_bytes( 89 | channel_id), signature=bytes(signature)) 90 | response = getattr(stub, "StartClaim")(request) 91 | return self._decode_PaymentReply(response) 92 | 93 | def print_unclaimed(self): 94 | grpc_channel = open_grpc_channel(self.args.endpoint) 95 | payments = self._call_GetListUnclaimed(grpc_channel) 96 | self._printout("# channel_id channel_nonce signed_amount (ASI(FET))") 97 | total = 0 98 | for p in payments: 99 | self._printout("%i %i %s" % ( 100 | p["channel_id"], p["nonce"], cogs2strtoken(p["amount"]))) 101 | total += p["amount"] 102 | self._printout("# total_unclaimed_in_ASI(FET) = %s" % cogs2strtoken(total)) 103 | 104 | def _blockchain_claim(self, payments): 105 | for payment in payments: 106 | channel_id = payment["channel_id"] 107 | amount = payment["amount"] 108 | sig = payment["signature"] 109 | if len(sig) != 65: 110 | raise Exception( 111 | "Length of signature is incorrect: %i instead of 65" % (len(sig))) 112 | v, r, s = int(sig[-1]), sig[:32], sig[32:64] 113 | v = v % 27 + 27 114 | params = [channel_id, amount, amount, v, r, s, False] 115 | self.transact_contract_command( 116 | "MultiPartyEscrow", "channelClaim", params) 117 | 118 | def _start_claim_channels(self, grpc_channel, channels_ids): 119 | """ Safely run StartClaim for given channels """ 120 | unclaimed_payments = self._call_GetListUnclaimed(grpc_channel) 121 | unclaimed_payments_dict = { 122 | p["channel_id"]: p for p in unclaimed_payments} 123 | 124 | to_claim = [] 125 | for channel_id in channels_ids: 126 | if channel_id not in unclaimed_payments_dict or unclaimed_payments_dict[channel_id]["amount"] == 0: 127 | self._printout( 128 | "There is nothing to claim for channel %i, we skip it" % channel_id) 129 | continue 130 | blockchain = self._get_channel_state_from_blockchain(channel_id) 131 | if unclaimed_payments_dict[channel_id]["nonce"] != blockchain["nonce"]: 132 | self._printout( 133 | "Old payment for channel %i is still in progress. Please run claim for this channel later." % channel_id) 134 | continue 135 | to_claim.append((channel_id, blockchain["nonce"])) 136 | 137 | payments = [self._call_StartClaim( 138 | grpc_channel, channel_id, nonce) for channel_id, nonce in to_claim] 139 | return payments 140 | 141 | def _claim_in_progress_and_claim_channels(self, grpc_channel, channels): 142 | """ Claim all 'pending' payments in progress and after we claim given channels """ 143 | # first we get the list of all 'payments in progress' in case we 'lost' some payments. 144 | payments = self._call_GetListInProgress(grpc_channel) 145 | if len(payments) > 0: 146 | self._printout( 147 | "There are %i payments in 'progress' (they haven't been claimed in blockchain). We will claim them." % len(payments)) 148 | self._blockchain_claim(payments) 149 | payments = self._start_claim_channels(grpc_channel, channels) 150 | self._blockchain_claim(payments) 151 | 152 | def claim_channels(self): 153 | self.check_ident() 154 | grpc_channel = open_grpc_channel(self.args.endpoint) 155 | self._claim_in_progress_and_claim_channels(grpc_channel, self.args.channels) 156 | 157 | def claim_all_channels(self): 158 | self.check_ident() 159 | grpc_channel = open_grpc_channel(self.args.endpoint) 160 | # we take list of all channels 161 | unclaimed_payments = self._call_GetListUnclaimed(grpc_channel) 162 | channels = [p["channel_id"] for p in unclaimed_payments] 163 | self._claim_in_progress_and_claim_channels(grpc_channel, channels) 164 | 165 | def claim_almost_expired_channels(self): 166 | self.check_ident() 167 | grpc_channel = open_grpc_channel(self.args.endpoint) 168 | # we take list of all channels 169 | unclaimed_payments = self._call_GetListUnclaimed(grpc_channel) 170 | 171 | channels = [] 172 | for p in unclaimed_payments: 173 | if p["amount"] == 0: 174 | continue 175 | channel_id = p["channel_id"] 176 | blockchain = self._get_channel_state_from_blockchain(channel_id) 177 | if blockchain["expiration"] < self.ident.w3.eth.block_number + self.args.expiration_threshold: 178 | self._printout("We are going to claim channel %i" % channel_id) 179 | channels.append(channel_id) 180 | self._claim_in_progress_and_claim_channels(grpc_channel, channels) 181 | -------------------------------------------------------------------------------- /snet/cli/resources/service_schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json-schema.org/draft-07/schema#", 3 | "title": "Service Metadata", 4 | "description": "Schema of a correct service metadata file", 5 | "type": "object", 6 | "properties": { 7 | "version": { 8 | "description": "Version of service", 9 | "type": "integer", 10 | "minimum": 1 11 | }, 12 | "display_name": { 13 | "description": "Service display name", 14 | "type": "string", 15 | "minLength": 1 16 | }, 17 | "encoding": { 18 | "description": "Service encoding [proto, json]", 19 | "type": "string", 20 | "enum": [ "proto", "json" ] 21 | }, 22 | "service_type": { 23 | "description": "Service type [grpc, jsonrpc, process, http]", 24 | "type": "string", 25 | "enum": [ "grpc", "jsonrpc", "process", "http" ] 26 | }, 27 | "model_ipfs_hash": { 28 | "description": "Hash of directory which contains protobuf files", 29 | "type": "string" 30 | }, 31 | "mpe_address": { 32 | "description": "Address of MultiPartyEscrow contract", 33 | "type": "string", 34 | "minLength": 1 35 | }, 36 | "groups": { 37 | "description": "Multiple groups can be associated with a service, one payment type is associated with every group", 38 | "type": "array", 39 | "items": { 40 | "type": "object", 41 | "properties": { 42 | "group_name": { 43 | "description": "Name of the payment group", 44 | "type": "string", 45 | "minLength": 1 46 | }, 47 | "pricing": { 48 | "description": "Pricing information", 49 | "type": "array", 50 | "items": { 51 | "type": "object", 52 | "properties": { 53 | "price_model": { 54 | "description": "[fixed-price, method-price]", 55 | "type": "string", 56 | "enum": [ "fixed_price", "method_price" ] 57 | }, 58 | "price_in_cogs": { 59 | "description": "Price in ASI(FET) tokens for all methods", 60 | "type": "number" 61 | }, 62 | "default": { 63 | "description": "Necessary for daemon functionality", 64 | "type": "boolean" 65 | } 66 | }, 67 | "additionalProperties": false, 68 | "required": [ "price_model", "price_in_cogs", "default" ] 69 | }, 70 | "minItems": 1 71 | }, 72 | "endpoints": { 73 | "description": "Storage end points for the clients to connect", 74 | "type": "array", 75 | "items": { 76 | "type": "string", 77 | "minLength": 1 78 | }, 79 | "minItems": 1 80 | }, 81 | "daemon_addresses": { 82 | "description": "Ethereum public addresses of daemon in given payment group of service", 83 | "type": "array", 84 | "items": { 85 | "type": "string", 86 | "minLength": 1 87 | }, 88 | "minItems": 1 89 | }, 90 | "free_calls": { 91 | "description": "Number of free calls", 92 | "type": "integer" 93 | }, 94 | "free_call_signer_address": { 95 | "description": "Public key address used for validating signatures requested specially for free call", 96 | "type": "string", 97 | "minLength": 1 98 | }, 99 | "group_id": { 100 | "description": "Group ID", 101 | "type": "string", 102 | "minLength": 1 103 | } 104 | }, 105 | "additionalProperties": false, 106 | "required": [ "group_name", "pricing", "endpoints", "daemon_addresses", "group_id" ] 107 | }, 108 | "minItems": 1 109 | }, 110 | "service_description": { 111 | "description": "Service description [user-guide, long-desc, short-desc]", 112 | "type": "object", 113 | "properties": { 114 | "url": { 115 | "description": "Service user guide", 116 | "type": "string" 117 | }, 118 | "long_description": { 119 | "description": "Service long description", 120 | "type": "string" 121 | }, 122 | "short_description": { 123 | "description": "Service short description", 124 | "type": "string" 125 | }, 126 | "description": { 127 | "description": "Service description", 128 | "type": "string" 129 | } 130 | }, 131 | "additionalProperties": true, 132 | "required": ["url", "short_description"], 133 | "anyOf": [ 134 | {"required": ["long_description"]}, 135 | {"required": ["description"]} 136 | ] 137 | }, 138 | "media": { 139 | "description": "Media assets with IPFS hash", 140 | "type": "array", 141 | "items": { 142 | "type": "object", 143 | "properties": { 144 | "order": { 145 | "description": "Unique ID to identify individual media", 146 | "type": "integer" 147 | }, 148 | "url": { 149 | "description": "IPFS hash for the individual media", 150 | "type": "string", 151 | "minLength": 1 152 | }, 153 | "file_type": { 154 | "description": "File type [image, video]", 155 | "type": "string", 156 | "enum": [ "image", "video" ] 157 | }, 158 | "alt_text": { 159 | "description": "Alternate to display if media doesn't load", 160 | "type": "string", 161 | "enum": [ "hover_on_the_image_text", "hover_on_the_video_url", ""] 162 | }, 163 | "asset_type": { 164 | "description": "Asset type", 165 | "type": "string" 166 | } 167 | }, 168 | "additionalProperties": false, 169 | "required": [ "order", "url", "file_type", "alt_text" ] 170 | } 171 | }, 172 | "contributors": { 173 | "description": "Name and Email of contributor(s)", 174 | "type": "array", 175 | "items": { 176 | "type": "object", 177 | "properties": { 178 | "name": { 179 | "description": "Name of contributor", 180 | "type": "string", 181 | "minLength": 1 182 | }, 183 | "email_id": { 184 | "description": "Email of contributor", 185 | "type": "string" 186 | } 187 | }, 188 | "additionalProperties": false, 189 | "required": ["name", "email_id"] 190 | }, 191 | "minItems": 1 192 | }, 193 | "tags": { 194 | "description": "Service tags", 195 | "type": "array", 196 | "items": { 197 | "type": "string", 198 | "minLength": 1 199 | } 200 | }, 201 | "assets": { 202 | "description": "Service assets", 203 | "type": "object", 204 | "additionalProperties": true 205 | }, 206 | "service_api_source": { 207 | "description": "Source of service API", 208 | "type": "string", 209 | "minLength": 1 210 | } 211 | }, 212 | "additionalProperties": false, 213 | "required": [ 214 | "version", "display_name", "encoding", "service_type", 215 | "model_ipfs_hash", "mpe_address", "groups", "contributors" 216 | ], 217 | "if": {"properties": {"model_ipfs_hash": {"const": ""}}}, 218 | "then": {"required": ["service_api_source"]} 219 | } -------------------------------------------------------------------------------- /snet/cli/metadata/organization.py: -------------------------------------------------------------------------------- 1 | import base64 2 | from enum import Enum 3 | from json import JSONEncoder 4 | import json 5 | 6 | from snet.cli.utils.utils import is_valid_url 7 | 8 | 9 | class DefaultEncoder(JSONEncoder): 10 | def default(self, o): 11 | return o.__dict__ 12 | 13 | 14 | class AssetType(Enum): 15 | HERO_IMAGE = "hero_image" 16 | 17 | 18 | class PaymentStorageClient(object): 19 | 20 | def __init__(self, connection_timeout=None, request_timeout="", endpoints=None): 21 | if endpoints is None: 22 | endpoints = [] 23 | self.connection_timeout = connection_timeout 24 | self.request_timeout = request_timeout 25 | self.endpoints = endpoints 26 | 27 | def add_payment_storage_client_details(self, connection_time_out, request_timeout, endpoints): 28 | self.connection_timeout = connection_time_out 29 | self.request_timeout = request_timeout 30 | self.endpoints = endpoints 31 | 32 | @classmethod 33 | def from_json(cls, json_data: dict, check_url=True): 34 | if check_url: 35 | endpoints = json_data["endpoints"] 36 | if endpoints: 37 | for endpoint in endpoints: 38 | if not is_valid_url(endpoint): 39 | raise Exception("Invalid endpoint passed in json file") 40 | return cls(**json_data) 41 | 42 | class Payment(object): 43 | 44 | def __init__(self, payment_address="", payment_expiration_threshold="", payment_channel_storage_type="", 45 | payment_channel_storage_client=PaymentStorageClient()): 46 | self.payment_address = payment_address 47 | self.payment_expiration_threshold = payment_expiration_threshold 48 | self.payment_channel_storage_type = payment_channel_storage_type 49 | self.payment_channel_storage_client = payment_channel_storage_client 50 | 51 | @classmethod 52 | def from_json(cls, json_data: dict, check_url=True): 53 | payment_channel_storage_client = PaymentStorageClient.from_json( 54 | json_data['payment_channel_storage_client'], check_url) 55 | return cls(json_data['payment_address'], json_data['payment_expiration_threshold'], 56 | json_data['payment_channel_storage_type'], payment_channel_storage_client) 57 | 58 | def update_connection_timeout(self, connection_timeout): 59 | self.payment_channel_storage_client.connection_timeout = connection_timeout 60 | 61 | def update_request_timeout(self, request_timeout): 62 | self.payment_channel_storage_client.request_timeout = request_timeout 63 | 64 | def update_endpoints(self, endpoints): 65 | self.payment_channel_storage_client.endpoints = endpoints 66 | 67 | 68 | class Group(object): 69 | 70 | def __init__(self, group_name="", group_id="", payment=Payment()): 71 | self.group_name = group_name 72 | self.group_id = group_id 73 | self.payment = payment 74 | 75 | @classmethod 76 | def from_json(cls, json_data: dict, check_url=True): 77 | payment = Payment() 78 | if 'payment' in json_data: 79 | payment = Payment.from_json(json_data['payment'], check_url) 80 | return cls(json_data['group_name'], json_data['group_id'], payment) 81 | 82 | def add_group_details(self, group_name, group_id, payment): 83 | self.group_name = group_name 84 | self.group_id = group_id 85 | self.payment = payment 86 | 87 | def update_payment_expiration_threshold(self, payment_expiration_threshold): 88 | self.payment.payment_expiration_threshold = payment_expiration_threshold 89 | 90 | def update_payment_channel_storage_type(self, payment_channel_storage_type): 91 | self.update_payment_channel_storage_type = payment_channel_storage_type 92 | 93 | def update_payment_address(self, payment_address): 94 | self.payment.payment_address = payment_address 95 | 96 | def update_connection_timeout(self, connection_timeout): 97 | self.payment.update_connection_timeout(connection_timeout) 98 | 99 | def update_request_timeout(self, request_timeout): 100 | self.payment.update_request_timeout(request_timeout) 101 | 102 | def update_endpoints(self, endpoints): 103 | self.payment.update_endpoints(endpoints) 104 | 105 | def get_group_id(self, group_name=None): 106 | return base64.b64decode(self.get_group_id_base64(group_name)) 107 | 108 | def get_payment_address(self): 109 | return self.payment.payment_address 110 | 111 | 112 | class OrganizationMetadata(object): 113 | """ 114 | { 115 | "org_name": "organization_name", 116 | "org_id": "org_id1", 117 | org_type: "organization"/"individual", 118 | "contacts": [ 119 | { 120 | "contact_type": "support", 121 | "email_id":"abcd@abcdef.com", 122 | "phone":"1234567890", 123 | }, 124 | { 125 | "contact_type": "dummy", 126 | "email_id":"dummy@abcdef.com", 127 | "phone":"1234567890", 128 | }, 129 | ], 130 | "description": "We do this and that ... Describe your organization here ", 131 | "assets": { 132 | "hero_image": "QmNW2jjz11enwbRrF1mJ2LdaQPeZVEtmKU8Uq7kpEkmXCc/hero_gene-annotation.png" 133 | }, 134 | "groups": [ 135 | { 136 | "group_name": "default_group2", 137 | "group_id": "99ybRIg2wAx55mqVsA6sB4S7WxPQHNKqa4BPu/bhj+U=", 138 | "payment": { 139 | "payment_address": "0x671276c61943A35D5F230d076bDFd91B0c47bF09", 140 | "payment_expiration_threshold": 40320, 141 | "payment_channel_storage_type": "etcd", 142 | "payment_channel_storage_client": { 143 | "connection_timeout": "5s", 144 | "request_timeout": "3s", 145 | "endpoints": [ 146 | "http://127.0.0.1:2379" 147 | ] 148 | } 149 | } 150 | }, 151 | { 152 | "group_name": "default_group2", 153 | "group_id": "99ybRIg2wAx55mqVsA6sB4S7WxPQHNKqa4BPu/bhj+U=", 154 | "payment": { 155 | "payment_address": "0x671276c61943A35D5F230d076bDFd91B0c47bF09", 156 | "payment_expiration_threshold": 40320, 157 | "payment_channel_storage_type": "etcd", 158 | "payment_channel_storage_client": { 159 | "connection_timeout": "5s", 160 | "request_timeout": "3s", 161 | "endpoints": [ 162 | "http://127.0.0.1:2379" 163 | ] 164 | } 165 | } 166 | } 167 | ] 168 | } 169 | """ 170 | 171 | def __init__(self, org_name="", org_id="", org_type="",contacts=[], description={}, 172 | assets={}, groups=[]): 173 | self.org_name = org_name 174 | self.org_id = org_id 175 | self.org_type = org_type 176 | self.description = description 177 | self.assets = assets 178 | self.contacts = contacts 179 | self.groups = groups 180 | 181 | def add_group(self, group): 182 | self.groups.append(group) 183 | 184 | def get_json_pretty(self): 185 | return json.dumps(self, indent=4, cls=DefaultEncoder) 186 | 187 | def save_pretty(self, file_name): 188 | with open(file_name, 'w') as f: 189 | f.write(self.get_json_pretty()) 190 | 191 | @classmethod 192 | def from_json(cls, json_data: dict, check_url=True): 193 | groups = [] 194 | if 'groups' in json_data: 195 | groups = list(map(lambda j_d: Group.from_json(j_d, check_url), json_data["groups"])) 196 | if "contacts" not in json_data: 197 | json_data["contacts"] = [] 198 | if "description" not in json_data: 199 | json_data["description"] = {} 200 | if "assets" not in json_data: 201 | json_data["assets"] = {} 202 | if "org_type" not in json_data: 203 | json_data["org_type"] = "" 204 | return cls( 205 | org_name=json_data['org_name'], 206 | org_id=json_data['org_id'], 207 | org_type=json_data['org_type'], 208 | contacts=json_data['contacts'], 209 | description=json_data['description'], 210 | groups=groups, 211 | assets=json_data['assets'] 212 | ) 213 | 214 | @classmethod 215 | def from_file(cls, filepath): 216 | try: 217 | with open(filepath, 'r') as f: 218 | return OrganizationMetadata.from_json(json.load(f)) 219 | except Exception as e: 220 | print( 221 | "Organization metadata json file not found ,Please check --metadata-file path ") 222 | raise e 223 | 224 | def is_removing_existing_group_from_org(self, current_group_name, existing_registry_metadata_group_names): 225 | if len(existing_registry_metadata_group_names - current_group_name) == 0: 226 | pass 227 | else: 228 | removed_groups = existing_registry_metadata_group_names - current_group_name 229 | raise Exception("Cannot remove existing group from organization as it might be attached" 230 | " to services, groups you are removing are %s" % removed_groups) 231 | 232 | def check_remove_groups(self, existing_registry_metadata): 233 | unique_group_names = set([group.group_name for group in self.groups]) 234 | existing_registry_metadata_group_names = set() 235 | 236 | if existing_registry_metadata: 237 | for group in existing_registry_metadata.groups: 238 | existing_registry_metadata_group_names.add(group.group_name) 239 | 240 | self.is_removing_existing_group_from_org(unique_group_names, existing_registry_metadata_group_names) 241 | 242 | def get_payment_address_for_group(self, group_name): 243 | for group in self.groups: 244 | if group.group_name == group_name: 245 | return group.get_payment_address() 246 | 247 | def get_group_id_by_group_name(self, group_name): 248 | for group in self.groups: 249 | if group.group_name == group_name: 250 | return group.group_id 251 | 252 | def get_group_by_group_id(self, group_id): 253 | for group in self.groups: 254 | if group.group_id == group_id: 255 | return group 256 | 257 | def add_asset(self, asset_ipfs_hash, asset_type): 258 | if asset_type == AssetType.HERO_IMAGE.value: 259 | self.assets[asset_type] = asset_ipfs_hash 260 | else: 261 | raise Exception("Invalid asset type %s" % asset_type) 262 | 263 | def remove_all_assets(self): 264 | self.assets = {} 265 | 266 | def remove_assets(self, asset_type): 267 | if asset_type == AssetType.HERO_IMAGE.value: 268 | self.assets[asset_type] = "" 269 | else: 270 | raise Exception("Invalid asset type %s" % asset_type) 271 | 272 | def add_description(self, description): 273 | self.description["description"] = description 274 | 275 | def add_short_description(self, short_description): 276 | self.description["short_description"] = short_description 277 | 278 | def add_url(self, url): 279 | self.description["url"] = url 280 | 281 | def remove_description(self): 282 | self.description = {} 283 | 284 | def add_contact(self, contact_type, phone, email): 285 | if phone is None: 286 | phone = "" 287 | if email is None: 288 | email = "" 289 | 290 | contact = { 291 | "contact_type": contact_type, 292 | "email_id": email, 293 | "phone": phone 294 | } 295 | self.contacts.append(contact) 296 | 297 | def remove_contact_by_type(self, contact_type): 298 | self.contacts = [contact for contact in self.contacts if contact["contact_type"] != contact_type] 299 | 300 | def remove_all_contacts(self): 301 | self.contacts = [] 302 | -------------------------------------------------------------------------------- /snet/cli/utils/utils.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | import subprocess 4 | import functools 5 | import re 6 | import sys 7 | import importlib.resources 8 | from importlib.metadata import distribution 9 | from urllib.parse import urlparse 10 | from pathlib import Path, PurePath 11 | from lighthouseweb3 import Lighthouse 12 | import io 13 | import tarfile 14 | 15 | import web3 16 | import grpc 17 | from grpc_tools.protoc import main as protoc 18 | from trezorlib.cli.firmware import download 19 | 20 | from snet import cli 21 | from snet.cli.resources.root_certificate import certificate 22 | from snet.cli.utils.ipfs_utils import get_from_ipfs_and_checkhash 23 | 24 | RESOURCES_PATH = PurePath(os.path.dirname(cli.__file__)).joinpath("resources") 25 | 26 | 27 | class DefaultAttributeObject(object): 28 | def __init__(self, **kwargs): 29 | for k, v in kwargs.items(): 30 | if v is not None: 31 | setattr(self, k, v) 32 | 33 | def getstring(self, item): 34 | return getattr(self, item) 35 | 36 | def getint(self, item): 37 | if getattr(self, item) is None: 38 | return None 39 | return int(getattr(self, item)) 40 | 41 | def getfloat(self, item): 42 | if getattr(self, item) is None: 43 | return None 44 | return float(getattr(self, item)) 45 | 46 | def getboolean(self, item): 47 | if getattr(self, item) is None: 48 | return None 49 | i = self.getstring(item) 50 | if i in ["yes", "on", "true", "True", "1"]: 51 | return True 52 | return False 53 | 54 | def __getattr__(self, item): 55 | return self.__dict__.get(item, None) 56 | 57 | def __repr__(self): 58 | return self.__dict__.__repr__() 59 | 60 | def __str__(self): 61 | return self.__dict__.__str__() 62 | 63 | 64 | def get_web3(rpc_endpoint): 65 | if rpc_endpoint.startswith("ws:"): 66 | provider = web3.WebsocketProvider(rpc_endpoint) 67 | else: 68 | provider = web3.HTTPProvider(rpc_endpoint) 69 | 70 | return web3.Web3(provider) 71 | 72 | 73 | def serializable(o): 74 | if isinstance(o, bytes): 75 | return o.hex() 76 | else: 77 | return o.__dict__ 78 | 79 | 80 | def safe_address_converter(a): 81 | if not web3.Web3.is_checksum_address(a): 82 | raise Exception("%s is not is not a valid Ethereum checksum address" % a) 83 | return a 84 | 85 | 86 | def type_converter(t): 87 | if t.endswith("[]"): 88 | return lambda x: list(map(type_converter(t.replace("[]", "")), json.loads(x))) 89 | else: 90 | if "int" in t: 91 | return lambda x: web3.Web3.to_int(text=x) 92 | elif "bytes32" in t: 93 | return lambda x: web3.Web3.to_bytes(text=x).ljust(32, b"\0") if not x.startswith( 94 | "0x") else web3.Web3.to_bytes(hexstr=x).ljust(32, b"\0") 95 | elif "byte" in t: 96 | return lambda x: web3.Web3.to_bytes(text=x) if not x.startswith("0x") else web3.Web3.to_bytes(hexstr=x) 97 | elif "address" in t: 98 | return safe_address_converter 99 | else: 100 | return str 101 | 102 | 103 | def bytes32_to_str(b): 104 | return b.rstrip(b"\0").decode("utf-8") 105 | 106 | 107 | def _add_next_paths(path, entry_path, seen_paths, next_paths): 108 | with open(path) as f: 109 | for line in f: 110 | if line.strip().startswith("import"): 111 | import_statement = "".join(line.split('"')[1::2]) 112 | if not import_statement.startswith("google/protobuf"): 113 | import_statement_path = Path(path.parent.joinpath(import_statement)).resolve() 114 | if entry_path.parent in path.parents: 115 | if import_statement_path not in seen_paths: 116 | seen_paths.add(import_statement_path) 117 | next_paths.append(import_statement_path) 118 | else: 119 | raise ValueError("Path must not be a parent of entry path") 120 | 121 | 122 | def walk_imports(entry_path): 123 | seen_paths = set() 124 | next_paths = [] 125 | for file_path in os.listdir(entry_path): 126 | if file_path.endswith(".proto"): 127 | file_path = entry_path.joinpath(file_path) 128 | seen_paths.add(file_path) 129 | next_paths.append(file_path) 130 | while next_paths: 131 | path = next_paths.pop() 132 | if os.path.isfile(path): 133 | _add_next_paths(path, entry_path, seen_paths, next_paths) 134 | else: 135 | raise IOError("Import path must be a valid file: {}".format(path)) 136 | return seen_paths 137 | 138 | 139 | def read_temp_tar(f): 140 | f.flush() 141 | f.seek(0) 142 | return f 143 | 144 | 145 | def get_cli_version(): 146 | return distribution("snet.cli").version 147 | 148 | 149 | def compile_proto(entry_path, codegen_dir, proto_file=None, add_training=False): 150 | try: 151 | if not os.path.exists(codegen_dir): 152 | os.makedirs(codegen_dir) 153 | proto_include = importlib.resources.files('grpc_tools') / '_proto' 154 | 155 | compiler_args = [ 156 | "-I{}".format(entry_path), 157 | "-I{}".format(proto_include) 158 | ] 159 | 160 | if add_training: 161 | training_include = RESOURCES_PATH.joinpath("proto", "training") 162 | compiler_args.append("-I{}".format(training_include)) 163 | 164 | compiler_args.insert(0, "protoc") 165 | compiler_args.append("--python_out={}".format(codegen_dir)) 166 | compiler_args.append("--grpc_python_out={}".format(codegen_dir)) 167 | compiler = protoc 168 | 169 | if proto_file: 170 | compiler_args.append(str(proto_file)) 171 | else: 172 | compiler_args.extend([str(p) for p in entry_path.glob("**/*.proto")]) 173 | 174 | if add_training: 175 | compiler_args.append(str(training_include.joinpath("training.proto"))) 176 | 177 | if not compiler(compiler_args): 178 | return True 179 | else: 180 | return False 181 | 182 | except Exception as e: 183 | print(e) 184 | return False 185 | 186 | 187 | def abi_get_element_by_name(abi, name): 188 | """ Return element of abi (return None if fails to find) """ 189 | if abi and "abi" in abi: 190 | for a in abi["abi"]: 191 | if "name" in a and a["name"] == name: 192 | return a 193 | return None 194 | 195 | 196 | def abi_decode_struct_to_dict(abi, struct_list): 197 | return {el_abi["name"]: el for el_abi, el in zip(abi["outputs"], struct_list)} 198 | 199 | 200 | def int4bytes_big(b): 201 | return int.from_bytes(b, byteorder='big') 202 | 203 | 204 | def is_valid_endpoint(url): 205 | """ 206 | Just ensures the url has a scheme (http/https), and a net location (IP or domain name). 207 | Can make more advanced or do on-network tests if needed, but this is really just to catch obvious errors. 208 | >>> is_valid_endpoint("https://34.216.72.29:6206") 209 | True 210 | >>> is_valid_endpoint("blahblah") 211 | False 212 | >>> is_valid_endpoint("blah://34.216.72.29") 213 | False 214 | >>> is_valid_endpoint("http://34.216.72.29:%%%") 215 | False 216 | >>> is_valid_endpoint("http://192.168.0.2:9999") 217 | True 218 | """ 219 | try: 220 | result = urlparse(url) 221 | if result.port: 222 | _port = int(result.port) 223 | return ( 224 | all([result.scheme, result.netloc]) and 225 | result.scheme in ['http', 'https'] 226 | ) 227 | except ValueError: 228 | return False 229 | 230 | 231 | def remove_http_https_prefix(endpoint): 232 | """remove http:// or https:// prefix if presented in endpoint""" 233 | endpoint = endpoint.replace("https://", "") 234 | endpoint = endpoint.replace("http://", "") 235 | return endpoint 236 | 237 | 238 | def open_grpc_channel(endpoint): 239 | """ 240 | open grpc channel: 241 | - for http:// we open insecure_channel 242 | - for https:// we open secure_channel (with default credentials) 243 | - without prefix we open insecure_channel 244 | """ 245 | _GB = 1024 ** 3 246 | options = [('grpc.max_send_message_length', _GB), 247 | ('grpc.max_receive_message_length', _GB)] 248 | if endpoint.startswith("https://"): 249 | return grpc.secure_channel(remove_http_https_prefix(endpoint), grpc.ssl_channel_credentials(root_certificates=certificate)) 250 | return grpc.insecure_channel(remove_http_https_prefix(endpoint)) 251 | 252 | 253 | def rgetattr(obj, attr): 254 | """ 255 | >>> from types import SimpleNamespace 256 | >>> args = SimpleNamespace(a=1, b=SimpleNamespace(c=2, d='e')) 257 | >>> rgetattr(args, "a") 258 | 1 259 | >>> rgetattr(args, "b.c") 260 | 2 261 | """ 262 | return functools.reduce(getattr, [obj] + attr.split('.')) 263 | 264 | 265 | def normalize_private_key(private_key): 266 | if private_key.startswith("0x"): 267 | private_key = bytes(bytearray.fromhex(private_key[2:])) 268 | else: 269 | private_key = bytes(bytearray.fromhex(private_key)) 270 | return private_key 271 | 272 | 273 | def get_address_from_private(private_key): 274 | return web3.Account.from_key(private_key).address 275 | 276 | 277 | class add_to_path(): 278 | def __init__(self, path): 279 | self.path = path 280 | 281 | def __enter__(self): 282 | sys.path.insert(0, self.path) 283 | 284 | def __exit__(self, exc_type, exc_value, traceback): 285 | try: 286 | sys.path.remove(self.path) 287 | except ValueError: 288 | pass 289 | 290 | 291 | def find_file_by_keyword(directory, keyword): 292 | for root, dirs, files in os.walk(directory): 293 | for file in files: 294 | if keyword in file: 295 | return file 296 | 297 | 298 | def is_valid_url(url): 299 | regex = re.compile( 300 | r'^(?:http|ftp)s?://' 301 | r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' 302 | r'localhost|' 303 | r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|' 304 | r'\[?[A-F0-9]*:[A-F0-9:]+\]?)' 305 | r'(?::\d+)?' 306 | r'(?:/?|[/?]\S+)$', re.IGNORECASE) 307 | return re.match(regex, url) is not None 308 | 309 | 310 | def bytesuri_to_hash(s, to_decode=True): 311 | if to_decode: 312 | s = s.rstrip(b"\0").decode('ascii') 313 | if s.startswith("ipfs://"): 314 | return "ipfs", s[7:] 315 | elif s.startswith("filecoin://"): 316 | return "filecoin", s[11:] 317 | else: 318 | raise Exception("We support only ipfs and filecoin uri in Registry") 319 | 320 | 321 | def get_file_from_filecoin(cid): 322 | lighthouse_client = Lighthouse(" ") 323 | downloaded_file, _ = lighthouse_client.download(cid) 324 | return downloaded_file 325 | 326 | 327 | def download_and_safe_extract_proto(service_api_source, protodir, ipfs_client): 328 | """ 329 | Tar files might be dangerous (see https://bugs.python.org/issue21109, 330 | and https://docs.python.org/3/library/tarfile.html, TarFile.extractall warning) 331 | we extract only simple files 332 | """ 333 | try: 334 | storage_type, service_api_source = bytesuri_to_hash(service_api_source, to_decode=False) 335 | except Exception: 336 | storage_type = "ipfs" 337 | 338 | if storage_type == "ipfs": 339 | spec_tar = get_from_ipfs_and_checkhash(ipfs_client, service_api_source) 340 | else: 341 | spec_tar = get_file_from_filecoin(service_api_source) 342 | 343 | with tarfile.open(fileobj=io.BytesIO(spec_tar)) as f: 344 | for m in f.getmembers(): 345 | if os.path.dirname(m.name) != "": 346 | raise Exception( 347 | "tarball has directories. We do not support it.") 348 | if not m.isfile(): 349 | raise Exception( 350 | "tarball contains %s which is not a files" % m.name) 351 | fullname = os.path.join(protodir, m.name) 352 | if os.path.exists(fullname): 353 | os.remove(fullname) 354 | print("%s removed." % fullname) 355 | # now it is safe to call extractall 356 | f.extractall(protodir) 357 | 358 | 359 | def check_training_in_proto(protodir) -> bool: 360 | files = os.listdir(protodir) 361 | for file in files: 362 | if ".proto" not in file: 363 | continue 364 | with open(protodir.joinpath(file), "r") as f: 365 | proto_text = f.read() 366 | if 'import "training.proto";' in proto_text: 367 | return True 368 | return False -------------------------------------------------------------------------------- /snet/cli/test/functional_tests/script1_twogroups.sh: -------------------------------------------------------------------------------- 1 | snet session 2 | 3 | # service side 4 | 5 | #should fail (not existed directory) 6 | snet service metadata-init ./bad_dir/ ExampleService --encoding json --service-type jsonrpc --group-name group1 && exit 1 || echo "fail as expected" 7 | 8 | #should fail (directory doesn't contain any *.proto files) 9 | snet service metadata-init ./ ExampleService --encoding json --service-type jsonrpc --group-name group1 && exit 1 || echo "fail as expected" 10 | 11 | # happy flow 12 | snet --print-traceback service metadata-init ./service_spec1/ ExampleService --encoding json --service-type jsonrpc --group-name group1 13 | jq .model_ipfs_hash=1 service_metadata.json >tmp.txt 14 | mv -f tmp.txt service_metadata.json 15 | snet service metadata-set-model ./service_spec1/ 16 | snet service metadata-add-description --json '{"description_string":"string1","description_int":1,"description_dict":{"a":1,"b":"s"}}' 17 | snet service metadata-add-description --json '{"description_string":"string1","description_int":1,"description_dict":{"a":1,"b":"s"}}' --description "description" --url "http://127.0.0.1" 18 | cat service_metadata.json | jq '.service_description.url' | grep "http://127.0.0.1" 19 | snet service metadata-add-description --url "http://127.0.0.2" 20 | cat service_metadata.json | jq '.service_description.url' | grep "http://127.0.0.2" 21 | snet service metadata-add-description --json '{"description":"s"}' --description "description" && exit 1 || echo "fail as expected" 22 | snet service metadata-add-description --json '{"url":"http://127.0.0.1"}' --url "http://127.0.0.2" && exit 1 || echo "fail as expected" 23 | 24 | #seconf argument is group_id should be removed 25 | snet service metadata-add-group group2 26 | snet service metadata-add-endpoints group1 8.8.8.8:2020 9.8.9.8:8080 27 | snet service metadata-add-endpoints group2 8.8.8.8:22 1.2.3.4:8080 28 | grep "8.8.8.8:2020" service_metadata.json 29 | snet service metadata-remove-all-endpoints group2 30 | grep "8.8.8.8:22" service_metadata.json && exit 1 || echo "fail as expected" 31 | snet service metadata-remove-all-endpoints group1 32 | snet service metadata-add-endpoints group1 8.8.8.8:2020 9.8.9.8:8080 33 | snet service metadata-add-endpoints group2 8.8.8.8:22 1.2.3.4:8080 34 | snet --print-traceback service metadata-update-endpoints group2 8.8.8.8:23456 1.2.3.4:22 35 | grep "8.8.8.8:23456" service_metadata.json 36 | grep "8.8.8.8:2020" service_metadata.json 37 | grep "9.8.9.8:8080" service_metadata.json 38 | grep "8.8.8.8:22" service_metadata.json && exit 1 || echo "fail as expected" 39 | grep "1.2.3.4:8080" service_metadata.json && exit 1 || echo "fail as expected" 40 | 41 | snet service metadata-set-fixed-price group1 0.0001 42 | 43 | # test --endpoints and --fixed-price options in 'snet service metadata-init' 44 | snet --print-traceback service metadata-init ./service_spec1/ ExampleService --encoding json --service-type jsonrpc --group-name group1 --fixed-price 0 --endpoints 8.8.8.8:2020 9.8.9.8:8080 --metadata-file service_metadata2.json 45 | grep fixed_price service_metadata2.json 46 | snet service metadata-init ./service_spec1/ ExampleService --encoding json --service-type jsonrpc --group-name group1 --fixed-price 0.0001 --endpoints 8.8.8.8:2020 9.8.9.8:8080 --metadata-file service_metadata2.json 47 | grep fixed_price service_metadata2.json 48 | grep 9.8.9.8:8080 service_metadata2.json 49 | 50 | IPFS_HASH=$(snet service publish-in-ipfs) 51 | echo $IPFS_HASH 52 | ipfs cat $IPFS_HASH >service_metadata2.json 53 | 54 | # compare service_metadata.json and service_metadata2.json 55 | cmp <(jq -S . service_metadata.json) <(jq -S . service_metadata2.json) 56 | snet organization metadata-init org1 testo individual 57 | grep org1 organization_metadata.json 58 | snet organization create testo && exit 1 || echo "fail as expected" 59 | # 60 | snet --print-traceback organization add-group group1 0x42A605c07EdE0E1f648aB054775D6D4E38496144 5.5.6.7:8089 61 | snet --print-traceback organization add-group group2 0x42A605c07EdE0E1f648aB054775D6D4E38496144 1.2.1.1:8089 62 | grep 5.5.6.7 organization_metadata.json 63 | grep 0x42A605c07EdE0E1f648aB054775D6D4E38496144 organization_metadata.json 64 | grep 5.5.6.7:8089 organization_metadata.json 65 | snet --print-traceback organization create testo -y 66 | snet organization print-metadata org1 testo >organization_metadata_print.json 67 | 68 | snet service metadata-add-tags tag1 tag2 tag3 69 | grep "tag1" service_metadata.json 70 | grep "tag2" service_metadata.json 71 | grep "tag3" service_metadata.json 72 | grep "tag4" service_metadata.json && exit 1 || echo "fail as expected" 73 | 74 | snet service metadata-remove-tags tag2 tag1 75 | grep "tag2" service_metadata.json && exit 1 || echo "fail as expected" 76 | grep "tag1" service_metadata.json && exit 1 || echo "fail as expected" 77 | grep "tag3" service_metadata.json 78 | 79 | snet service publish testo tests -y -q 80 | snet service update-add-tags testo tests tag1 tag2 tag3 -y -q 81 | snet service update-remove-tags testo tests tag2 tag1 -y -q 82 | snet service print-tags testo tests 83 | 84 | # it should have only tag3 now 85 | cmp <(echo "tag3") <(snet service print-tags testo tests) 86 | 87 | snet service print-metadata testo tests >service_metadata3.json 88 | 89 | # compare service_metadata.json and service_metadata3.json 90 | cmp <(jq -S . service_metadata.json) <(jq -S . service_metadata3.json) 91 | 92 | # test get_api_registry and 93 | snet service get-api-registry testo tests _d1 94 | snet service get-api-metadata --metadata-file service_metadata3.json _d2 95 | 96 | # as usual, by default it is metatada_file=service_metadata.json 97 | snet service get-api-metadata _d3 98 | 99 | cmp ./service_spec1/ExampleService.proto _d1/ExampleService.proto 100 | cmp ./service_spec1/ExampleService.proto _d2/ExampleService.proto 101 | cmp ./service_spec1/ExampleService.proto _d3/ExampleService.proto 102 | 103 | rm -r _d1 _d2 _d3 104 | 105 | # client side 106 | snet account balance 107 | snet account deposit 123456 -y -q 108 | snet account transfer 0x0067b427E299Eb2A4CBafc0B04C723F77c6d8a18 42 -y -q 109 | snet account withdraw 1 -y -q 110 | 111 | #open channel usig org and group 112 | snet --print-traceback channel open-init-metadata testo group1 42 1 -y -q 113 | snet channel print-initialized 114 | snet channel claim-timeout 0 -y -q 115 | snet channel print-initialized 116 | # we do not send transaction second time 117 | snet channel claim-timeout 0 -y -q && exit 1 || echo "fail as expected" 118 | 119 | snet channel extend-add 0 --expiration 10000 --amount 42 -y -q 120 | snet channel print-initialized 121 | snet channel extend-add 0 --amount 42 -y -q 122 | snet channel print-initialized 123 | snet channel extend-add 0 --expiration +10000blocks -y -q 124 | snet channel extend-add 0 --expiration +10000days -y -q && exit 1 || echo "fail as expected" 125 | snet channel extend-add 0 --expiration +10000days --force -y -q 126 | snet channel extend-add 0 --expiration 57600000 --force -y -q && exit 1 || echo "fail as expected" 127 | 128 | EXPIRATION1=$(($(snet channel block-number) + 57600000)) 129 | snet channel extend-add 0 --expiration $EXPIRATION1 --force --amount 0 -y -q 130 | 131 | snet channel open-init testo group1 9712.1234 +14days -y -q 132 | 133 | # test print_initialized_channels and print_all_channels. We should have channels openned for specific identity 134 | snet channel print-initialized 135 | snet --print-traceback channel print-initialized | grep 84 136 | snet channel print-all-filter-sender | grep 0x42A605c07EdE0E1f648aB054775D6D4E38496144 137 | 138 | # we have two initilized channels one for group1 and anther for group1 (recipient=0x42A605c07EdE0E1f648aB054775D6D4E38496144) 139 | 140 | snet --print-traceback service metadata-init ./service_spec1/ ExampleService --group-name group2 --fixed-price 0.0001 --endpoints 8.8.8.8:2020 --metadata-file service_metadata2.json 141 | grep "8.8.8.8:2020" service_metadata2.json 142 | snet service metadata-update-endpoints group2 8.8.8.8:2025 --metadata-file service_metadata2.json 143 | grep "8.8.8.8:2025" service_metadata2.json 144 | grep "8.8.8.8:2020" service_metadata2.json && exit 1 || echo "fail as expected" 145 | 146 | snet service publish testo tests2 -y -q --metadata-file service_metadata2.json 147 | 148 | snet channel open-init testo group2 7234.345 1 -y -q --signer 0x3b2b3C2e2E7C93db335E69D827F3CC4bC2A2A2cB 149 | 150 | snet --print-traceback channel print-initialized-filter-org testo group2 151 | snet channel print-initialized-filter-org testo group2 | grep 7234.345 152 | snet channel print-initialized-filter-org testo group2 | grep 9712.1234 && exit 1 || echo "fail as expected" 153 | 154 | snet channel print-initialized 155 | snet channel print-initialized | grep 84 156 | snet channel print-initialized | grep 7234.345 157 | 158 | snet channel print-initialized --only-id 159 | snet channel print-initialized --only-id | grep 7234.345 && exit 1 || echo "fail as expected" 160 | 161 | snet channel print-initialized --filter-signer | grep 7234.345 && exit 1 || echo "fail as expected" 162 | snet channel print-initialized --filter-signer --wallet-index 1 | grep 7234.345 163 | 164 | snet channel print-initialized-filter-org testo group2 165 | snet channel print-initialized-filter-org testo group2 | grep 7234.345 166 | 167 | rm -rf ~/.snet/mpe_client/ 168 | 169 | # snet shoundn't try to open new channels. He simply should reinitilize old ones 170 | snet channel open-init testo group1 0 0 -y -q 171 | snet channel open-init testo group2 0 0 -y -q 172 | snet channel open-init testo group2 0 0 --signer 0x3b2b3C2e2E7C93db335E69D827F3CC4bC2A2A2cB -y -q 173 | snet channel print-initialized | grep 7234.345 174 | snet channel print-initialized | grep 84 175 | snet channel open-init-metadata testo group2 0 0 176 | 177 | rm -rf ~/.snet/mpe_client/ 178 | # this should open new channel instead of using old one 179 | snet channel open-init testo group2 111222 1 --open-new-anyway -yq 180 | snet channel print-initialized | grep 9712.1234 && exit 1 || echo "fail as expected" 181 | snet channel print-initialized-filter-org testo group2 | grep 111222 182 | 183 | rm -rf ~/.snet/mpe_client/ 184 | 185 | snet --print-traceback channel print-all-filter-group testo group2 186 | snet channel print-all-filter-sender 187 | snet channel print-all-filter-recipient 188 | 189 | #Uncomment this when all testing is done 190 | #snet channel print-all-filter-sender | grep 0x42A605c07EdE0E1f648aB054775D6D4E38496144 191 | # 192 | #snet channel print-all-filter-recipient | grep 0x52653A9091b5d5021bed06c5118D24b23620c529 && exit 1 || echo "fail as expected" 193 | #snet channel print-all-filter-recipient --wallet-index 9 |grep 0x52653A9091b5d5021bed06c5118D24b23620c529 194 | #snet channel print-all-filter-recipient --recipient 0x52653A9091b5d5021bed06c5118D24b23620c529 |grep 0x52653A9091b5d5021bed06c5118D24b23620c529 195 | # 196 | #snet channel print-all-filter-group testo group2 | grep 0x52653A9091b5d5021bed06c5118D24b23620c529 197 | #snet channel print-all-filter-group testo group2 | grep 0x42A605c07EdE0E1f648aB054775D6D4E38496144 && exit 1 || echo "fail as expected" 198 | # 199 | #snet channel print-all-filter-group testo group2 |grep 0x0067b427E299Eb2A4CBafc0B04C723F77c6d8a18 200 | # 201 | #snet channel print-all-filter-group-sender testo group2 | grep 0x52653A9091b5d5021bed06c5118D24b23620c529 202 | #snet channel print-all-filter-group-sender testo group2 | grep 0x42A605c07EdE0E1f648aB054775D6D4E38496144 && exit 1 || echo "fail as expected" 203 | 204 | # should fail because of wrong groupId 205 | snet channel init-metadata testo metadata-tests 0 --metadata-file service_metadata2.json && exit 1 || echo "fail as expected" 206 | snet channel init testo wrong_group_name 1 && exit 1 || echo "fail as expected" 207 | 208 | snet --print-traceback channel init-metadata testo group1 1 209 | snet --print-traceback channel init testo group2 1 210 | snet channel print-initialized 211 | snet channel print-all-filter-sender 212 | snet service delete testo tests -y -q 213 | snet organization list-services testo 214 | 215 | # open channel with sender=signer=0x32267d505B1901236508DcDa64C1D0d5B9DF639a 216 | 217 | snet account transfer 0x32267d505B1901236508DcDa64C1D0d5B9DF639a 1 -y -q 218 | snet channel open-init testo group2 1 314156700003452 --force -y -q --wallet-index 3 219 | snet channel print-all-filter-sender --sender 0x32267d505B1901236508DcDa64C1D0d5B9DF639a | grep 314156700003452 220 | snet channel print-all-filter-sender | grep 314156700003452 && exit 1 || echo "fail as expected" 221 | 222 | snet channel print-all-filter-group-sender testo group2 --sender 0x32267d505B1901236508DcDa64C1D0d5B9DF639a | grep 314156700003452 223 | snet organization list-services testo 224 | # test migration to different network 225 | 226 | # get service metadata from registry and set mpe_address to wrong value 227 | snet service print-metadata testo tests2 | jq '.mpe_address = "0x52653A9091b5d5021bed06c5118D24b23620c529"' >service_metadata.json 228 | 229 | # this should fail because of wrong mpe_address 230 | snet service publish-in-ipfs && exit 1 || echo "fail as expected" 231 | 232 | snet service publish-in-ipfs --multipartyescrow-at 0x52653A9091b5d5021bed06c5118D24b23620c529 233 | snet service publish-in-ipfs && exit 1 || echo "fail as expected" 234 | snet service publish-in-ipfs --update-mpe-address 235 | snet --print-traceback service publish-in-ipfs 236 | 237 | snet --print-traceback service print-metadata testo tests2 | jq '.mpe_address = "0x52653A9091b5d5021bed06c5118D24b23620c529"' >service_metadata.json 238 | 239 | # this should fail because of wrong mpe_address 240 | snet service publish testo tests4 && exit 1 || echo "fail as expected" 241 | 242 | snet --print-traceback service publish testo tests4 --multipartyescrow-at 0x52653A9091b5d5021bed06c5118D24b23620c529 -yq 243 | snet service publish testo tests5 -yq && exit 1 || echo "fail as expected" 244 | snet service publish testo tests6 --update-mpe-address -yq 245 | snet service publish testo tests7 -yq 246 | 247 | # test snet service update-metadata 248 | snet service metadata-add-group group1 249 | #group already added 250 | snet service metadata-add-group group2 && exit 1 || echo "fail as expected" 251 | snet service metadata-add-endpoints group1 8.8.8.8:22 1.2.3.4:8080 252 | snet service update-metadata testo tests7 -y 253 | 254 | 255 | #testcase for updating fixed price 256 | snet --print-traceback service metadata-init ./service_spec1/ ExampleService --encoding json --service-type jsonrpc --group-name group1 --fixed-price 0.01212 --endpoints 8.8.8.8:2020 9.8.9.8:8080 --metadata-file service_metadata_fixed_price.json 257 | grep "1212000" service_metadata_fixed_price.json 258 | snet service metadata-set-fixed-price group1 0.2323 --metadata-file service_metadata_fixed_price.json 259 | grep "23230000" service_metadata_fixed_price.json 260 | --------------------------------------------------------------------------------