├── tests ├── __init__.py ├── core.py └── ext-tangleid.py ├── extensions ├── __init__.py ├── tangleid │ ├── __init__.py │ └── main.py └── README.md ├── pytest.ini ├── requirements.txt ├── .gitignore ├── .gitmodules ├── .travis.yml ├── config.py ├── Pipfile ├── utils.py ├── Dockerfile ├── Makefile ├── PoW.py ├── LICENSE ├── server.py ├── README.md └── swarm_node.py /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /extensions/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /extensions/tangleid/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | python_files = *.py -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | PyOTA 2 | flask 3 | flask_cors 4 | pytest -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | log.txt 3 | *.pyc 4 | *.o 5 | *.o.d 6 | 7 | .pytest_cache/ -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "deps/dcurl"] 2 | path = deps/dcurl 3 | url = https://github.com/DLTcollab/dcurl 4 | branch = dev 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "3.6" 4 | install: 5 | - pip install -r requirements.txt 6 | 7 | script: 8 | - make check 9 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | FULLNODE = "http://node1.puyuma.org:14266" 2 | SEED = 'AMRWQP9BUMJALJHBXUCHOD9HFFD9LGTGEAWMJWWXSDVOF9PI9YGJAPBQLQUOMNYEQCZPGCTHGVNNAPGHA' 3 | -------------------------------------------------------------------------------- /extensions/README.md: -------------------------------------------------------------------------------- 1 | # How to write extensions for IOTA Swarm node 2 | 3 | It is straighforard to implement extensions for IOTA Swarm nodes, allowing 4 | operations being taken regardless of fullnodes. 5 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [dev-packages] 7 | 8 | [packages] 9 | flask = "*" 10 | flask-cors = "*" 11 | pyota = "*" 12 | pytest = "*" 13 | -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | import json 2 | from iota import Address, Hash 3 | 4 | class IotaJSONEncoder(json.JSONEncoder): 5 | def default(self, obj): 6 | if isinstance(obj, Address): 7 | return str(obj) 8 | if isinstance(obj, Hash): 9 | return str(obj) 10 | return json.JSONEncoder.default(self, obj) 11 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Dockerfile 2 | FROM python:3.6.7-alpine 3 | LABEL maintainer="Ender Su " 4 | 5 | # Install python3 dependencies 6 | RUN apk add --virtual build-tools libffi-dev openssl-dev build-base \ 7 | && pip3 install pyota \ 8 | && apk del build-tools 9 | 10 | # Copy source code into docker image 11 | ADD . /iota-swarm-node 12 | 13 | EXPOSE 8000 14 | WORKDIR /iota-swarm-node 15 | CMD [ "python3", "/iota-swarm-node/server.py" ] -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | DCURL_DIR := deps/dcurl 2 | DCURL_LIB := $(DCURL_DIR)/build/libdcurl.so 3 | DEPS += $(DCURL_LIB) 4 | 5 | all: $(DEPS) 6 | 7 | .PHONY: $(DCURL_LIB) 8 | $(DCURL_LIB): $(DCURL_DIR) 9 | git submodule update --init $^ 10 | git submodule update --remote $^ 11 | $(MAKE) -C $^ config 12 | @echo 13 | $(info Modify $^/build/local.mk for your environments.) 14 | $(MAKE) -C $^ all 15 | check: server.py $(DCURL_LIB) 16 | pytest tests 17 | clean: 18 | find . -name '*.pyc' | xargs $(RM) 19 | $(MAKE) -C $(DCURL_DIR) clean 20 | distclean: clean 21 | $(RM) -r $(DCURL_DIR) 22 | git checkout $(DCURL_DIR) 23 | -------------------------------------------------------------------------------- /PoW.py: -------------------------------------------------------------------------------- 1 | import ctypes 2 | import sys 3 | from iota import TryteString 4 | 5 | 6 | def PoW_load_library(DCURL_PATH): 7 | try: 8 | libdcurl = ctypes.cdll.LoadLibrary(DCURL_PATH) 9 | libdcurl.dcurl_entry.argtypes = [ctypes.c_char_p, ctypes.c_int] 10 | libdcurl.dcurl_entry.restype = ctypes.c_char_p 11 | return libdcurl 12 | except BaseException: 13 | return None 14 | 15 | 16 | def PoW_interface_init(lib): 17 | if (lib) is not None: 18 | lib.dcurl_init() 19 | 20 | # return nonce 21 | def PoW_interface_search(lib, tryte, mwm): 22 | if lib is not None: 23 | # Do the PoW 24 | ctryte = str(tryte).encode('ascii') 25 | ret = lib.dcurl_entry(ctryte, mwm) 26 | 27 | # Get the nonce 28 | ret_tryte = TryteString(ret[:2673]) 29 | return str(ret_tryte[-27:]) 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | iota-swarm-node is freely redistributable under the two-clause BSD License: 2 | 3 | Copyright (c) 2018 BiiLabs, Co. Ltd. 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import json 3 | from flask import Flask, request 4 | from flask_cors import CORS 5 | 6 | from swarm_node import send_transfer, get_tips, generate_address 7 | from extensions.tangleid import main as extension_tangleid 8 | from utils import IotaJSONEncoder 9 | 10 | PORT = 8000 11 | 12 | app = Flask(__name__) 13 | CORS(app) 14 | 15 | @app.route('/') 16 | def swarm_node_info(): 17 | response = { 18 | 'status': 'SUCCESS', 19 | 'data': 'Hello I am a swarm node.' 20 | } 21 | 22 | return json.dumps(response) 23 | 24 | 25 | @app.route('/', methods=['POST']) 26 | def execute_api(): 27 | request_data = request.get_data() 28 | print("Get request data ... %s" % (str(request_data))) 29 | 30 | request_command = json.loads(request_data) 31 | 32 | if request_command['command'] == "generate_address": 33 | address_result = generate_address() 34 | result = json.dumps(address_result, cls=IotaJSONEncoder) 35 | 36 | elif request_command['command'] == "get_tips": 37 | tips_result = get_tips(int(request_command['type'])) 38 | result = json.dumps(tips_result, cls=IotaJSONEncoder) 39 | 40 | elif request_command['command'] == "send_transfer": 41 | if 'debug' not in request_command: 42 | debug = 0 43 | else: 44 | debug = int(request_command['debug']) 45 | 46 | dict_tips = get_tips(int(request_command['tips_type'])) 47 | result = send_transfer( 48 | request_command['tag'], request_command['message'], request_command['address'], int( 49 | request_command['value']), dict_tips, debug) 50 | else: 51 | result = extension_tangleid.load(request_data) 52 | 53 | print("Result ... %s" % (str(result))) 54 | 55 | return result 56 | 57 | if __name__ == "__main__": 58 | app.run(port=PORT, threaded=True) 59 | -------------------------------------------------------------------------------- /tests/core.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | import tempfile 4 | 5 | import pytest 6 | 7 | from server import app 8 | 9 | @pytest.fixture 10 | def client(): 11 | app.config['TESTING'] = True 12 | client = app.test_client() 13 | 14 | yield client 15 | 16 | def test_generate_address(client): 17 | post_data = '{"command":"generate_address"}' 18 | rv = client.post('/', data=post_data) 19 | response = json.loads(rv.data) 20 | assert len(response['addresses']) > 0 21 | 22 | for address in response['addresses']: 23 | assert len(address) is 81 24 | 25 | def test_get_tips_from_iri(client): 26 | TIPS_TYPE_IRI_REQULAR_ALGORITHM = 0 27 | post_data = { 28 | "command": "get_tips", 29 | "type": TIPS_TYPE_IRI_REQULAR_ALGORITHM, 30 | } 31 | rv = client.post('/', data=json.dumps(post_data)) 32 | response = json.loads(rv.data) 33 | 34 | assert len(response['trunkTransaction']) is 81 35 | assert len(response['branchTransaction']) is 81 36 | 37 | def test_get_tips_with_null(client): 38 | TIPS_TYPE_TWO_NULL_IOTA_TNX = 1 39 | post_data = { 40 | "command": "get_tips", 41 | "type": TIPS_TYPE_TWO_NULL_IOTA_TNX, 42 | } 43 | rv = client.post('/', data=json.dumps(post_data)) 44 | response = json.loads(rv.data) 45 | 46 | assert len(response['trunkTransaction']) is 81 47 | assert len(response['branchTransaction']) is 81 48 | 49 | def test_send_transfer(client): 50 | tag = "SWARMTEST" 51 | message = "TEST MESSAGE" 52 | address = "BXEOYAONFPBGKEUQZDUZZZODHWJDWHEOYY9AENYF9VNLXZHXBOODCOTYXW9MGGINTEJPLK9AGOPTPODVX" 53 | value = 0 54 | TIPS_TYPE_TWO_NULL_IOTA_TNX = 1 55 | post_data = { 56 | 'command': 'send_transfer', 57 | 'tag': tag, 58 | 'message': message, 59 | 'address': address, 60 | 'value': value, 61 | 'tips_type': TIPS_TYPE_TWO_NULL_IOTA_TNX, 62 | 'debug': 1 63 | } 64 | rv = client.post('/', data=json.dumps(post_data)) 65 | assert len(rv.data) is 81 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IOTA Swarm Node 2 | 3 | ## Summary 4 | 5 | `iota-swarm-node` is a proof-of-concept implementation of IOTA Swarm node, that 6 | is a device with software/hardware implementing an algorithm aiming for 7 | allowing several swarm nodes behave as a full node. 8 | 9 | Most use cases for micropayments involve a single user or device interacting 10 | repeatedly with a few vendors. 11 | 12 | ## Prerequisites 13 | 14 | Install dependent packages: 15 | ```shell 16 | $ sudo apt-get install python-pip python-setuptools python-dev python3-dev \ 17 | build-essential libssl-dev libffi-dev 18 | ``` 19 | 20 | Install Python package dependencies: 21 | ```shell 22 | $ pip install -r requirements.txt 23 | ``` 24 | 25 | (Alternative) Install dependencies and activate virtualenv with Pipenv: 26 | ```shell 27 | $ pipenv install 28 | $ pipenv shell 29 | ``` 30 | 31 | ## Build from scratch 32 | 33 | Ensure gcc or clang available in build environment and then execute: 34 | ```shell 35 | $ make 36 | ``` 37 | 38 | (Optional) run test suite: 39 | ```shell 40 | $ make check 41 | ``` 42 | 43 | ## Launch the service 44 | 45 | * Launch swarm node as server: 46 | 47 | ```shell 48 | $ python server.py 49 | Listening on localhost:8000 50 | ``` 51 | 52 | * Generate an unused address: 53 | ```shell 54 | $ python tests/generate_address.py 55 | Generating an unused address ... 56 | {u'addresses': [Address('OMAEMGRMASNBLYVFCRG9UARBBCWDIC9RGCOFTVAVJZDWISOHVMFLSW9ZL9FIJIHVVRYQLIMYBWEYP9WSX')]} 57 | Duration: 73.5027749538 seconds 58 | ``` 59 | 60 | * Get tips from full node 61 | ```shell 62 | $ python tests/get_tips.py 63 | Getting tips ... 64 | {u'duration': 484, u'branchTransaction': TransactionHash('QCPNKOXJXFERNNLTZZG9LBWDJQRLFIWDYNYQBHZJANJGXAADKNFTPWBWVDGHROVVVQWBKP9ROKRMZ9999'), u'trunkTransaction': TransactionHash('GEPJNFUNQGPDSFECJZGEWYYWYMGVWDCOELBKZQWILEUGGVHPNWFRLHNQHYKHCHPQWSQAXGYG9AIBA9999')} 65 | Duration: 0.960033893585 seconds 66 | ``` 67 | 68 | * Send data (0 value transaction) 69 | ```shell 70 | $ python tests/send_transfer.py 71 | Send send transfer command ... 72 | WIAEHXJUVO9IDZXROJEDBQLFHVFLZCIQKPLLXCGWLNZFIUJZLBZACVLZPWAKUBYLDYRZKFIDKLSAHJHEY 73 | Duration: 1.91658091545 seconds 74 | ``` 75 | 76 | ## Build the docker image 77 | 78 | Before building the docker image, you need to build the iota-swarm-node. 79 | 80 | ```shell 81 | $ make 82 | ``` 83 | 84 | Build the docker image and tag with `iota-swarm-node`. 85 | 86 | ```shell 87 | $ docker build -t iota-swarm-node . 88 | ``` 89 | 90 | ## Publish docker image to Docker Hub 91 | 92 | 1. Login to the Docker Hub. 93 | 94 | ```shell 95 | $ docker login 96 | ``` 97 | 98 | 2. Tag docker image. 99 | 100 | ```shell 101 | $ docker tag iota-swarm-node DOCKER_ID_USER/iota-swarm-node 102 | ``` 103 | 104 | 3. Push image to Docker Hub. 105 | 106 | ```shell 107 | $ docker push DOCKER_ID_USER/iota-swarm-node 108 | ``` 109 | 110 | ## Licensing 111 | 112 | `iota-swarm-node` is freely redistributable under the two-clause BSD License. 113 | Use of this source code is governed by a BSD-style license that can be found 114 | in the `LICENSE` file. 115 | -------------------------------------------------------------------------------- /tests/ext-tangleid.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | import tempfile 4 | 5 | import pytest 6 | 7 | from server import app 8 | 9 | @pytest.fixture 10 | def client(): 11 | app.config['TESTING'] = True 12 | client = app.test_client() 13 | 14 | yield client 15 | 16 | def test_welcome_message(client): 17 | rv = client.get('/') 18 | assert b'Hello I am a swarm node.' in rv.data 19 | 20 | def test_get_all_claims(client): 21 | post_data = '{"command":"get_all_claims","uuid": "V9TCFLAOGGTAQATTJBLABAG9WY"}' 22 | rv = client.post('/', data=post_data) 23 | assert len(json.loads(rv.data)) > 0 24 | 25 | def test_get_all_notifies(client): 26 | post_data = '{"command":"get_all_notifies","uuid": "SD9BCRDGJYWDHPTDNOPRULFWWG"}' 27 | rv = client.post('/', data=post_data) 28 | assert len(json.loads(rv.data)) > 0 29 | 30 | def test_get_all_revoke_claims(client): 31 | post_data = '{"command":"get_all_revoke_claims","uuid": "SD9BCRDGJYWDHPTDNOPRULFWWG"}' 32 | rv = client.post('/', data=post_data) 33 | print('rv.data' + str(rv.data)) 34 | assert len(json.loads(rv.data)) > 0 35 | 36 | def test_get_claim_info(client): 37 | post_data = '{"command":"get_claim_info","hash_txn": "UG9WOSTUUEXPTZSIWNLHIQSOAJAWFVGQWA9MBFSEQAIDNZLIVALAELPBAUNTCHRVYWYLONEFHESYSJ999"}' 38 | rv = client.post('/', data=post_data) 39 | assert type(json.loads(rv.data)) is dict 40 | 41 | def test_login(client): 42 | post_data = '{"command":"login","uuid": "ED9BCRDGJYWDHPTDNOPRULFWWG"}' 43 | rv = client.post('/', data=post_data) 44 | assert len(json.loads(rv.data)) > 0 45 | 46 | def test_new_claim(client): 47 | post_data = '{"extension":"tangleid", "command":"new_claim","uuid": "V9TCFLAOGGTAQATTJBLABAG9WY", "part_a":"V9TCFLAOGGTAQATTJBLABAG9WY","part_b":"V9TCFLAOGGTAQATTJBLABAGABB", "exp_date":"20191201", "claim_pic":"https://i.imgur.com/MxwIXXn.jpg", "msg":"TestingMessage"}' 48 | rv = client.post('/', data=post_data) 49 | assert len(rv.data) == 81 50 | 51 | def test_new_group(client): 52 | post_data = '{"command":"new_group","first_name":"Huang", "cosignerp":"SD9BCRDGJYWDHPTDNOPRULFWWG","cosigners":"SD9BCRDGJYWDHPTDNOPRULFWWG", "last_name":"JyunYu","profile_picture":"https://s3-us-west-1.amazonaws.com/niusnews-imgs/146716_5.jpg", "uuid":"ED9BCRDGJYWDHPTDNOPRULFWWG","pk":"SD9BCRDGJYWDHPTDNOPRULFWWG"}' 53 | rv = client.post('/', data=post_data) 54 | print('rv.data' + str(rv.data)) 55 | assert len(rv.data) == 81 56 | 57 | def test_new_user(client): 58 | post_data = '{"command":"new_user","first_name":"Huang","cosignerp":"SD9BCRDGJYWDHPTDNOPRULFWWG","cosigners":"SD9BCRDGJYWDHPTDNOPRULFWWG","last_name":"JyunYu","profile_picture":"https://s3-us-west-1.amazonaws.com/niusnews-imgs/146716_5.jpg","uuid":"ED9BCRDGJYWDHPTDNOPRULFWWG","pk":"SD9BCRDGJYWDHPTDNOPRULFWWG"}' 59 | rv = client.post('/', data=post_data) 60 | assert len(rv.data) == 81 61 | 62 | def test_revoke_claim(client): 63 | post_data = '{"command":"revoke_claim","uuid": "SD9BCRDGJYWDHPTDNOPRULFWWG","txnhash":"KQD9IKGNUM9ZVMHJHKVZLAAVDNDWJRNTZDYB9SXKMXPMBYNRGCOIMIVLTSRCJEXRAMWDNZODLBVR99999"}' 64 | rv = client.post('/', data=post_data) 65 | assert len(rv.data) == 81 66 | 67 | def test_send_notify(client): 68 | post_data = '{"command":"send_notify","uuid":"SD9BCRDGJYWDHPTDNOPRULFWWG","receiver": "SD9BCRDGJYWDHPTDNOPRULFWWG","message":"HAPPYBIRTHDAY"}' 69 | rv = client.post('/', data=post_data) 70 | assert len(rv.data) == 81 -------------------------------------------------------------------------------- /extensions/tangleid/main.py: -------------------------------------------------------------------------------- 1 | import time 2 | import json 3 | from swarm_node import get_tips, send_transfer, \ 4 | find_transactions_by_tag, get_txn_msg 5 | from utils import IotaJSONEncoder 6 | 7 | address = "BXEOYAONFPBGKEUQZDUZZZODHWJDWHEOYY9AENYF9VNLXZHXBOODCOTYXW9MGGINTEJPLK9AGOPTPODVX" 8 | 9 | 10 | def load(data): 11 | request_data = json.loads(data) 12 | 13 | if request_data['command'] == "new_claim": 14 | bundle_hash = new_claim(data) 15 | return bundle_hash 16 | elif request_data['command'] == "get_all_claims": 17 | list_claims = list_all_claims(data) 18 | return list_claims 19 | elif request_data['command'] == "get_claim_info": 20 | result = get_claim_info(data) 21 | return result 22 | elif request_data['command'] == "login": 23 | result = login(data) 24 | return result 25 | elif request_data['command'] == "new_user": 26 | result = new_user(data) 27 | return result 28 | elif request_data['command'] == "send_notify": 29 | result = send_notify(data) 30 | return result 31 | elif request_data['command'] == "get_all_notifies": 32 | result = get_all_notifies(data) 33 | return result 34 | elif request_data['command'] == "revoke_claim": 35 | result = revoke_claim(data) 36 | return result 37 | elif request_data['command'] == "get_all_revoke_claims": 38 | result = get_all_revoke_claims(data) 39 | return result 40 | elif request_data['command'] == "new_group": 41 | result = new_group(data) 42 | return result 43 | 44 | 45 | def new_claim(data): 46 | data = json.loads(data) 47 | 48 | # Get tips 49 | dict_tips = get_tips(0) 50 | 51 | # Set output transaction 52 | tag = data['uuid'] + "C" 53 | response = send_transfer(tag, json.dumps(data), address, 0, dict_tips, debug=0) 54 | 55 | return str(response) 56 | 57 | 58 | def list_all_claims(data): 59 | data = json.loads(data) 60 | 61 | uuid = data['uuid'] 62 | uuid = uuid + "C" 63 | 64 | list_claims = find_transactions_by_tag(uuid) 65 | 66 | if len(list_claims) == 0: 67 | return [] 68 | 69 | list_claims = list_claims['hashes'] 70 | 71 | list_output = [] 72 | for obj in list_claims: 73 | list_output.append(str(obj)) 74 | 75 | return str(json.dumps(list_output)) 76 | 77 | 78 | def get_claim_info(data): 79 | data = json.loads(data) 80 | 81 | txn_hash = data['hash_txn'] 82 | result = get_txn_msg(txn_hash) 83 | 84 | return result 85 | 86 | 87 | def login(data): 88 | data = json.loads(data) 89 | 90 | uuid = data['uuid'] + "I" 91 | 92 | list_result = find_transactions_by_tag(uuid) 93 | 94 | return json.dumps(list_result['hashes'], cls=IotaJSONEncoder) 95 | 96 | 97 | def new_user(data): 98 | data = json.loads(data) 99 | 100 | # Get tips 101 | dict_tips = get_tips(0) 102 | 103 | # Set output transaction 104 | tag = data['uuid'] + "I" 105 | response = send_transfer(tag, json.dumps(data), address, 0, dict_tips, debug=0) 106 | 107 | return str(response) 108 | 109 | 110 | def send_notify(data): 111 | data = json.loads(data) 112 | 113 | # Get tips 114 | dict_tips = get_tips(0) 115 | 116 | # Set output transaction 117 | tag = data['uuid'] + "M" 118 | response = send_transfer(tag, json.dumps(data), address, 0, dict_tips, debug=0) 119 | 120 | return str(response) 121 | 122 | 123 | def get_all_notifies(data): 124 | data = json.loads(data) 125 | 126 | uuid = data['uuid'] 127 | uuid = uuid + "M" 128 | 129 | list_claims = find_transactions_by_tag(uuid) 130 | 131 | if len(list_claims) == 0: 132 | return [] 133 | 134 | list_claims = list_claims['hashes'] 135 | 136 | list_output = [] 137 | for obj in list_claims: 138 | list_output.append(str(obj)) 139 | 140 | return json.dumps(list_output) 141 | 142 | 143 | def revoke_claim(data): 144 | data = json.loads(data) 145 | 146 | # Get tips 147 | dict_tips = get_tips(0) 148 | 149 | # Set output transaction 150 | tag = data['uuid'] + "R" 151 | response = send_transfer(tag, json.dumps(data), address, 0, dict_tips, debug=0) 152 | 153 | return str(response) 154 | 155 | 156 | def get_all_revoke_claims(data): 157 | data = json.loads(data) 158 | 159 | uuid = data['uuid'] 160 | uuid = uuid + "R" 161 | 162 | #list_result = api.find_transactions(tags = [uuid]) 163 | list_result = find_transactions_by_tag(uuid) 164 | 165 | return json.dumps(list_result['hashes'], cls=IotaJSONEncoder) 166 | 167 | 168 | def new_group(data): 169 | data = json.loads(data) 170 | 171 | # Get tips 172 | dict_tips = get_tips(0) 173 | 174 | # Set output transaction 175 | tag = data['uuid'] + "G" 176 | response = send_transfer(tag, json.dumps(data), address, 0, dict_tips, debug=0) 177 | 178 | return str(response) 179 | -------------------------------------------------------------------------------- /swarm_node.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import subprocess 3 | import json 4 | import time 5 | 6 | import iota 7 | from iota import Iota, Address, TryteString, Transaction 8 | from iota.crypto.signing import SignatureFragmentGenerator 9 | from iota.crypto.kerl.conv import convertToBytes, convertToTrits, \ 10 | trits_to_trytes, trytes_to_trits 11 | from iota.trits import trits_from_int 12 | 13 | from config import SEED, FULLNODE 14 | from PoW import * 15 | 16 | DCURL_PATH = "./deps/dcurl/build/libdcurl.so" 17 | TXN_SECURITY_LEVEL = 2 18 | DEPTH = 7 19 | MWM = 9 20 | 21 | api = Iota(FULLNODE, seed=SEED) 22 | 23 | 24 | def insert_to_trytes(index_start, index_end, str_insert, trytes): 25 | trytes = trytes[:index_start] + str_insert + trytes[index_end:] 26 | 27 | return trytes 28 | 29 | # Return an unused IOTA address 30 | 31 | 32 | def generate_address(): 33 | print("Generating an unused address ...") 34 | return api.get_new_addresses(count=None, index=None) 35 | 36 | # Get transaction tips 37 | # Parameter: 38 | # 0: Request getTransactionToApprove RestfulAPI to fullnode 39 | # 1: Return two null transaction 40 | # 2: Get tips list from fullnode 41 | 42 | 43 | def get_tips(tips_type): 44 | if tips_type == 0: 45 | return api.get_transactions_to_approve(DEPTH) 46 | if tips_type == 1: 47 | return { 48 | 'trunkTransaction': iota.Hash(''), 49 | 'branchTransaction': iota.Hash('')} 50 | if tips_type == 2: 51 | return api.get_tips() 52 | 53 | 54 | def send_transfer(tag, messages, address, values, dict_tips, debug=0): 55 | # Initialize PoW Library 56 | PoWlib = PoW_load_library(DCURL_PATH) 57 | PoW_interface_init(PoWlib) 58 | 59 | # Set output transaction 60 | print("Start to transfer ... ") 61 | time_start_send = time.time() 62 | 63 | propose_bundle = iota.ProposedBundle() 64 | 65 | print("Setting output transaction ...") 66 | txn_output = iota.ProposedTransaction( 67 | address=iota.Address(address), 68 | value=values, 69 | tag=iota.Tag(tag), 70 | message=TryteString.from_unicode(messages) 71 | ) 72 | 73 | propose_bundle.add_transaction(txn_output) 74 | 75 | # Get input address 76 | if int(values) > 0: 77 | print("DEBUG values = %s" % (str(values))) 78 | 79 | print("Checking input balance ...") 80 | 81 | dict_inputs = api.get_inputs() 82 | if int(dict_inputs['totalBalance']) < int(values): 83 | print("Balance not enough") 84 | return 0 85 | 86 | # Setting intput transaction 87 | if int(values) > 0: 88 | print("Setting input transaction ...") 89 | value_input = 0 90 | index_input = 0 91 | while (int(value_input) < int(values)): 92 | addy = iota.Address(dict_inputs['inputs'][index_input]) 93 | addy.balance = dict_inputs['inputs'][index_input].balance 94 | addy.key_index = dict_inputs['inputs'][index_input].key_index 95 | addy.security_level = TXN_SECURITY_LEVEL 96 | 97 | propose_bundle.add_inputs([addy]) 98 | value_input = value_input + int(dict_inputs['inputs'][0].balance) 99 | 100 | # Send unspent inputs to 101 | print("Setting unspent input to a new address ...") 102 | unspent = iota.Address(generate_address()['addresses'][0]) 103 | propose_bundle.send_unspent_inputs_to(unspent) 104 | 105 | # This will get the bundle hash 106 | print("Bundle finalize ...") 107 | 108 | time_start_bundle_finz = time.time() 109 | propose_bundle.finalize() 110 | time_end_bundle_finz = time.time() 111 | elapsed_bundle_finz = time_end_bundle_finz - time_start_bundle_finz 112 | 113 | # Signing 114 | # If the transaction need sign, it will then sign-up the transaction 115 | # to fill up signature fragements 116 | if int(values) > 0: 117 | print("Signing...") 118 | propose_bundle.sign_inputs(iota.crypto.signing.KeyGenerator(SEED)) 119 | 120 | trytes = propose_bundle.as_tryte_strings() 121 | 122 | # Get tips by getTransactionsToApprove 123 | trunk_hash = dict_tips['trunkTransaction'] 124 | branch_hash = dict_tips['branchTransaction'] 125 | 126 | # Do PoW (attach to tangle) 127 | elapsed_pow = 0 128 | time_start_pow = time.time() 129 | for tx_tryte in trytes: 130 | # Attachment timestamp insert 131 | timestamp = TryteString.from_trits( 132 | trits_from_int(int(time.time() * 1000), pad=27)) 133 | tx_tryte = insert_to_trytes(2619, 2628, str(timestamp), tx_tryte) 134 | # timestamp_lower_bound = MIN_VALUE 135 | # timestamp_upper_bound = MAX_VALUE 136 | tx_tryte = insert_to_trytes(2637, 2646, str("MMMMMMMMM"), tx_tryte) 137 | 138 | # Tips insert - trunk 139 | tx_tryte = insert_to_trytes(2430, 2511, str(trunk_hash), tx_tryte) 140 | # Tips insert - branch 141 | tx_tryte = insert_to_trytes(2511, 2592, str(branch_hash), tx_tryte) 142 | 143 | # Do PoW for this transaction 144 | print("Do POW for this transaction ...") 145 | 146 | nonce = PoW_interface_search(PoWlib, tx_tryte, MWM) 147 | tx_tryte = insert_to_trytes(2646, 2673, str(nonce), tx_tryte) 148 | 149 | time_end_pow = time.time() 150 | elapsed_pow = elapsed_pow + (time_end_pow - time_start_pow) 151 | 152 | # Update previous tx hash for next transaction 153 | trunk_hash = Transaction.from_tryte_string(tx_tryte[0:2673]).hash 154 | 155 | print("Prepare to store and broadcast ...") 156 | try: 157 | api.broadcast_and_store([tx_tryte[0:2673]]) 158 | except Exception as e: 159 | print("Error: %s" % (str(e.context))) 160 | 161 | time_end_send = time.time() 162 | elapsed_send = time_end_send - time_start_send 163 | 164 | if debug == 1: 165 | data = [{'platform': 'pi3', 'total_time': str(elapsed_send), 'elapsed_pow': str( 166 | elapsed_pow), 'elqpsed_bundle_finished': str(elapsed_bundle_finz)}] 167 | json_data = json.dumps(data) 168 | print(json_data) 169 | 170 | # attach_debug_message_to_tangle(json_data) 171 | obj_txn = api.find_transactions(bundles=[propose_bundle.hash]) 172 | return str(obj_txn['hashes'][0]) 173 | 174 | 175 | def attach_debug_message_to_tangle(data): 176 | tag = "SWARMNODETESTINGDATA" 177 | message = TryteString.from_string(data) 178 | address = "BXEOYAONFPBGKEUQZDUZZZODHWJDWHEOYY9AENYF9VNLXZHXBOODCOTYXW9MGGINTEJPLK9AGOPTPODVX" 179 | value = 0 180 | 181 | # Get tips 182 | print("Attaching debug data to tangle ... %s" % (str(data))) 183 | dict_tips = get_tips(0) 184 | 185 | print("Debug bundle = %s" % 186 | (str(send_transfer(tag, message, address, value, dict_tips, 0)))) 187 | 188 | 189 | def find_transactions_by_tag(data): 190 | 191 | try: 192 | list_result = api.find_transactions(tags=[data]) 193 | except BaseException: 194 | return [] 195 | 196 | return list_result 197 | 198 | 199 | def get_txn_msg(data): 200 | message = "" 201 | list_txn = [] 202 | 203 | try: 204 | list_txn = api.get_trytes([data]) 205 | except BaseException: 206 | return "" 207 | 208 | trytes_txn = str(list_txn['trytes'][0]) 209 | txn = Transaction.from_tryte_string(trytes_txn) 210 | 211 | try: 212 | message = TryteString(txn.signature_message_fragment).decode() 213 | except BaseException: 214 | return "" 215 | 216 | return message 217 | --------------------------------------------------------------------------------