From 62f5ff00727aecc68c9d2763dd0929b7299ad22f Mon Sep 17 00:00:00 2001 From: Lee Date: Mon, 27 Nov 2017 11:11:10 -0600 Subject: [PATCH 01/11] networking init --- Pipfile | 40 +++---- Pipfile.lock | 115 +++++++++++++++++++ src/core/__init__.py | 3 +- src/core/threader.py | 32 ++++++ src/network/__init__.py | 1 + src/network/network.py | 86 ++++++++++++++ src/persist/transaction.py | 96 ++++++++-------- src/persist/utxo.py | 228 ++++++++++++++++++------------------- 8 files changed, 418 insertions(+), 183 deletions(-) create mode 100644 Pipfile.lock create mode 100644 src/core/threader.py create mode 100644 src/network/__init__.py create mode 100644 src/network/network.py diff --git a/Pipfile b/Pipfile index 73980f5..19a7269 100644 --- a/Pipfile +++ b/Pipfile @@ -1,21 +1,21 @@ -[[source]] - -url = "https://pypi.python.org/simple" -verify_ssl = true -name = "pypi" - - -[dev-packages] - - - -[requires] - -python_version = "3.6" - - -[packages] - -flask = "==0.12.2" -requests = "==2.18.4" +[[source]] + +url = "https://pypi.python.org/simple" +verify_ssl = true +name = "pypi" + + +[dev-packages] + + + +[requires] + +python_version = "3.6" + + +[packages] + +flask = "==0.12.2" +requests = "*" plyvel = "*" \ No newline at end of file diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..cc36521 --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,115 @@ +{ + "_meta": { + "hash": { + "sha256": "b4f6e5d772744a97f81b9d8f9570bc069225a844e8fa961d5e156508f7ea41df" + }, + "host-environment-markers": { + "implementation_name": "cpython", + "implementation_version": "3.5.2", + "os_name": "posix", + "platform_machine": "x86_64", + "platform_python_implementation": "CPython", + "platform_release": "4.4.0-43-Microsoft", + "platform_system": "Linux", + "platform_version": "#1-Microsoft Wed Dec 31 14:42:53 PST 2014", + "python_full_version": "3.5.2", + "python_version": "3.5", + "sys_platform": "linux" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.6" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.python.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "certifi": { + "hashes": [ + "sha256:244be0d93b71e93fc0a0a479862051414d0e00e16435707e5bf5000f92e04694", + "sha256:5ec74291ca1136b40f0379e1128ff80e866597e4e2c1e755739a913bbc3613c0" + ], + "version": "==2017.11.5" + }, + "chardet": { + "hashes": [ + "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691", + "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae" + ], + "version": "==3.0.4" + }, + "click": { + "hashes": [ + "sha256:29f99fc6125fbc931b758dc053b3114e55c77a6e4c6c3a2674a2dc986016381d", + "sha256:f15516df478d5a56180fbf80e68f206010e6d160fc39fa508b65e035fd75130b" + ], + "version": "==6.7" + }, + "flask": { + "hashes": [ + "sha256:0749df235e3ff61ac108f69ac178c9770caeaccad2509cb762ce1f65570a8856", + "sha256:49f44461237b69ecd901cc7ce66feea0319b9158743dd27a2899962ab214dac1" + ], + "version": "==0.12.2" + }, + "idna": { + "hashes": [ + "sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4", + "sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f" + ], + "version": "==2.6" + }, + "itsdangerous": { + "hashes": [ + "sha256:cbb3fcf8d3e33df861709ecaf89d9e6629cff0a217bc2848f1b41cd30d360519" + ], + "version": "==0.24" + }, + "jinja2": { + "hashes": [ + "sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd", + "sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4" + ], + "version": "==2.10" + }, + "markupsafe": { + "hashes": [ + "sha256:a6be69091dac236ea9c6bc7d012beab42010fa914c459791d627dad4910eb665" + ], + "version": "==1.0" + }, + "plyvel": { + "hashes": [ + "sha256:587d93681ae44936ae086b4b45486eb302e3853ba5af149aac3be9e9713998e9" + ], + "version": "==0.9" + }, + "requests": { + "hashes": [ + "sha256:6a1b267aa90cac58ac3a765d067950e7dbbf75b1da07e895d1f594193a40a38b", + "sha256:9c443e7324ba5b85070c4a818ade28bfabedf16ea10206da1132edaa6dda237e" + ], + "version": "==2.18.4" + }, + "urllib3": { + "hashes": [ + "sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b", + "sha256:cc44da8e1145637334317feebd728bd869a35285b93cbb4cca2577da7e62db4f" + ], + "version": "==1.22" + }, + "werkzeug": { + "hashes": [ + "sha256:e8549c143af3ce6559699a01e26fa4174f4c591dbee0a499f3cd4c3781cdec3d", + "sha256:903a7b87b74635244548b30d30db4c8947fe64c5198f58899ddcd3a13c23bb26" + ], + "version": "==0.12.2" + } + }, + "develop": {} +} diff --git a/src/core/__init__.py b/src/core/__init__.py index a0bb0da..0d166ae 100644 --- a/src/core/__init__.py +++ b/src/core/__init__.py @@ -1 +1,2 @@ -from src.core.api import mine, create_transaction, get_block, init_configuration \ No newline at end of file +from src.core.api import mine, create_transaction, get_block, init_configuration +from src.core import threader \ No newline at end of file diff --git a/src/core/threader.py b/src/core/threader.py new file mode 100644 index 0000000..dac1a44 --- /dev/null +++ b/src/core/threader.py @@ -0,0 +1,32 @@ +import threading + + +class Threader(object): + ''' + Threading class for the application + ''' + + def __init__(self): + self.threads = [] + + def startBackgroundThread(self, method, args=False): + ''' + Start new thread + ''' + + if args: + newThread = threading.Thread(target=method, args=args) + else: + newThread = threading.Thread(target=method) + + newThread.start() + + self.threads.append(newThread) + + def waitForThreads(self, timeout=5.00): + ''' + Send stop signal to threads and wait for them to end + ''' + + for thread in self.threads: + thread.join(timeout) \ No newline at end of file diff --git a/src/network/__init__.py b/src/network/__init__.py new file mode 100644 index 0000000..75d0058 --- /dev/null +++ b/src/network/__init__.py @@ -0,0 +1 @@ +from src.network import network diff --git a/src/network/network.py b/src/network/network.py new file mode 100644 index 0000000..dfbae68 --- /dev/null +++ b/src/network/network.py @@ -0,0 +1,86 @@ +''' +Networking - Server +- handle seeding peers +- handle recieving new block +- handle updating chain on startup +- handle transmitting new block to network +- handle recieving new transaction from network +- handle transmitting new transaction to network +''' + +from flask import Flask +from flask import request + +app = Flask(__name__) + +def seed_peers(): + ''' + seed peers by connecting to firebase cloud function that returns a list of peers. + :return: + ''' + + url = 'https://us-central1-abc-network.cloudfunctions.net/getpeers/' + p = data # expecting {port: 50050} + r = request.get(url, params=p) + return r.json() + + +def trasmit(): + ''' + trasmit data by making a request to peers + :return: + ''' + # post request + # for each peer, make a request to the endpoint with the appropriate data + url = 'http://{0}:{2}/{1}'.format(host, endpoint, port) + payload = data + headers = {'content-type': 'application/json'} + r = request.post(url, data=payload, headers=headers) + return r.json() + +def recieve(): + ''' + receive data from peer through a get request to an endpoint + :return: + ''' + # get request + url = 'http://{0}:{2}/{1}'.format(host, endpoint, port) + p = data + r = request.get(url, params=p) + return r.json() + +@app.route('/block', methods=['POST']) +# recieve a new block from the network. +# should verify the block +# if valid stop mining, update height and transmit to peers +# start mining new block + +@app.route('/block', methods=['GET']) +# request for a certain block +# contain params for last block hash +# should return the next block + +@app.route('/height', methods=['GET']) +# request from network for block height +# should return current block height + +@app.route('/txn', methods=['POST']) +# recieve a new txn from the network. +# should verify the txn +# transmit to peers either way +# add to verified or unverified txns list + +@app.route('/txn', methods=['GET']) +# request for updated list of txns +# should return list of txns + +@app.route('/peers', methods=['GET']) +# request from network for peers +# should return peers + +@app.route('/ping', methods=['GET']) +# request to check if connection is alive +# should ping back + +if __name__ == '__main__': + app.run() \ No newline at end of file diff --git a/src/persist/transaction.py b/src/persist/transaction.py index be75693..cc640d2 100644 --- a/src/persist/transaction.py +++ b/src/persist/transaction.py @@ -1,49 +1,49 @@ -import os, json - -def save_verified_transaction(tnx_id, tnx_data): - """ - Saves the transaction to the verified pool. - Note that it will save the transaction as a key-value pair where the id is the key and the rest is the value - :param tnx_id: transaction id - :param tnx_data: transaction data - :return: None - """ - verified_tnx = {tnx_id: tnx_data} - try: - with open('{0}/verified_transactions.json'.format(os.path.join(os.getcwd(), r'data')), 'r+') as file: - data = json.load(file) - data.update(verified_tnx) - - file.seek(0) - json.dump(data, file) - file.close() - except IOError as e: - with open('{0}/verified_transactions.json'.format(os.path.join(os.getcwd(), r'data')), 'w') as file: - data = {} - data.update(verified_tnx) - json.dump(data, file) - file.close() - -def save_unverified_transaction(tnx_id, tnx_data): - """ - Saves the transaction to the unverified pool. - Note that it will save the transaction as a key-value pair where the id is the key and the rest is the value - :param tnx_id: transaction id - :param tnx_data: transaction data - :return: None - """ - unverified_tnx = {tnx_id: tnx_data} - try: - with open('{0}/unverified_transactions.json'.format(os.path.join(os.getcwd(), r'data')), 'r+') as file: - data = json.load(file) - data.update([unverified_tnx]) - - file.seek(0) - json.dump(data, file) - file.close() - except IOError as e: - with open('{0}/unverified_transactions.json'.format(os.path.join(os.getcwd(), r'data')), 'w') as file: - data = {} - data.update([unverified_tnx]) - json.dump(data, file) +import os, json + +def save_verified_transaction(tnx_id, tnx_data): + """ + Saves the transaction to the verified pool. + Note that it will save the transaction as a key-value pair where the id is the key and the rest is the value + :param tnx_id: transaction id + :param tnx_data: transaction data + :return: None + """ + verified_tnx = {tnx_id: tnx_data} + try: + with open('{0}/verified_transactions.json'.format(os.path.join(os.getcwd(), r'data')), 'r+') as file: + data = json.load(file) + data.update(verified_tnx) + + file.seek(0) + json.dump(data, file) + file.close() + except IOError as e: + with open('{0}/verified_transactions.json'.format(os.path.join(os.getcwd(), r'data')), 'w') as file: + data = {} + data.update(verified_tnx) + json.dump(data, file) + file.close() + +def save_unverified_transaction(tnx_id, tnx_data): + """ + Saves the transaction to the unverified pool. + Note that it will save the transaction as a key-value pair where the id is the key and the rest is the value + :param tnx_id: transaction id + :param tnx_data: transaction data + :return: None + """ + unverified_tnx = {tnx_id: tnx_data} + try: + with open('{0}/unverified_transactions.json'.format(os.path.join(os.getcwd(), r'data')), 'r+') as file: + data = json.load(file) + data.update([unverified_tnx]) + + file.seek(0) + json.dump(data, file) + file.close() + except IOError as e: + with open('{0}/unverified_transactions.json'.format(os.path.join(os.getcwd(), r'data')), 'w') as file: + data = {} + data.update([unverified_tnx]) + json.dump(data, file) file.close() \ No newline at end of file diff --git a/src/persist/utxo.py b/src/persist/utxo.py index a962fe2..1e6c1c0 100644 --- a/src/persist/utxo.py +++ b/src/persist/utxo.py @@ -1,114 +1,114 @@ -import copy -import json -import os - -_PATH_UNSPENT_TNX = '{0}/utxo.json'.format(os.path.join(os.getcwd(), r'data')) - - -def get_unspent_outputs(amount): - """ - Create a dict of unspent transaction outputs that add up - to or exceed `amount` - NOTE: This function assumes that the TX fee is included in the param amount - NOTE: This is most efficient if UTXOs are sorted in descending amount value - :param amount: the minimum amount required from the utxos - :return: a dict of utxo's if sufficient funds found, otherwise an empty dict. Also returns utxo sum - """ - - try: - with open(_PATH_UNSPENT_TNX) as file: - data = json.load(file) - file.close() - except IOError: - with open(_PATH_UNSPENT_TNX, 'w') as file: - data = {} - json.dump(data, file) - file.close() - - utxos = copy.deepcopy(data) - selected_utxos = [] - utxo_sum = 0 - - # NOTE: This is random at the moment - for key, value in utxos.items(): - if utxo_sum < amount: - selected_utxos.append({ - "transaction_id": key, - "output_index": value["index"], - "block_hash": value["block"]}) - data.pop(key) - utxo_sum = utxo_sum + value["amount"] - else: - break - - if utxo_sum >= amount: - # if there was sufficient funds, remove them from the utxo file - with open('{0}/utxo.json'.format(os.path.join(os.getcwd(), r'data')), 'w') as file: - json.dump(data, file) - file.close() - else: - raise ValueError("Insufficient funds") - return selected_utxos, utxo_sum - - -def find_unspent_output(transaction_id, output_index, block_hash): - """ - Get an unspent transaction output from the block chain - :param transaction_id: id of the transaction - :param output_index: index of the output within the transaction - :param block_hash: the block hash of the transaction - :return: - - a dict representing an unspent transaction output in the form: - - { - "transaction_id": "", - "output_index": "", - "address": "", - "amount": "" - } - - """ - - try: - with open('{0}/{1}.json'.format(os.path.join(os.getcwd(), r'data'), block_hash)) as file: - data = json.load(file) - file.close() - - output = data["transactions"][transaction_id]["outputs"][output_index] - - # adding additional info to the output object(since the output structure does not include index or id) - output["transaction_id"] = transaction_id - output["output_index"] = output_index - - return output - except IOError as e: - # file does not exist or not able to read file - print(e) - except KeyError as e: - # error finding utxo - print('Transaction not found\nTXID:{0}'.format(e)) - -def save_utxo(transaction_id, output_index, block_hash, amount): - new_utxo = {"{0}".format(transaction_id): { - "amount": amount, - "index": output_index, - "block": block_hash - }} - - try: - with open(_PATH_UNSPENT_TNX, 'r+') as file: - data = json.load(file) - data.update(new_utxo) - - file.seek(0) - json.dump(data, file) - file.close() - except IOError: - with open(_PATH_UNSPENT_TNX, 'w') as file: - data = {} - - data.update(new_utxo) - json.dump(data, file) - file.close() - +import copy +import json +import os + +_PATH_UNSPENT_TNX = '{0}/utxo.json'.format(os.path.join(os.getcwd(), r'data')) + + +def get_unspent_outputs(amount): + """ + Create a dict of unspent transaction outputs that add up + to or exceed `amount` + NOTE: This function assumes that the TX fee is included in the param amount + NOTE: This is most efficient if UTXOs are sorted in descending amount value + :param amount: the minimum amount required from the utxos + :return: a dict of utxo's if sufficient funds found, otherwise an empty dict. Also returns utxo sum + """ + + try: + with open(_PATH_UNSPENT_TNX) as file: + data = json.load(file) + file.close() + except IOError: + with open(_PATH_UNSPENT_TNX, 'w') as file: + data = {} + json.dump(data, file) + file.close() + + utxos = copy.deepcopy(data) + selected_utxos = [] + utxo_sum = 0 + + # NOTE: This is random at the moment + for key, value in utxos.items(): + if utxo_sum < amount: + selected_utxos.append({ + "transaction_id": key, + "output_index": value["index"], + "block_hash": value["block"]}) + data.pop(key) + utxo_sum = utxo_sum + value["amount"] + else: + break + + if utxo_sum >= amount: + # if there was sufficient funds, remove them from the utxo file + with open('{0}/utxo.json'.format(os.path.join(os.getcwd(), r'data')), 'w') as file: + json.dump(data, file) + file.close() + else: + raise ValueError("Insufficient funds") + return selected_utxos, utxo_sum + + +def find_unspent_output(transaction_id, output_index, block_hash): + """ + Get an unspent transaction output from the block chain + :param transaction_id: id of the transaction + :param output_index: index of the output within the transaction + :param block_hash: the block hash of the transaction + :return: + + a dict representing an unspent transaction output in the form: + + { + "transaction_id": "", + "output_index": "", + "address": "", + "amount": "" + } + + """ + + try: + with open('{0}/{1}.json'.format(os.path.join(os.getcwd(), r'data'), block_hash)) as file: + data = json.load(file) + file.close() + + output = data["transactions"][transaction_id]["outputs"][output_index] + + # adding additional info to the output object(since the output structure does not include index or id) + output["transaction_id"] = transaction_id + output["output_index"] = output_index + + return output + except IOError as e: + # file does not exist or not able to read file + print(e) + except KeyError as e: + # error finding utxo + print('Transaction not found\nTXID:{0}'.format(e)) + +def save_utxo(transaction_id, output_index, block_hash, amount): + new_utxo = {"{0}".format(transaction_id): { + "amount": amount, + "index": output_index, + "block": block_hash + }} + + try: + with open(_PATH_UNSPENT_TNX, 'r+') as file: + data = json.load(file) + data.update(new_utxo) + + file.seek(0) + json.dump(data, file) + file.close() + except IOError: + with open(_PATH_UNSPENT_TNX, 'w') as file: + data = {} + + data.update(new_utxo) + json.dump(data, file) + file.close() + From 0369cc62b2130a2c94bf4d25074f827a068fca3a Mon Sep 17 00:00:00 2001 From: Lee Date: Mon, 27 Nov 2017 19:11:55 -0600 Subject: [PATCH 02/11] flask server running buggy --- Pipfile | 3 +- Pipfile.lock | 246 +++++++++++++++-------------- src/client/cli.py | 7 + src/configuration/configuration.py | 10 +- src/core/threader.py | 59 ++++--- src/network/network.py | 131 ++++++++------- 6 files changed, 248 insertions(+), 208 deletions(-) diff --git a/Pipfile b/Pipfile index 19a7269..dc655f1 100644 --- a/Pipfile +++ b/Pipfile @@ -18,4 +18,5 @@ python_version = "3.6" flask = "==0.12.2" requests = "*" -plyvel = "*" \ No newline at end of file +plyvel = "*" +pycryptodome = "*" \ No newline at end of file diff --git a/Pipfile.lock b/Pipfile.lock index cc36521..ddddc8c 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,115 +1,131 @@ -{ - "_meta": { - "hash": { - "sha256": "b4f6e5d772744a97f81b9d8f9570bc069225a844e8fa961d5e156508f7ea41df" - }, - "host-environment-markers": { - "implementation_name": "cpython", - "implementation_version": "3.5.2", - "os_name": "posix", - "platform_machine": "x86_64", - "platform_python_implementation": "CPython", - "platform_release": "4.4.0-43-Microsoft", - "platform_system": "Linux", - "platform_version": "#1-Microsoft Wed Dec 31 14:42:53 PST 2014", - "python_full_version": "3.5.2", - "python_version": "3.5", - "sys_platform": "linux" - }, - "pipfile-spec": 6, - "requires": { - "python_version": "3.6" - }, - "sources": [ - { - "name": "pypi", - "url": "https://pypi.python.org/simple", - "verify_ssl": true - } - ] - }, - "default": { - "certifi": { - "hashes": [ - "sha256:244be0d93b71e93fc0a0a479862051414d0e00e16435707e5bf5000f92e04694", - "sha256:5ec74291ca1136b40f0379e1128ff80e866597e4e2c1e755739a913bbc3613c0" - ], - "version": "==2017.11.5" - }, - "chardet": { - "hashes": [ - "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691", - "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae" - ], - "version": "==3.0.4" - }, - "click": { - "hashes": [ - "sha256:29f99fc6125fbc931b758dc053b3114e55c77a6e4c6c3a2674a2dc986016381d", - "sha256:f15516df478d5a56180fbf80e68f206010e6d160fc39fa508b65e035fd75130b" - ], - "version": "==6.7" - }, - "flask": { - "hashes": [ - "sha256:0749df235e3ff61ac108f69ac178c9770caeaccad2509cb762ce1f65570a8856", - "sha256:49f44461237b69ecd901cc7ce66feea0319b9158743dd27a2899962ab214dac1" - ], - "version": "==0.12.2" - }, - "idna": { - "hashes": [ - "sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4", - "sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f" - ], - "version": "==2.6" - }, - "itsdangerous": { - "hashes": [ - "sha256:cbb3fcf8d3e33df861709ecaf89d9e6629cff0a217bc2848f1b41cd30d360519" - ], - "version": "==0.24" - }, - "jinja2": { - "hashes": [ - "sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd", - "sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4" - ], - "version": "==2.10" - }, - "markupsafe": { - "hashes": [ - "sha256:a6be69091dac236ea9c6bc7d012beab42010fa914c459791d627dad4910eb665" - ], - "version": "==1.0" - }, - "plyvel": { - "hashes": [ - "sha256:587d93681ae44936ae086b4b45486eb302e3853ba5af149aac3be9e9713998e9" - ], - "version": "==0.9" - }, - "requests": { - "hashes": [ - "sha256:6a1b267aa90cac58ac3a765d067950e7dbbf75b1da07e895d1f594193a40a38b", - "sha256:9c443e7324ba5b85070c4a818ade28bfabedf16ea10206da1132edaa6dda237e" - ], - "version": "==2.18.4" - }, - "urllib3": { - "hashes": [ - "sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b", - "sha256:cc44da8e1145637334317feebd728bd869a35285b93cbb4cca2577da7e62db4f" - ], - "version": "==1.22" - }, - "werkzeug": { - "hashes": [ - "sha256:e8549c143af3ce6559699a01e26fa4174f4c591dbee0a499f3cd4c3781cdec3d", - "sha256:903a7b87b74635244548b30d30db4c8947fe64c5198f58899ddcd3a13c23bb26" - ], - "version": "==0.12.2" - } - }, - "develop": {} -} +{ + "_meta": { + "hash": { + "sha256": "04629642b8cd689ef2436c467d19507850137cb80fe335c90d642c51df8255ca" + }, + "host-environment-markers": { + "implementation_name": "cpython", + "implementation_version": "3.6.2", + "os_name": "posix", + "platform_machine": "x86_64", + "platform_python_implementation": "CPython", + "platform_release": "4.4.0-43-Microsoft", + "platform_system": "Linux", + "platform_version": "#1-Microsoft Wed Dec 31 14:42:53 PST 2014", + "python_full_version": "3.6.2", + "python_version": "3.6", + "sys_platform": "linux" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.6" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.python.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "certifi": { + "hashes": [ + "sha256:244be0d93b71e93fc0a0a479862051414d0e00e16435707e5bf5000f92e04694", + "sha256:5ec74291ca1136b40f0379e1128ff80e866597e4e2c1e755739a913bbc3613c0" + ], + "version": "==2017.11.5" + }, + "chardet": { + "hashes": [ + "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691", + "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae" + ], + "version": "==3.0.4" + }, + "click": { + "hashes": [ + "sha256:29f99fc6125fbc931b758dc053b3114e55c77a6e4c6c3a2674a2dc986016381d", + "sha256:f15516df478d5a56180fbf80e68f206010e6d160fc39fa508b65e035fd75130b" + ], + "version": "==6.7" + }, + "flask": { + "hashes": [ + "sha256:0749df235e3ff61ac108f69ac178c9770caeaccad2509cb762ce1f65570a8856", + "sha256:49f44461237b69ecd901cc7ce66feea0319b9158743dd27a2899962ab214dac1" + ], + "version": "==0.12.2" + }, + "idna": { + "hashes": [ + "sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4", + "sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f" + ], + "version": "==2.6" + }, + "itsdangerous": { + "hashes": [ + "sha256:cbb3fcf8d3e33df861709ecaf89d9e6629cff0a217bc2848f1b41cd30d360519" + ], + "version": "==0.24" + }, + "jinja2": { + "hashes": [ + "sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd", + "sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4" + ], + "version": "==2.10" + }, + "markupsafe": { + "hashes": [ + "sha256:a6be69091dac236ea9c6bc7d012beab42010fa914c459791d627dad4910eb665" + ], + "version": "==1.0" + }, + "plyvel": { + "hashes": [ + "sha256:587d93681ae44936ae086b4b45486eb302e3853ba5af149aac3be9e9713998e9" + ], + "version": "==0.9" + }, + "pycryptodome": { + "hashes": [ + "sha256:2174fa555916b5ae8bcc7747ecfe2a4d5943b42c9dcf4878e269baaae264e85d", + "sha256:9fc97cd0f6eeec59af736b3df81e5811d836fa646b89a4325672dcaf997250b3", + "sha256:8440a35ccd52f0eab0f4ece284bd13a587d86d79bd404d8914f81eda74a66de1", + "sha256:6f64d8b63034fd9289bae4cb48aa8f7049f6b8db702c7af50cb3718821d28147", + "sha256:f0196124f83221f9c5e06a68e247019466395d35d92d4ce4482c835f75302851", + "sha256:8851b1e1d85e4fb981048c8a8a8431839103f43ea3c35f1b46bae2e41699f439", + "sha256:a9e3e3e9ab0241b0303206656a74d5cd6bd00fcad6f9ffd0ba6b8e35072f74d7", + "sha256:15ced95a00b55bb2fc22f3dddde1c8d6f270089f35c3af0e07306bc2ba1e1c4e", + "sha256:f7befe2249df41e012a3d8079ab3c7089be21969591eb77b21767fa24557a7b7", + "sha256:ec560e62258358afd7a1a3d34c8860fdf478e28c0999173f2d5c618fd2fd60d3", + "sha256:18d8dfe31bf0cb53d58694903e526be68f3cf48e6e3c6dfbbc1e7042b1693af7" + ], + "version": "==3.4.7" + }, + "requests": { + "hashes": [ + "sha256:6a1b267aa90cac58ac3a765d067950e7dbbf75b1da07e895d1f594193a40a38b", + "sha256:9c443e7324ba5b85070c4a818ade28bfabedf16ea10206da1132edaa6dda237e" + ], + "version": "==2.18.4" + }, + "urllib3": { + "hashes": [ + "sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b", + "sha256:cc44da8e1145637334317feebd728bd869a35285b93cbb4cca2577da7e62db4f" + ], + "version": "==1.22" + }, + "werkzeug": { + "hashes": [ + "sha256:e8549c143af3ce6559699a01e26fa4174f4c591dbee0a499f3cd4c3781cdec3d", + "sha256:903a7b87b74635244548b30d30db4c8947fe64c5198f58899ddcd3a13c23bb26" + ], + "version": "==0.12.2" + } + }, + "develop": {} +} diff --git a/src/client/cli.py b/src/client/cli.py index d551ea2..135d8d2 100644 --- a/src/client/cli.py +++ b/src/client/cli.py @@ -1,10 +1,12 @@ ''' The cmd line interface ''' import cmd import json +import _thread from src.client.helpers import cromulon # TODO: this module should not explicitly import persist or config. Need api fot this from src.core import mine, create_transaction, get_block, init_configuration +from src.network.network import start_server, seed_peers class CLI(cmd.Cmd, object): @@ -54,6 +56,7 @@ def do_block_info(self, arg): print(json.dumps(b, indent=4, sort_keys=True)) def do_send(self, arg): + 'Send an amount' # TODO: add exception handling for wrong input format try: args = arg.split() @@ -103,6 +106,10 @@ def preloop(self): self.wallet = self.conf.get_conf("wallet") self.peers = self.conf.get_conf("peers") + _thread.start_new_thread(start_server, ()) # start flask server on new thread + # seed_peers() + + def postloop(self): 'Do stuff on end' # this is where we want to save all the data on exit diff --git a/src/configuration/configuration.py b/src/configuration/configuration.py index 31742c5..062171c 100644 --- a/src/configuration/configuration.py +++ b/src/configuration/configuration.py @@ -56,14 +56,16 @@ def create_conf(self): 'address': hashed_address, 'amount': 0 }, + 'ip': "0.0.0.0", + 'port': "5000", 'peers': { '1': { - 'ip': "127.0.0.1", - 'port': 3390 + 'ip': "0.0.0.0", + 'port': 5001 }, '2': { - 'ip': "localhost", - 'port': 3390 + 'ip': "0.0.0.0", + 'port': 5002 } } } diff --git a/src/core/threader.py b/src/core/threader.py index dac1a44..38ce7d8 100644 --- a/src/core/threader.py +++ b/src/core/threader.py @@ -1,32 +1,31 @@ import threading - -class Threader(object): - ''' - Threading class for the application - ''' - - def __init__(self): - self.threads = [] - - def startBackgroundThread(self, method, args=False): - ''' - Start new thread - ''' - - if args: - newThread = threading.Thread(target=method, args=args) - else: - newThread = threading.Thread(target=method) - - newThread.start() - - self.threads.append(newThread) - - def waitForThreads(self, timeout=5.00): - ''' - Send stop signal to threads and wait for them to end - ''' - - for thread in self.threads: - thread.join(timeout) \ No newline at end of file +# class Threader(object): +# ''' +# Threading class for the application +# ''' +# +# def __init__(self): +# self.threads = [] +# +# def startBackgroundThread(self, method, args=False): +# ''' +# Start new thread +# ''' +# +# if args: +# newThread = threading.Thread(target=method, args=args) +# else: +# newThread = threading.Thread(target=method) +# +# newThread.start() +# +# self.threads.append(newThread) +# +# def waitForThreads(self, timeout=5.00): +# ''' +# Send stop signal to threads and wait for them to end +# ''' +# +# for thread in self.threads: +# thread.join(timeout) \ No newline at end of file diff --git a/src/network/network.py b/src/network/network.py index dfbae68..817a386 100644 --- a/src/network/network.py +++ b/src/network/network.py @@ -10,77 +10,92 @@ from flask import Flask from flask import request +import requests +from src.configuration import Configuration app = Flask(__name__) - -def seed_peers(): - ''' - seed peers by connecting to firebase cloud function that returns a list of peers. - :return: - ''' - - url = 'https://us-central1-abc-network.cloudfunctions.net/getpeers/' - p = data # expecting {port: 50050} - r = request.get(url, params=p) - return r.json() +# app.logger.disabled = True -def trasmit(): +def start_server(): ''' - trasmit data by making a request to peers + Starts the server and prevents default start :return: ''' - # post request - # for each peer, make a request to the endpoint with the appropriate data - url = 'http://{0}:{2}/{1}'.format(host, endpoint, port) - payload = data - headers = {'content-type': 'application/json'} - r = request.post(url, data=payload, headers=headers) - return r.json() + app.run(host='0.0.0.0', port='5000', use_reloader=False, debug=False) + -def recieve(): +def seed_peers(): ''' - receive data from peer through a get request to an endpoint + seed peers by connecting to firebase cloud function that returns a list of peers. :return: ''' - # get request - url = 'http://{0}:{2}/{1}'.format(host, endpoint, port) - p = data - r = request.get(url, params=p) + conf = Configuration() + url = 'https://us-central1-abc-network.cloudfunctions.net/getpeers/' + port = conf.get_conf("port") # expecting {port: 50050} + p = {'port': port} + r = requests.get(url, params=p) # Not currently working. maybe requires POST? return r.json() -@app.route('/block', methods=['POST']) -# recieve a new block from the network. -# should verify the block -# if valid stop mining, update height and transmit to peers -# start mining new block - -@app.route('/block', methods=['GET']) -# request for a certain block -# contain params for last block hash -# should return the next block - -@app.route('/height', methods=['GET']) -# request from network for block height -# should return current block height - -@app.route('/txn', methods=['POST']) -# recieve a new txn from the network. -# should verify the txn -# transmit to peers either way -# add to verified or unverified txns list - -@app.route('/txn', methods=['GET']) -# request for updated list of txns -# should return list of txns -@app.route('/peers', methods=['GET']) -# request from network for peers -# should return peers +# def trasmit(): +# ''' +# trasmit data by making a request to peers +# :return: +# ''' +# # post request +# # for each peer, make a request to the endpoint with the appropriate data +# url = 'http://{0}:{2}/{1}'.format(host, endpoint, port) +# payload = data +# headers = {'content-type': 'application/json'} +# r = requests.post(url, data=payload, headers=headers) +# return r.json() +# +# def recieve(): +# ''' +# receive data from peer through a get request to an endpoint +# :return: +# ''' +# # get request +# url = 'http://{0}:{2}/{1}'.format(host, endpoint, port) +# p = data +# r = requests.get(url, params=p) +# return r.json() -@app.route('/ping', methods=['GET']) -# request to check if connection is alive -# should ping back +@app.route('/', methods=['GET']) +def test(): + print("working.") + data = seed_peers() -if __name__ == '__main__': - app.run() \ No newline at end of file +# @app.route('/block', methods=['POST']) +# # recieve a new block from the network. +# # should verify the block +# # if valid stop mining, update height and transmit to peers +# # start mining new block +# +# @app.route('/block', methods=['GET']) +# # request for a certain block +# # contain params for last block hash +# # should return the next block +# +# @app.route('/height', methods=['GET']) +# # request from network for block height +# # should return current block height +# +# @app.route('/txn', methods=['POST']) +# # recieve a new txn from the network. +# # should verify the txn +# # transmit to peers either way +# # add to verified or unverified txns list +# +# @app.route('/txn', methods=['GET']) +# # request for updated list of txns +# # should return list of txns +# +# @app.route('/peers', methods=['GET']) +# # request from network for peers +# # should return peers +# +# @app.route('/ping', methods=['GET']) +# # request to check if connection is alive +# # should ping back From 5f435dc9675dd3ba819e3c808805d11107be85c8 Mon Sep 17 00:00:00 2001 From: Lee Date: Mon, 4 Dec 2017 20:34:37 -0600 Subject: [PATCH 03/11] networking stuff --- Pipfile | 1 - Pipfile.lock | 8 +-- main.py | 4 ++ src/block/block.py | 42 ++++++++---- src/client/cli.py | 2 +- src/core/api.py | 7 ++ src/network/network.py | 150 +++++++++++++++++++++++++++++------------ src/persist/block.py | 18 ++++- 8 files changed, 163 insertions(+), 69 deletions(-) diff --git a/Pipfile b/Pipfile index dc655f1..de6b89c 100644 --- a/Pipfile +++ b/Pipfile @@ -18,5 +18,4 @@ python_version = "3.6" flask = "==0.12.2" requests = "*" -plyvel = "*" pycryptodome = "*" \ No newline at end of file diff --git a/Pipfile.lock b/Pipfile.lock index ddddc8c..e440d0b 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "04629642b8cd689ef2436c467d19507850137cb80fe335c90d642c51df8255ca" + "sha256": "bf2ebb1f31508383de0ec6cf0f9686bfcd145810fac55eec1083dcd70fb91b5c" }, "host-environment-markers": { "implementation_name": "cpython", @@ -83,12 +83,6 @@ ], "version": "==1.0" }, - "plyvel": { - "hashes": [ - "sha256:587d93681ae44936ae086b4b45486eb302e3853ba5af149aac3be9e9713998e9" - ], - "version": "==0.9" - }, "pycryptodome": { "hashes": [ "sha256:2174fa555916b5ae8bcc7747ecfe2a4d5943b42c9dcf4878e269baaae264e85d", diff --git a/main.py b/main.py index e757515..e08138c 100644 --- a/main.py +++ b/main.py @@ -1,5 +1,9 @@ ''' Main File ''' # from block import Block +import _thread, time from src.client import CLI +from src.network.network import start_server, seed_peers +_thread.start_new_thread(start_server, ()) +time.sleep(1) CLI().cmdloop() diff --git a/src/block/block.py b/src/block/block.py index 9b1784b..44f2045 100644 --- a/src/block/block.py +++ b/src/block/block.py @@ -16,7 +16,7 @@ class Block(object): - def __init__(self, previous_hash=None, transactions=None): + def __init__(self, previous_hash=None, transactions=None, **kwargs): """ Constructor for Block class Block Header: Key info on block (80 bytes) @@ -30,19 +30,33 @@ def __init__(self, previous_hash=None, transactions=None): merkle_root - a hash of all the hashed transactions in the merkle tree """ - - # define block header attributes - self.version = version.encode('utf8') # 4 bytes - self.previous_hash = previous_hash.encode('utf8') # 32 bytes - self.merkle_root = self.merkle_root(transactions) # 32 bytes - self.timestamp = self.block_timestamp() # 4 bytes - self.nonce = None # 4 bytes - self.target = None # 4 bytes - - # define rest of block - self.transactions = transactions # NOTE: may need to change - self.txcount = len(transactions) # 4 bytes - self.size = self.block_size() # 4 bytes + self.payload = kwargs.pop('payload', None) + if self.payload: + # define block header attributes + self.version = self.payload['header']['version'].encode('utf8') # 4 bytes + self.previous_hash = self.payload['header']['parent'].encode('utf8') # 32 bytes + self.merkle_root = self.payload['header']['merkle_root'] # 32 bytes + self.timestamp = self.payload['header']['timestamp'].encode('utf8') # 4 bytes + self.nonce = self.payload['header']['nonce'] + self.target = self.payload['header']['target'] + + # define rest of block + self.transactions = self.payload['transactions'] # NOTE: may need to change + self.txcount = self.payload['txcount'] # 4 bytes + self.size = self.payload['size'] + else: + # define block header attributes + self.version = version.encode('utf8') # 4 bytes + self.previous_hash = previous_hash.encode('utf8') # 32 bytes + self.merkle_root = self.merkle_root(transactions) # 32 bytes + self.timestamp = self.block_timestamp() # 4 bytes + self.nonce = None # 4 bytes + self.target = None # 4 bytes + + # define rest of block + self.transactions = transactions # NOTE: may need to change + self.txcount = len(transactions) # 4 bytes + self.size = self.block_size() # 4 bytes def block_size(self): # calculates the size of the block and returns instance size to value diff --git a/src/client/cli.py b/src/client/cli.py index 135d8d2..a176d44 100644 --- a/src/client/cli.py +++ b/src/client/cli.py @@ -106,7 +106,7 @@ def preloop(self): self.wallet = self.conf.get_conf("wallet") self.peers = self.conf.get_conf("peers") - _thread.start_new_thread(start_server, ()) # start flask server on new thread + # _thread.start_new_thread(start_server, ()) # start flask server on new thread # seed_peers() diff --git a/src/core/api.py b/src/core/api.py index d478852..4f2f6f5 100644 --- a/src/core/api.py +++ b/src/core/api.py @@ -27,6 +27,13 @@ def mine(): conf.increment_height() conf.update_previous_hash(b.block_hash()) +def verify_block(b): + # need to check txns + conf = Configuration() + bh = Block(payload=json.loads(b)) + save_block(bh) + conf.increment_height() + conf.update_previous_hash(bh.block_hash()) def create_transaction(recipient, amount): """ diff --git a/src/network/network.py b/src/network/network.py index 817a386..4a6c347 100644 --- a/src/network/network.py +++ b/src/network/network.py @@ -11,11 +11,15 @@ from flask import Flask from flask import request import requests +import logging, json, os from src.configuration import Configuration +from src.persist import block +from src.core import api as core app = Flask(__name__) # app.logger.disabled = True - +log = logging.getLogger('werkzeug') +log.setLevel(logging.ERROR) def start_server(): ''' @@ -38,64 +42,124 @@ def seed_peers(): return r.json() -# def trasmit(): -# ''' -# trasmit data by making a request to peers -# :return: -# ''' -# # post request -# # for each peer, make a request to the endpoint with the appropriate data -# url = 'http://{0}:{2}/{1}'.format(host, endpoint, port) -# payload = data -# headers = {'content-type': 'application/json'} -# r = requests.post(url, data=payload, headers=headers) -# return r.json() -# -# def recieve(): -# ''' -# receive data from peer through a get request to an endpoint -# :return: -# ''' -# # get request -# url = 'http://{0}:{2}/{1}'.format(host, endpoint, port) -# p = data -# r = requests.get(url, params=p) -# return r.json() +def req_post(host, endpoint, port, data): + ''' + trasmit data by making a request to peers + :return: + ''' + # post request + # for each peer, make a request to the endpoint with the appropriate data + url = 'http://{0}:{2}/{1}'.format(host, endpoint, port) + payload = json.dumps(data) + headers = {'content-type': 'application/json', 'dataType': 'json'} + r = requests.post(url, data=payload, headers=headers) + # print(r.json()) + return r.json() + +def req_get(host, endpoint, port, data=None): + ''' + receive data from peer through a get request to an endpoint + :return: + ''' + # get request + url = 'http://{0}:{2}/{1}'.format(host, endpoint, port) + p = data + r = requests.get(url, params=p) + return r.json() + +def transmit(data, handler): + # send data to all peers + conf = Configuration() + peers = conf.get_conf("peers") + for n in peers: + p = peers[n] + h = req_post(p["ip"], handler, p["port"], data) # make post to peer + +def sync(): + ''' + sync with peers by checking your height against peers and getting transactions. + :return: + ''' + conf = Configuration() + peers = conf.get_conf("peers") + for n in peers: + p = peers[n] + h = req_get(p["ip"], "height", p["port"]) # make request to get peers height + while h > conf.get_conf("height"): + nb = req_get(p["ip"], "block", p["port"], conf.get_conf("last_block")) + core.verify_block(nb) + @app.route('/', methods=['GET']) def test(): - print("working.") - data = seed_peers() + b = block.read_block("0000fff57a5e49c38152b54edfd587c738ba33708008c995d973c2d2238179a6") + r = transmit(b, "block") + # r = req_post('localhost', 'block', '5001', b) + return "working" -# @app.route('/block', methods=['POST']) +@app.route('/block', methods=['POST']) # # recieve a new block from the network. # # should verify the block # # if valid stop mining, update height and transmit to peers # # start mining new block -# -# @app.route('/block', methods=['GET']) +def h_block(): + # handle getting new block + nb = request.get_json(silent=True) + core.verify_block(nb) + return json.dumps(nb) + +@app.route('/block', methods=['GET']) # # request for a certain block # # contain params for last block hash # # should return the next block -# -# @app.route('/height', methods=['GET']) -# # request from network for block height -# # should return current block height -# -# @app.route('/txn', methods=['POST']) +def r_block(): + bh = request.args.get("block") + b = block.find_block(bh) + return json.dumps(b) + +@app.route('/height', methods=['GET']) +# request from network for block height +# should return current block height +def r_height(): + conf = Configuration() + h = {'height': conf.get_conf("height")} + return json.dumps(h) + +@app.route('/txn', methods=['POST']) # # recieve a new txn from the network. # # should verify the txn # # transmit to peers either way # # add to verified or unverified txns list -# -# @app.route('/txn', methods=['GET']) +def h_txn(): + # handle the txn + return "receive txn" + +@app.route('/txn', methods=['GET']) # # request for updated list of txns # # should return list of txns -# -# @app.route('/peers', methods=['GET']) -# # request from network for peers -# # should return peers -# -# @app.route('/ping', methods=['GET']) +def r_txn(): + try: + with open('{0}/verified_transactions.json'.format(os.path.join(os.getcwd(), r'data')), 'r') as file: + data = json.load(file) + file.close() + except IOError: + with open('{0}/verified_transactions.json'.format(os.path.join(os.getcwd(), r'data')), 'w') as file: + data = {} + json.dump(data, file) + file.close() + return json.dumps(data) + +@app.route('/peers', methods=['GET']) +# request from network for peers +# should return peers +def r_peers(): + conf = Configuration() + peers = conf.get_conf("peers") + return json.dumps(peers) + +@app.route('/ping', methods=['GET']) # # request to check if connection is alive # # should ping back +def ping(): + r = {'ping': 'pong'} + return json.dumps(r) diff --git a/src/persist/block.py b/src/persist/block.py index c8973d7..1d62e90 100644 --- a/src/persist/block.py +++ b/src/persist/block.py @@ -1,7 +1,7 @@ """ Getting and Putting blocks into json files """ import json import os -from src.persist.utxo import save_utxo +from src.configuration import Configuration def read_block(block_hash): @@ -15,9 +15,20 @@ def read_block(block_hash): # file does not exist or not able to read file data = {} print(e) - return data +def find_block(parent_hash=None, block_hash=None): + # find the next block based on hash.. + if not block_hash: + conf = Configuration() + block_hash = conf.get_conf("last_block") + + b = read_block(block_hash) + if b["header"]["parent"] == parent_hash: + return b + else: + find_block(parent_hash, b["header"]["parent"]) + def save_block(b): # saves the block @@ -26,4 +37,5 @@ def save_block(b): json.dump(b.info(), file, indent=4, sort_keys=True) file.close() except IOError as e: - print(e) \ No newline at end of file + print(e) + From 5ed885c57e076b61758c4b0fc94a22797d337087 Mon Sep 17 00:00:00 2001 From: Lee Date: Tue, 5 Dec 2017 14:07:18 -0600 Subject: [PATCH 04/11] update --- Dockerfile | 3 +++ src/core/api.py | 3 ++- src/network/network.py | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index f0c48cb..195bdc8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,7 +7,10 @@ ENV BUILD_LIST git RUN apk add --update $BUILD_LIST \ && git clone https://github.com/leesander1/ABC.git /abc \ + && git checkout -b docker origin/networking \ && apk --repository http://dl-3.alpinelinux.org/alpine/edge/testing/ --update add leveldb leveldb-dev \ + && apk add --no-cache gcc \ + && apk add musl-dev \ && pip install pipenv \ && pipenv --python=python3.6 \ && pipenv install \ diff --git a/src/core/api.py b/src/core/api.py index 4f2f6f5..39eab39 100644 --- a/src/core/api.py +++ b/src/core/api.py @@ -8,7 +8,7 @@ from src.configuration import Configuration from src.transaction import Transaction from src.wallet import get_public_key, get_private_key - +from src.network import network def mine(): # config object @@ -26,6 +26,7 @@ def mine(): save_block(b) conf.increment_height() conf.update_previous_hash(b.block_hash()) + # TODO: create new transmit with mined block def verify_block(b): # need to check txns diff --git a/src/network/network.py b/src/network/network.py index 4a6c347..72e7db3 100644 --- a/src/network/network.py +++ b/src/network/network.py @@ -26,7 +26,7 @@ def start_server(): Starts the server and prevents default start :return: ''' - app.run(host='0.0.0.0', port='5000', use_reloader=False, debug=False) + app.run(host='0.0.0.0', port='50050', use_reloader=False, debug=False) def seed_peers(): From 65aabd5a6c8625fe4524a6d079bb51b1c3eb9726 Mon Sep 17 00:00:00 2001 From: Lee Date: Tue, 5 Dec 2017 14:17:45 -0600 Subject: [PATCH 05/11] update --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 195bdc8..6ddde70 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,6 +17,6 @@ RUN apk add --update $BUILD_LIST \ && apk del $BUILD_LIST \ && rm -rf /var/cache/apk/* -EXPOSE 5000 +EXPOSE 50050 ENTRYPOINT [ "pipenv", "run", "python", "/abc/main.py" ] \ No newline at end of file From 7c492c67243fb81a8d4a69accd003a2ff2531c2c Mon Sep 17 00:00:00 2001 From: Kevin Taing Date: Tue, 5 Dec 2017 17:03:33 -0600 Subject: [PATCH 06/11] added verify_incoming_tnx --- src/network/network.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/network/network.py b/src/network/network.py index 72e7db3..0110426 100644 --- a/src/network/network.py +++ b/src/network/network.py @@ -13,8 +13,9 @@ import requests import logging, json, os from src.configuration import Configuration -from src.persist import block +from src.persist import block, save_verified_transaction, save_unverified_transaction from src.core import api as core +from src.transaction import Transaction app = Flask(__name__) # app.logger.disabled = True @@ -90,6 +91,24 @@ def sync(): core.verify_block(nb) +def verify_incoming_tnx(data): + """ + Deserializes transaction and verifies it. If so, add it to verified transaction + :param data: serialized transaction + :return: None + """ + tnx_tuple = data.items() + + tnx_payload = tnx_tuple[1] + tnx_payload["transaction_id"] = tnx_tuple[0] + + tnx = Transaction(payload=tnx_payload) + + if tnx.verify(): + save_verified_transaction(tnx.get_transaction_id(), tnx.get_data()) + else: + save_unverified_transaction(tnx.get_transaction_id(), tnx.get_data()) + @app.route('/', methods=['GET']) def test(): b = block.read_block("0000fff57a5e49c38152b54edfd587c738ba33708008c995d973c2d2238179a6") From 44bdba7635704c1f5ac0f9f03c28e56cb38a2247 Mon Sep 17 00:00:00 2001 From: Lee Date: Tue, 5 Dec 2017 18:34:19 -0600 Subject: [PATCH 07/11] update --- Dockerfile | 2 +- src/network/network.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 6ddde70..195bdc8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,6 +17,6 @@ RUN apk add --update $BUILD_LIST \ && apk del $BUILD_LIST \ && rm -rf /var/cache/apk/* -EXPOSE 50050 +EXPOSE 5000 ENTRYPOINT [ "pipenv", "run", "python", "/abc/main.py" ] \ No newline at end of file diff --git a/src/network/network.py b/src/network/network.py index 72e7db3..4a6c347 100644 --- a/src/network/network.py +++ b/src/network/network.py @@ -26,7 +26,7 @@ def start_server(): Starts the server and prevents default start :return: ''' - app.run(host='0.0.0.0', port='50050', use_reloader=False, debug=False) + app.run(host='0.0.0.0', port='5000', use_reloader=False, debug=False) def seed_peers(): From 2cf60a39ce1031b771349bc96c51c268dd7dad64 Mon Sep 17 00:00:00 2001 From: Lee Date: Tue, 5 Dec 2017 18:45:40 -0600 Subject: [PATCH 08/11] transmit --- src/core/api.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/core/api.py b/src/core/api.py index 39eab39..41dcc1c 100644 --- a/src/core/api.py +++ b/src/core/api.py @@ -27,7 +27,8 @@ def mine(): conf.increment_height() conf.update_previous_hash(b.block_hash()) # TODO: create new transmit with mined block - + network.transmit(b, "block") + def verify_block(b): # need to check txns conf = Configuration() @@ -49,6 +50,7 @@ def create_transaction(recipient, amount): tx.add_output(recipient, amount) tx.unlock_inputs(get_private_key(), get_public_key("string")) save_verified_transaction(tx.get_transaction_id(), tx.get_data()) + network.transmit(tx.get_data(), "txn") except ValueError as e: # Will raise if insufficient utxos are found raise ValueError("INSUFFICIENT FUNDS") From 83c741c55507fa1dd2cc22cca750e85e6769d64e Mon Sep 17 00:00:00 2001 From: Lee Sander Date: Tue, 5 Dec 2017 18:59:36 -0600 Subject: [PATCH 09/11] Auto stash before merge of "networking" and "origin/networking" --- Pipfile | 21 -- README.md | 12 + src/configuration/configuration.py | 230 +++++++------ src/transaction/transaction.py | 508 +++++++++++++++-------------- 4 files changed, 395 insertions(+), 376 deletions(-) delete mode 100644 Pipfile diff --git a/Pipfile b/Pipfile deleted file mode 100644 index de6b89c..0000000 --- a/Pipfile +++ /dev/null @@ -1,21 +0,0 @@ -[[source]] - -url = "https://pypi.python.org/simple" -verify_ssl = true -name = "pypi" - - -[dev-packages] - - - -[requires] - -python_version = "3.6" - - -[packages] - -flask = "==0.12.2" -requests = "*" -pycryptodome = "*" \ No newline at end of file diff --git a/README.md b/README.md index 8122a9f..2163474 100644 --- a/README.md +++ b/README.md @@ -7,3 +7,15 @@ The core functions we will discuss, explore and develop are the following: 3. Communication with other nodes 4. Proof of work algorithms 5. Transactions + + +### Docker + +```bash +$ docker build -t abc . +``` +```bash +$ docker run -it --rm -p 80:5000 abc +$ docker run -it --rm -p 80:5001 abc +$ docker run -it --rm -p 80:5002 abc +``` diff --git a/src/configuration/configuration.py b/src/configuration/configuration.py index 062171c..a259be6 100644 --- a/src/configuration/configuration.py +++ b/src/configuration/configuration.py @@ -1,106 +1,124 @@ -""" Singleton used to access client config """ -import json -import os - -from Crypto.Hash import SHA256 -from src.wallet import get_public_key - -# private path -_CONFIGURATION_PATH = '{0}/abc.json'.format(os.path.join(os.getcwd(), r'data')) - - -class Singleton(type): - _instances = {} - - def __call__(cls, *args, **kwargs): - if cls not in cls._instances: - cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) - return cls._instances[cls] - - -class Configuration(metaclass=Singleton): - def __init__(self): - """ - 1) check to see if conf appdata exists - 2) if it does load it up then begin sync ( connect, get updates, etc ) - 3) if not, create new public key/wallet, files, connect to some seed peers, sync - """ - self.conf = {} - self.load_conf() - - def load_conf(self): - # loads the config file - try: - with open(_CONFIGURATION_PATH) as file: - # read in the data - self.conf = json.load(file) - file.close() - pass - except IOError as e: - # file does not exist or not able to read file - self.create_conf() - print("Creating new configuration") - - def create_conf(self): - # creates a new config - pubkey = get_public_key("string") - hashed_address = SHA256.new(pubkey.encode()).hexdigest() - - self.conf = { - 'height': 0, - 'last_block': "", - 'version': "00000001", - 'difficulty': 4, - 'reward': 100, - 'wallet': { - 'address': hashed_address, - 'amount': 0 - }, - 'ip': "0.0.0.0", - 'port': "5000", - 'peers': { - '1': { - 'ip': "0.0.0.0", - 'port': 5001 - }, - '2': { - 'ip': "0.0.0.0", - 'port': 5002 - } - } - } - # call save_conf - self.save_conf() - - def save_conf(self): - try: - with open(_CONFIGURATION_PATH, 'w') as file: - json.dump(self.conf, file, indent=4, sort_keys=True) - file.close() - except IOError as e: - print('{0}'.format(e)) - - def increment_height(self): - # updates the height of the chain in the conf - self.conf["height"] += 1 - self.save_conf() - - def update_previous_hash(self, block_hash): - # updates the height of the chain in the conf - self.conf["last_block"] = block_hash - self.save_conf() - return self.conf - - def get_conf(self, key=None): - """ - returns the value for the matching key in the configuration - :param key: key - :return: value for the key - """ - try: - if key: - return self.conf.get(key) - else: - return self.conf - except KeyError as e: - print("Key was not found: {0}".format(e)) +""" Singleton used to access client config """ +import json +import os + +from Crypto.Hash import SHA256 +from src.wallet import get_public_key + +# private path +_CONFIGURATION_PATH = '{0}/abc.json'.format(os.path.join(os.getcwd(), r'data')) + + +class Singleton(type): + _instances = {} + + def __call__(cls, *args, **kwargs): + if cls not in cls._instances: + cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) + return cls._instances[cls] + + +class Configuration(metaclass=Singleton): + def __init__(self): + """ + 1) check to see if conf appdata exists + 2) if it does load it up then begin sync ( connect, get updates, etc ) + 3) if not, create new public key/wallet, files, connect to some seed peers, sync + """ + self.conf = {} + self.load_conf() + + def load_conf(self): + # loads the config file + try: + with open(_CONFIGURATION_PATH) as file: + # read in the data + self.conf = json.load(file) + file.close() + pass + except IOError as e: + # file does not exist or not able to read file + self.create_conf() + print("Creating new configuration") + + def create_conf(self): + # creates a new config + pubkey = get_public_key("string") + hashed_address = SHA256.new(pubkey.encode()).hexdigest() + + self.conf = { + 'height': 0, + 'last_block': "", + 'version': "00000001", + 'difficulty': 4, + 'reward': 100, + 'wallet': { + 'address': hashed_address, + 'amount': 0 + }, + 'ip': "0.0.0.0", + 'port': "5000", + 'peers': { + '1': { + 'ip': "0.0.0.0", + 'port': 5001 + }, + '2': { + 'ip': "0.0.0.0", + 'port': 5002 + } + } + } + # call save_conf + self.save_conf() + + def save_conf(self): + try: + with open(_CONFIGURATION_PATH, 'w') as file: + json.dump(self.conf, file, indent=4, sort_keys=True) + file.close() + except IOError as e: + print('{0}'.format(e)) + + def increment_height(self): + # updates the height of the chain in the conf + self.conf["height"] += 1 + self.save_conf() + + def update_previous_hash(self, block_hash): + # updates the height of the chain in the conf + self.conf["last_block"] = block_hash + self.save_conf() + return self.conf + + def add_balance(self, new_amount): + """ + Updates the wallet amount, aka the balance, by adding new_amount + :param new_amount: amount to add + :return: None + """ + self.conf["wallet"]["amount"] = self.conf["wallet"]["amount"] + new_amount + self.save_conf() + + def subtract_balance(self, amount): + """ + Subtracts an amount from the balance + :param amount: amount to subtract + :return: None + """ + self.conf["wallet"]["amount"] = self.conf["wallet"]["amount"] - amount + self.save_conf() + + def get_conf(self, key=None): + """ + returns the value for the matching key in the configuration + :param key: key + :return: value for the key + """ + try: + if key: + return self.conf.get(key) + else: + return self.conf + except KeyError as e: + print("Key was not found: {0}".format(e)) diff --git a/src/transaction/transaction.py b/src/transaction/transaction.py index 2f153c2..32c8975 100644 --- a/src/transaction/transaction.py +++ b/src/transaction/transaction.py @@ -1,250 +1,260 @@ -import base64 - -from Crypto.Hash import SHA256 -from Crypto.Signature import DSS - -from src.persist import get_unspent_outputs, find_unspent_output -from src.wallet import import_public_key, get_public_key - - -class Transaction(object): - def __init__(self, **kwargs): - """ - Instantiate a Transaction object from the network, a file, or by - by building a new transaction. - :param **kwargs: arguments passed as key:value pair, can contain: - - :payload: payload is passed in from the network or from a file - and contains a JSON object (Python dict) representation - of a transaction. All information necessary to build a - Transaction object is in the payload object. - - - Otherwise, it is assumed that a new transaction is being created, - and subsequent calls to `Transaction().add_output()` are needed - to fill the transaction, followed by `Transaction().unlock_inputs()` - to complete the new Transaction. - - :notes - - Transaction Input Structure: - [ - { - transaction_id: # hash of previous transaction, - output_index: # index of referenced output, - unlock: { - public_key: # full public key of sender - signature: # signature of this transaction - } - }, - ] - - Transaction Output Structure: - [ - { - address: # hashed public key of recipient, - amount: # amount for this output - }, - ] - - Transaction structure: - id: { - input_count: # how many transaction inputs - inputs: # list of transaction input objects - output_count: # how many transaction outputs - outputs: # list of transaction output objects - } - - """ - - self.payload = kwargs.pop('payload', None) - if self.payload: # un-packing transaction from network or file - self.transaction_id = self.payload['transaction_id'] - self.input_count = self.payload['input_count'] - self.inputs = self.payload['inputs'] - self.output_count = self.payload['output_count'] - self.outputs = self.payload['outputs'] - else: # creating new transaction - self.transaction_id = None - self.input_count = 0 - self.inputs = [] - self.output_count = 0 - self.outputs = [] - self.unused_amount = 0 - - def add_output(self, address, amount): - """ - Add a new output and necessary inputs to this transaction. - Get enough currency by referencing previous unspent transaction outputs - :param address: the public key address recipient - :param amount: the amount to send - :return: None - """ - hash_address = SHA256.new(address.encode('utf-8')).hexdigest() - - if self.unused_amount >= amount: # enough left over to cover output - self.unused_amount -= amount - elif self.unused_amount < amount: # need to find more outputs - find_amount = amount - self.unused_amount - utxos, total = get_unspent_outputs(find_amount) - self.input_count += len(utxos) - self.inputs += utxos - self.unused_amount = total - find_amount - - self.output_count += 1 - self.outputs.append({ # add new output - "address": hash_address, - "amount": amount - }) - - def add_coinbase_output(self, address, amount): - """ - Adds an output to an address without having to verify inputs - :param address: client's public key - :param amount: amount to send - :return: none - """ - hash_address = SHA256.new(address.encode('utf-8')).hexdigest() - - self.output_count += 1 - self.outputs.append({ # add new output - "address": hash_address, - "amount": amount - }) - - def unlock_inputs(self, private_key, public_key): - """ - Unlock previous unspent transaction outputs for a new transaction. - Compose a transaction message consisting of: - - input transaction ID - - input output index - - unspent transaction object's public key script (hashed pubkey) - - this transaction's public key script (s)? - - this transaction's output amount (s)? - and sign it with this node's private key. - - Place a dict consisting of {key: public_key, sig: signature} in the - corresponding input at "unlock" where `public_key` is this node's - full public key and `signature` is the signed transaction message. - """ - if self.unused_amount != 0: # use up all input amounts (change) - hash_address = SHA256.new(public_key.encode('utf-8')).hexdigest() - self.outputs.append({ - "address": hash_address, - "amount": self.unused_amount - }) - self.output_count += 1 - self.unused_amount = 0 - - for tnx_input in self.inputs: # for each input - utxo = find_unspent_output(tnx_input['transaction_id'], - tnx_input['output_index'], - tnx_input['block_hash']) - if utxo: - transaction_message = SHA256.new(( # compose message - str(tnx_input['transaction_id']) + - str(tnx_input['output_index']) + - str(utxo['address']) + # hashed public key address - str(self.outputs) - ).encode('utf-8')) - signer = DSS.new(private_key, 'fips-186-3') - signature = signer.sign(transaction_message) - encoded = base64.b64encode(signature).decode() - unlock = { # create unlocking portion of the transaction - "public_key": public_key, - "signature": encoded - } - tnx_input['unlock'] = unlock # assign to input. - else: - # TODO: raise error - print("Invalid input found for {}".format(tnx_input)) - - def verify(self): - # TODO: Maybe rename this function to authenticate - """ - Verify an incoming transaction. - 1) SHA256 hash the unspent transaction object's address - and SHA256 hash this transaction's signature script key and - see if they match. If they do, continue. Otherwise return false. - - 2) Compose the transaction's message consisting of: - - input transaction ID - - input output index - - unspent transaction object's public key script (hashed pubkey) - - this transaction's public key script (s)? - - this transaction's output amount (s)? - and check to see if this transaction's signature can be - verified by the now authorized signature script key. - - :return: True if the transaction is valid, false otherwise - """ - authentic = True - for tnx_input in self.inputs: # for each referenced input - utxo = find_unspent_output(tnx_input['transaction_id'], - tnx_input['output_index'], - tnx_input['block_hash']) - - sig_key = SHA256.new( # get this transaction's unlock public key - tnx_input['unlock']['public_key'].encode() - ).hexdigest() - - if sig_key == utxo['address']: # if this node is the recipient of - # the previous utxo - transaction_message = SHA256.new(( # transaction message - str(tnx_input['transaction_id']) + # input id - str(tnx_input['output_index']) + # output index - str(utxo['address']) + # hashed public key as address - str(self.outputs) - ).encode('utf-8')) - - ecc_key = import_public_key(tnx_input['unlock']['public_key']) - signature = tnx_input['unlock']['signature'] - decoded = base64.b64decode(signature.encode()) - verifier = DSS.new(ecc_key, 'fips-186-3') - try: - verifier.verify(transaction_message, decoded) - except ValueError: - authentic = False - break - - return authentic - - def get_transaction_id(self): - """ - Calculate the transaction id. - :return: the transaction id of this transaction - """ - transaction = self.get_data() - txid = SHA256.new( - str(transaction).encode() - ).hexdigest() - self.transaction_id = txid - return txid - - def get_data(self): - """ - Get a dict representation of a transaction with the - transaction id as the key - :return: a dict representation of the transaction object. - This would typically be the value in a tx key-value - """ - transaction = { - "input_count": self.input_count, - "inputs": self.inputs, - "output_count": self.output_count, - "outputs": self.outputs - } - return transaction - - -def create_coinbase_tx(reward_amount): - """ - Create a new transaction where the output is the client's public key - Will create a coinbase transaction - :return: new transaction following a coinbase protocol - """ - cbtx = Transaction() - - hashed_address = SHA256.new(get_public_key("string").encode()).hexdigest() - cbtx.add_coinbase_output(hashed_address, reward_amount) +import base64 + +from Crypto.Hash import SHA256 +from Crypto.Signature import DSS + +from src.persist import get_unspent_outputs, find_unspent_output +from src.wallet import import_public_key, get_public_key + + +class Transaction(object): + def __init__(self, **kwargs): + """ + Instantiate a Transaction object from the network, a file, or by + by building a new transaction. + :param **kwargs: arguments passed as key:value pair, can contain: + + :payload: payload is passed in from the network or from a file + and contains a JSON object (Python dict) representation + of a transaction. All information necessary to build a + Transaction object is in the payload object. + + + Otherwise, it is assumed that a new transaction is being created, + and subsequent calls to `Transaction().add_output()` are needed + to fill the transaction, followed by `Transaction().unlock_inputs()` + to complete the new Transaction. + + :notes + + Transaction Input Structure: + [ + { + transaction_id: # hash of previous transaction, + output_index: # index of referenced output, + unlock: { + public_key: # full public key of sender + signature: # signature of this transaction + } + }, + ] + + Transaction Output Structure: + [ + { + address: # hashed public key of recipient, + amount: # amount for this output + }, + ] + + Transaction structure: + id: { + input_count: # how many transaction inputs + inputs: # list of transaction input objects + output_count: # how many transaction outputs + outputs: # list of transaction output objects + } + + """ + + self.payload = kwargs.pop('payload', None) + if self.payload: # un-packing transaction from network or file + self.transaction_id = self.payload['transaction_id'] + self.input_count = self.payload['input_count'] + self.inputs = self.payload['inputs'] + self.output_count = self.payload['output_count'] + self.outputs = self.payload['outputs'] + else: # creating new transaction + self.transaction_id = None + self.input_count = 0 + self.inputs = [] + self.output_count = 0 + self.outputs = [] + self.unused_amount = 0 + + def add_output(self, address, amount): + """ + Add a new output and necessary inputs to this transaction. + Get enough currency by referencing previous unspent transaction outputs + :param address: the public key address recipient + :param amount: the amount to send + :return: None + """ + hash_address = SHA256.new(address.encode('utf-8')).hexdigest() + + if self.unused_amount >= amount: # enough left over to cover output + self.unused_amount -= amount + elif self.unused_amount < amount: # need to find more outputs + find_amount = amount - self.unused_amount + utxos, total = get_unspent_outputs(find_amount) + self.input_count += len(utxos) + self.inputs += utxos + self.unused_amount = total - find_amount + + self.output_count += 1 + self.outputs.append({ # add new output + "address": hash_address, + "amount": amount + }) + + def add_coinbase_output(self, address, amount): + """ + Adds an output to an address without having to verify inputs + :param address: client's public key + :param amount: amount to send + :return: none + """ + hash_address = SHA256.new(address.encode('utf-8')).hexdigest() + + self.output_count += 1 + self.outputs.append({ # add new output + "address": hash_address, + "amount": amount + }) + + def unlock_inputs(self, private_key, public_key): + """ + Unlock previous unspent transaction outputs for a new transaction. + Compose a transaction message consisting of: + - input transaction ID + - input output index + - unspent transaction object's public key script (hashed pubkey) + - this transaction's public key script (s)? + - this transaction's output amount (s)? + and sign it with this node's private key. + + Place a dict consisting of {key: public_key, sig: signature} in the + corresponding input at "unlock" where `public_key` is this node's + full public key and `signature` is the signed transaction message. + """ + if self.unused_amount != 0: # use up all input amounts (change) + hash_address = SHA256.new(public_key.encode('utf-8')).hexdigest() + self.outputs.append({ + "address": hash_address, + "amount": self.unused_amount + }) + self.output_count += 1 + self.unused_amount = 0 + + for tnx_input in self.inputs: # for each input + utxo = find_unspent_output(tnx_input['transaction_id'], + tnx_input['output_index'], + tnx_input['block_hash']) + if utxo: + transaction_message = SHA256.new(( # compose message + str(tnx_input['transaction_id']) + + str(tnx_input['output_index']) + + str(utxo['address']) + # hashed public key address + str(self.outputs) + ).encode('utf-8')) + signer = DSS.new(private_key, 'fips-186-3') + signature = signer.sign(transaction_message) + encoded = base64.b64encode(signature).decode() + unlock = { # create unlocking portion of the transaction + "public_key": public_key, + "signature": encoded + } + tnx_input['unlock'] = unlock # assign to input. + else: + # TODO: raise error + print("Invalid input found for {}".format(tnx_input)) + + def verify(self): + # TODO: Maybe rename this function to authenticate + """ + Verify an incoming transaction. + 1) SHA256 hash the unspent transaction object's address + and SHA256 hash this transaction's signature script key and + see if they match. If they do, continue. Otherwise return false. + + 2) Compose the transaction's message consisting of: + - input transaction ID + - input output index + - unspent transaction object's public key script (hashed pubkey) + - this transaction's public key script (s)? + - this transaction's output amount (s)? + and check to see if this transaction's signature can be + verified by the now authorized signature script key. + + :return: True if the transaction is valid, false otherwise + """ + authentic = True + for tnx_input in self.inputs: # for each referenced input + utxo = find_unspent_output(tnx_input['transaction_id'], + tnx_input['output_index'], + tnx_input['block_hash']) + + sig_key = SHA256.new( # get this transaction's unlock public key + tnx_input['unlock']['public_key'].encode() + ).hexdigest() + + if sig_key == utxo['address']: # if this node is the recipient of + # the previous utxo + transaction_message = SHA256.new(( # transaction message + str(tnx_input['transaction_id']) + # input id + str(tnx_input['output_index']) + # output index + str(utxo['address']) + # hashed public key as address + str(self.outputs) + ).encode('utf-8')) + + ecc_key = import_public_key(tnx_input['unlock']['public_key']) + signature = tnx_input['unlock']['signature'] + decoded = base64.b64decode(signature.encode()) + verifier = DSS.new(ecc_key, 'fips-186-3') + try: + verifier.verify(transaction_message, decoded) + except ValueError: + authentic = False + break + + return authentic + + def get_transaction_id(self): + """ + Calculate the transaction id. + :return: the transaction id of this transaction + """ + transaction = self.get_data() + txid = SHA256.new( + str(transaction).encode() + ).hexdigest() + self.transaction_id = txid + return txid + + def get_data(self): + """ + Get a dict representation of a transaction with the + transaction id as the key + :return: a dict representation of the transaction object. + This would typically be the value in a tx key-value + """ + transaction = { + "input_count": self.input_count, + "inputs": self.inputs, + "output_count": self.output_count, + "outputs": self.outputs + } + return transaction + + def sum_of_outputs(self): + """ + Calculates the sum of all outputs + :return: Double + """ + total = 0 + + for output in self.outputs: + total = total + output["amount"] + return total + +def create_coinbase_tx(reward_amount): + """ + Create a new transaction where the output is the client's public key + Will create a coinbase transaction + :return: new transaction following a coinbase protocol + """ + cbtx = Transaction() + + hashed_address = SHA256.new(get_public_key("string").encode()).hexdigest() + cbtx.add_coinbase_output(hashed_address, reward_amount) return cbtx \ No newline at end of file From 02956790c662da330bcd9d449d6ad8b8ec6bf6cc Mon Sep 17 00:00:00 2001 From: Lee Date: Tue, 5 Dec 2017 19:04:07 -0600 Subject: [PATCH 10/11] api --- Pipfile | 12 ++++++++++++ src/core/api.py | 3 ++- 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 Pipfile diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..5058a55 --- /dev/null +++ b/Pipfile @@ -0,0 +1,12 @@ +[[source]] + +name = "pypi" +url = "https://pypi.python.org/simple" +verify_ssl = true + + +[dev-packages] + + + +[packages] diff --git a/src/core/api.py b/src/core/api.py index 41dcc1c..3a678e6 100644 --- a/src/core/api.py +++ b/src/core/api.py @@ -28,7 +28,7 @@ def mine(): conf.update_previous_hash(b.block_hash()) # TODO: create new transmit with mined block network.transmit(b, "block") - + def verify_block(b): # need to check txns conf = Configuration() @@ -51,6 +51,7 @@ def create_transaction(recipient, amount): tx.unlock_inputs(get_private_key(), get_public_key("string")) save_verified_transaction(tx.get_transaction_id(), tx.get_data()) network.transmit(tx.get_data(), "txn") + except ValueError as e: # Will raise if insufficient utxos are found raise ValueError("INSUFFICIENT FUNDS") From 7f308c93de5f3d0b5d1e8581b6e5c238fa944a66 Mon Sep 17 00:00:00 2001 From: kevintaing86 Date: Tue, 5 Dec 2017 19:04:31 -0600 Subject: [PATCH 11/11] merged master into network --- README.md | 12 ++++++++++++ src/configuration/configuration.py | 18 ++++++++++++++++++ src/transaction/transaction.py | 10 ++++++++++ 3 files changed, 40 insertions(+) diff --git a/README.md b/README.md index 8122a9f..2163474 100644 --- a/README.md +++ b/README.md @@ -7,3 +7,15 @@ The core functions we will discuss, explore and develop are the following: 3. Communication with other nodes 4. Proof of work algorithms 5. Transactions + + +### Docker + +```bash +$ docker build -t abc . +``` +```bash +$ docker run -it --rm -p 80:5000 abc +$ docker run -it --rm -p 80:5001 abc +$ docker run -it --rm -p 80:5002 abc +``` diff --git a/src/configuration/configuration.py b/src/configuration/configuration.py index 062171c..7a65d3d 100644 --- a/src/configuration/configuration.py +++ b/src/configuration/configuration.py @@ -91,6 +91,24 @@ def update_previous_hash(self, block_hash): self.save_conf() return self.conf + def add_balance(self, new_amount): + """ + Updates the wallet amount, aka the balance, by adding new_amount + :param new_amount: amount to add + :return: None + """ + self.conf["wallet"]["amount"] = self.conf["wallet"]["amount"] + new_amount + self.save_conf() + + def subtract_balance(self, amount): + """ + Subtracts an amount from the balance + :param amount: amount to subtract + :return: None + """ + self.conf["wallet"]["amount"] = self.conf["wallet"]["amount"] - amount + self.save_conf() + def get_conf(self, key=None): """ returns the value for the matching key in the configuration diff --git a/src/transaction/transaction.py b/src/transaction/transaction.py index 2f153c2..d66685a 100644 --- a/src/transaction/transaction.py +++ b/src/transaction/transaction.py @@ -236,6 +236,16 @@ def get_data(self): } return transaction + def sum_of_outputs(self): + """ + Calculates the sum of all outputs + :return: Double + """ + total = 0 + + for output in self.outputs: + total = total + output["amount"] + return total def create_coinbase_tx(reward_amount): """