diff --git a/Pipfile b/Pipfile index bb66cbe1..c2bd65af 100644 --- a/Pipfile +++ b/Pipfile @@ -5,6 +5,9 @@ verify_ssl = true [dev-packages] flake8 = "*" +black = "*" +pylint = "*" +pytest = "*" [packages] requests = ">=2.20.0" @@ -12,3 +15,6 @@ flask = ">=1.0.0" [requires] python_version = "3.7" + +[pipenv] +allow_prereleases = true diff --git a/Pipfile.lock b/Pipfile.lock index c6bb80f0..773145cf 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "1cfc261928838be069088d094d13b664d7ff98667a8a9b432e94c3241ab5ad6c" + "sha256": "9322e065a462171ee8102857710a22ad26d428950d15824c1a189dddf7791f45" }, "pipfile-spec": 6, "requires": { @@ -18,10 +18,10 @@ "default": { "certifi": { "hashes": [ - "sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3", - "sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f" + "sha256:1d987a998c75633c40847cc966fcf5904906c920a7f17ef374f5aa4282abd304", + "sha256:51fcb31174be6e6664c5f69e3e1691a2d72a1a12e90f872cbdb1567eb47b6519" ], - "version": "==2019.11.28" + "version": "==2020.4.5.1" }, "chardet": { "hashes": [ @@ -39,11 +39,11 @@ }, "flask": { "hashes": [ - "sha256:13f9f196f330c7c2c5d7a5cf91af894110ca0215ac051b5844701f2bfd934d52", - "sha256:45eb5a6fd193d6cf7e0cf5d8a5b31f83d5faae0293695626f539a823e93b13f6" + "sha256:4efa1ae2d7c9865af48986de8aeb8504bf32c7f3d6fdc9353d34b21f4b127060", + "sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557" ], "index": "pypi", - "version": "==1.1.1" + "version": "==1.1.2" }, "idna": { "hashes": [ @@ -61,10 +61,10 @@ }, "jinja2": { "hashes": [ - "sha256:93187ffbc7808079673ef52771baa950426fd664d3aad1d0fa3e95644360e250", - "sha256:b0eaf100007721b5c16c1fc1eecb87409464edc10469ddc9a22a27a99123be49" + "sha256:c10142f819c2d22bdcd17548c46fa9b77cf4fda45097854c689666bf425e7484", + "sha256:c922560ac46888d47384de1dbdc3daaa2ea993af4b26a436dec31fa2c19ec668" ], - "version": "==2.11.1" + "version": "==3.0.0a1" }, "markupsafe": { "hashes": [ @@ -128,6 +128,58 @@ } }, "develop": { + "appdirs": { + "hashes": [ + "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92", + "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e" + ], + "version": "==1.4.3" + }, + "astroid": { + "hashes": [ + "sha256:71ea07f44df9568a75d0f354c49143a4575d90645e9fead6dfb52c26a85ed13a", + "sha256:840947ebfa8b58f318d42301cf8c0a20fd794a33b61cc4638e28e9e61ba32f42" + ], + "version": "==2.3.3" + }, + "atomicwrites": { + "hashes": [ + "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4", + "sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6" + ], + "markers": "sys_platform == 'win32'", + "version": "==1.3.0" + }, + "attrs": { + "hashes": [ + "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c", + "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72" + ], + "version": "==19.3.0" + }, + "black": { + "hashes": [ + "sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b", + "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539" + ], + "index": "pypi", + "version": "==19.10b0" + }, + "click": { + "hashes": [ + "sha256:8a18b4ea89d8820c5d0c7da8a64b2c324b4dabb695804dbfea19b9be9d88c0cc", + "sha256:e345d143d80bf5ee7534056164e5e112ea5e22716bbb1ce727941f4c8b471b9a" + ], + "version": "==7.1.1" + }, + "colorama": { + "hashes": [ + "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff", + "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1" + ], + "markers": "sys_platform == 'win32'", + "version": "==0.4.3" + }, "entrypoints": { "hashes": [ "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19", @@ -143,6 +195,47 @@ "index": "pypi", "version": "==3.7.9" }, + "importlib-metadata": { + "hashes": [ + "sha256:2a688cbaa90e0cc587f1df48bdc97a6eadccdcd9c35fb3f976a09e3b5016d90f", + "sha256:34513a8a0c4962bc66d35b359558fd8a5e10cd472d37aec5f66858addef32c1e" + ], + "markers": "python_version < '3.8'", + "version": "==1.6.0" + }, + "isort": { + "hashes": [ + "sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1", + "sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd" + ], + "version": "==4.3.21" + }, + "lazy-object-proxy": { + "hashes": [ + "sha256:0c4b206227a8097f05c4dbdd323c50edf81f15db3b8dc064d08c62d37e1a504d", + "sha256:194d092e6f246b906e8f70884e620e459fc54db3259e60cf69a4d66c3fda3449", + "sha256:1be7e4c9f96948003609aa6c974ae59830a6baecc5376c25c92d7d697e684c08", + "sha256:4677f594e474c91da97f489fea5b7daa17b5517190899cf213697e48d3902f5a", + "sha256:48dab84ebd4831077b150572aec802f303117c8cc5c871e182447281ebf3ac50", + "sha256:5541cada25cd173702dbd99f8e22434105456314462326f06dba3e180f203dfd", + "sha256:59f79fef100b09564bc2df42ea2d8d21a64fdcda64979c0fa3db7bdaabaf6239", + "sha256:8d859b89baf8ef7f8bc6b00aa20316483d67f0b1cbf422f5b4dc56701c8f2ffb", + "sha256:9254f4358b9b541e3441b007a0ea0764b9d056afdeafc1a5569eee1cc6c1b9ea", + "sha256:9651375199045a358eb6741df3e02a651e0330be090b3bc79f6d0de31a80ec3e", + "sha256:97bb5884f6f1cdce0099f86b907aa41c970c3c672ac8b9c8352789e103cf3156", + "sha256:9b15f3f4c0f35727d3a0fba4b770b3c4ebbb1fa907dbcc046a1d2799f3edd142", + "sha256:a2238e9d1bb71a56cd710611a1614d1194dc10a175c1e08d75e1a7bcc250d442", + "sha256:a6ae12d08c0bf9909ce12385803a543bfe99b95fe01e752536a60af2b7797c62", + "sha256:ca0a928a3ddbc5725be2dd1cf895ec0a254798915fb3a36af0964a0a4149e3db", + "sha256:cb2c7c57005a6804ab66f106ceb8482da55f5314b7fcb06551db1edae4ad1531", + "sha256:d74bb8693bf9cf75ac3b47a54d716bbb1a92648d5f781fc799347cfc95952383", + "sha256:d945239a5639b3ff35b70a88c5f2f491913eb94871780ebfabb2568bd58afc5a", + "sha256:eba7011090323c1dadf18b3b689845fd96a61ba0a1dfbd7f24b921398affc357", + "sha256:efa1909120ce98bbb3777e8b6f92237f5d5c8ea6758efea36a473e1d38f7d3e4", + "sha256:f3900e8a5de27447acbf900b4750b0ddfd7ec1ea7fbaf11dfa911141bc522af0" + ], + "version": "==1.4.3" + }, "mccabe": { "hashes": [ "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", @@ -150,6 +243,41 @@ ], "version": "==0.6.1" }, + "more-itertools": { + "hashes": [ + "sha256:5dd8bcf33e5f9513ffa06d5ad33d78f31e1931ac9a18f33d37e77a180d393a7c", + "sha256:b1ddb932186d8a6ac451e1d95844b382f55e12686d51ca0c68b6f61f2ab7a507" + ], + "version": "==8.2.0" + }, + "packaging": { + "hashes": [ + "sha256:3c292b474fda1671ec57d46d739d072bfd495a4f51ad01a055121d81e952b7a3", + "sha256:82f77b9bee21c1bafbf35a84905d604d5d1223801d639cf3ed140bd651c08752" + ], + "version": "==20.3" + }, + "pathspec": { + "hashes": [ + "sha256:163b0632d4e31cef212976cf57b43d9fd6b0bac6e67c26015d611a647d5e7424", + "sha256:562aa70af2e0d434367d9790ad37aed893de47f1693e4201fd1d3dca15d19b96" + ], + "version": "==0.7.0" + }, + "pluggy": { + "hashes": [ + "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0", + "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d" + ], + "version": "==0.13.1" + }, + "py": { + "hashes": [ + "sha256:5e27081401262157467ad6e7f851b7aa402c5852dbcb3dae06768434de5752aa", + "sha256:c20fdd83a5dbc0af9efd622bee9a5564e278f6380fffcacc43ba6f43db2813b0" + ], + "version": "==1.8.1" + }, "pycodestyle": { "hashes": [ "sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56", @@ -163,6 +291,116 @@ "sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2" ], "version": "==2.1.1" + }, + "pylint": { + "hashes": [ + "sha256:3db5468ad013380e987410a8d6956226963aed94ecb5f9d3a28acca6d9ac36cd", + "sha256:886e6afc935ea2590b462664b161ca9a5e40168ea99e5300935f6591ad467df4" + ], + "index": "pypi", + "version": "==2.4.4" + }, + "pyparsing": { + "hashes": [ + "sha256:67199f0c41a9c702154efb0e7a8cc08accf830eb003b4d9fa42c4059002e2492", + "sha256:700d17888d441604b0bd51535908dcb297561b040819cccde647a92439db5a2a" + ], + "version": "==3.0.0a1" + }, + "pytest": { + "hashes": [ + "sha256:0e5b30f5cb04e887b91b1ee519fa3d89049595f428c1db76e73bd7f17b09b172", + "sha256:84dde37075b8805f3d1f392cc47e38a0e59518fb46a431cfdaf7cf1ce805f970" + ], + "index": "pypi", + "version": "==5.4.1" + }, + "regex": { + "hashes": [ + "sha256:08119f707f0ebf2da60d2f24c2f39ca616277bb67ef6c92b72cbf90cbe3a556b", + "sha256:0ce9537396d8f556bcfc317c65b6a0705320701e5ce511f05fc04421ba05b8a8", + "sha256:1cbe0fa0b7f673400eb29e9ef41d4f53638f65f9a2143854de6b1ce2899185c3", + "sha256:2294f8b70e058a2553cd009df003a20802ef75b3c629506be20687df0908177e", + "sha256:23069d9c07e115537f37270d1d5faea3e0bdded8279081c4d4d607a2ad393683", + "sha256:24f4f4062eb16c5bbfff6a22312e8eab92c2c99c51a02e39b4eae54ce8255cd1", + "sha256:295badf61a51add2d428a46b8580309c520d8b26e769868b922750cf3ce67142", + "sha256:2a3bf8b48f8e37c3a40bb3f854bf0121c194e69a650b209628d951190b862de3", + "sha256:4385f12aa289d79419fede43f979e372f527892ac44a541b5446617e4406c468", + "sha256:5635cd1ed0a12b4c42cce18a8d2fb53ff13ff537f09de5fd791e97de27b6400e", + "sha256:5bfed051dbff32fd8945eccca70f5e22b55e4148d2a8a45141a3b053d6455ae3", + "sha256:7e1037073b1b7053ee74c3c6c0ada80f3501ec29d5f46e42669378eae6d4405a", + "sha256:90742c6ff121a9c5b261b9b215cb476eea97df98ea82037ec8ac95d1be7a034f", + "sha256:a58dd45cb865be0ce1d5ecc4cfc85cd8c6867bea66733623e54bd95131f473b6", + "sha256:c087bff162158536387c53647411db09b6ee3f9603c334c90943e97b1052a156", + "sha256:c162a21e0da33eb3d31a3ac17a51db5e634fc347f650d271f0305d96601dc15b", + "sha256:c9423a150d3a4fc0f3f2aae897a59919acd293f4cb397429b120a5fcd96ea3db", + "sha256:ccccdd84912875e34c5ad2d06e1989d890d43af6c2242c6fcfa51556997af6cd", + "sha256:e91ba11da11cf770f389e47c3f5c30473e6d85e06d7fd9dcba0017d2867aab4a", + "sha256:ea4adf02d23b437684cd388d557bf76e3afa72f7fed5bbc013482cc00c816948", + "sha256:fb95debbd1a824b2c4376932f2216cc186912e389bdb0e27147778cf6acb3f89" + ], + "version": "==2020.4.4" + }, + "six": { + "hashes": [ + "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a", + "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c" + ], + "version": "==1.14.0" + }, + "toml": { + "hashes": [ + "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c", + "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e" + ], + "version": "==0.10.0" + }, + "typed-ast": { + "hashes": [ + "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355", + "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919", + "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa", + "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652", + "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75", + "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01", + "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d", + "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1", + "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907", + "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c", + "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3", + "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b", + "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614", + "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb", + "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b", + "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41", + "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6", + "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34", + "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe", + "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4", + "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7" + ], + "markers": "implementation_name == 'cpython' and python_version < '3.8'", + "version": "==1.4.1" + }, + "wcwidth": { + "hashes": [ + "sha256:cafe2186b3c009a04067022ce1dcd79cb38d8d65ee4f4791b8888d6599d1bbe1", + "sha256:ee73862862a156bf77ff92b09034fc4825dd3af9cf81bc5b360668d425f3c5f1" + ], + "version": "==0.1.9" + }, + "wrapt": { + "hashes": [ + "sha256:565a021fd19419476b9362b05eeaa094178de64f8361e44468f9e9d7843901e1" + ], + "version": "==1.11.2" + }, + "zipp": { + "hashes": [ + "sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b", + "sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96" + ], + "version": "==3.1.0" } } } diff --git a/basic_block_gp/blockchain.py b/basic_block_gp/blockchain.py index 54ead5c1..f88d176e 100644 --- a/basic_block_gp/blockchain.py +++ b/basic_block_gp/blockchain.py @@ -30,14 +30,28 @@ def new_block(self, proof, previous_hash=None): :return: New Block """ + if len(self.chain) > 0: + block_string = json.dumps(self.last_block, sort_keys=True) + guess = f'{block_string}{proof}'.encode() + current_hash = hashlib.sha256(guess).hexdigest() + else: + current_hash = "" + block = { - # TODO + 'index': len(self.chain) + 1, + 'timestamp': time(), + 'transactions': self.current_transactions, + 'proof': proof, + 'previous_hash': previous_hash or self.hash(self.chain[-1]), + 'hash': current_hash, } # Reset the current list of transactions + self.current_transactions = [] # Append the chain to the block + self.chain.append(block) # Return the new block - pass + return block def hash(self, block): """ @@ -56,8 +70,12 @@ def hash(self, block): # or we'll have inconsistent hashes # TODO: Create the block_string + string_object = json.dumps(block, sort_keys=True) + block_string = string_object.encode() # TODO: Hash this string using sha256 + raw_hash = hashlib.sha256(block_string) + hex_hash = raw_hash.hexdigest() # By itself, the sha256 function returns the hash in a raw string # that will likely include escaped characters. @@ -66,13 +84,13 @@ def hash(self, block): # easier to work with and understand # TODO: Return the hashed block string in hexadecimal format - pass + return hex_hash @property def last_block(self): return self.chain[-1] - def proof_of_work(self, block): + def proof_of_work(self): """ Simple Proof of Work Algorithm Stringify the block and look for a proof. @@ -80,9 +98,11 @@ def proof_of_work(self, block): in an effort to find a number that is a valid proof :return: A valid proof for the provided block """ - # TODO - pass - # return proof + block_string = json.dumps(self.last_block, sort_keys=True) + proof = 0 + while not self.valid_proof(block_string, proof): + proof += 1 + return proof @staticmethod def valid_proof(block_string, proof): @@ -96,9 +116,9 @@ def valid_proof(block_string, proof): correct number of leading zeroes. :return: True if the resulting hash is a valid proof, False otherwise """ - # TODO - pass - # return True or False + guess = f"{block_string}{proof}".encode() + guess_hash = hashlib.sha256(guess).hexdigest() + return guess_hash[:4] == "0000" # Instantiate our Node @@ -114,11 +134,18 @@ def valid_proof(block_string, proof): @app.route('/mine', methods=['GET']) def mine(): # Run the proof of work algorithm to get the next proof + proof = blockchain.proof_of_work() # Forge the new Block by adding it to the chain with the proof + previous_hash = blockchain.hash(blockchain.last_block) + block = blockchain.new_block(proof, previous_hash) response = { - # TODO: Send a JSON response with the new block + 'message': "New Block Forged", + 'index': block['index'], + 'transactions': block['transactions'], + 'proof': block['proof'], + 'previous_hash': block['previous_hash'], } return jsonify(response), 200 @@ -127,7 +154,8 @@ def mine(): @app.route('/chain', methods=['GET']) def full_chain(): response = { - # TODO: Return the chain and its current length + 'length': len(blockchain.chain), + 'chain': blockchain.chain } return jsonify(response), 200 diff --git a/basic_transactions_gp/blockchain.py b/basic_transactions_gp/blockchain.py index 5a2c7e5a..4b8d0e21 100644 --- a/basic_transactions_gp/blockchain.py +++ b/basic_transactions_gp/blockchain.py @@ -1,2 +1,228 @@ -# Paste your version of blockchain.py from the client_mining_p -# folder here +import hashlib +import json +from time import time +from uuid import uuid4 + +from flask import Flask, jsonify, request + + +class Blockchain(object): + def __init__(self): + self.chain = [] + self.current_transactions = [] + + # Create the genesis block + self.new_block(previous_hash=1, proof=100) + + def new_block(self, proof, previous_hash=None): + """ + Create a new Block in the Blockchain + + A block should have: + * Index + * Timestamp + * List of current transactions + * The proof used to mine this block + * The hash of the previous block + + :param proof: The proof given by the Proof of Work algorithm + :param previous_hash: (Optional) Hash of previous Block + :return: New Block + """ + + if len(self.chain) > 0: + block_string = json.dumps(self.last_block, sort_keys=True) + guess = f'{block_string}{proof}'.encode() + current_hash = hashlib.sha256(guess).hexdigest() + else: + current_hash = "" + + block = { + 'index': len(self.chain) + 1, + 'timestamp': time(), + 'transactions': self.current_transactions, + 'proof': proof, + 'previous_hash': previous_hash or self.hash(self.chain[-1]), + 'hash': current_hash, + } + + # Reset the current list of transactions + self.current_transactions = [] + # Append the chain to the block + self.chain.append(block) + # Return the new block + return block + + def new_transaction(self, sender, recipient, amount): + """ + Creates a new transaction to go into the next mined Block + :param sender: Address of the Recipient + :param recipient: Address of the Recipient + :param amount: Amount + :return: The index of the Block that will hold this transaction + """ + self.current_transactions.append({ + 'sender': sender, + 'recipient': recipient, + 'amount': amount + }) + return self.last_block['index'] + 1 + + def hash(self, block): + """ + Creates a SHA-256 hash of a Block + + :param block": Block + "return": + """ + + # Use json.dumps to convert json into a string + # Use hashlib.sha256 to create a hash + # It requires a `bytes-like` object, which is what + # .encode() does. + # It converts the Python string into a byte string. + # We must make sure that the Dictionary is Ordered, + # or we'll have inconsistent hashes + + # TODO: Create the block_string + string_object = json.dumps(block, sort_keys=True) + block_string = string_object.encode() + + # TODO: Hash this string using sha256 + raw_hash = hashlib.sha256(block_string) + hex_hash = raw_hash.hexdigest() + + # By itself, the sha256 function returns the hash in a raw string + # that will likely include escaped characters. + # This can be hard to read, but .hexdigest() converts the + # hash to a string of hexadecimal characters, which is + # easier to work with and understand + + # TODO: Return the hashed block string in hexadecimal format + return hex_hash + + @property + def last_block(self): + return self.chain[-1] + + # def proof_of_work(self, block, proof): + # """ + # Simple Proof of Work Algorithm + # Stringify the block and look for a proof. + # Loop through possibilities, checking each one against `valid_proof` + # in an effort to find a number that is a valid proof + # :return: A valid proof for the provided block + # """ + # block_string = json.dumps(self.last_block, sort_keys=True) + # proof = 0 + # while not self.valid_proof(block_string, proof): + # proof += 1 + # return proof + + @staticmethod + def valid_proof(block_string, proof): + """ + Validates the Proof: Does hash(block_string, proof) contain 6 + leading zeroes? Return true if the proof is valid + :param block_string: The stringified block to use to + check in combination with `proof` + :param proof: The value that when combined with the + stringified previous block results in a hash that has the + correct number of leading zeroes. + :return: True if the resulting hash is a valid proof, False otherwise + """ + guess = f"{block_string}{proof}".encode() + guess_hash = hashlib.sha256(guess).hexdigest() + return guess_hash[:6] == "000000" + + +# Instantiate our Node +app = Flask(__name__) + +# Generate a globally unique address for this node +node_identifier = str(uuid4()).replace('-', '') + +# Instantiate the Blockchain +blockchain = Blockchain() + + +@app.route('/mine', methods=['POST']) +def mine(): + # Run the proof of work algorithm to get the next proof + # proof = blockchain.proof_of_work() + + # TODO: GET PROOF FROM CLIENT + # data is a dictionary with the POST variables + data = request.get_json() + + # Check that 'proof', and 'id' are present + if 'proof' not in data or 'id' not in data: + response = {'message': 'Must contain "proof" and "id"'} + return jsonify(response), 400 + + proof = data['proof'] + + # Determine if the proof is valid + last_block = blockchain.last_block + last_block_string = json.dumps(last_block, sort_keys=True) + + if blockchain.valid_proof(last_block_string, proof): + blockchain.new_transaction(sender="0", recipient=data["id"].strip(), amount=1) + + # Forge the new Block by adding it to the chain with the proof + previous_hash = blockchain.hash(last_block) + block = blockchain.new_block(proof, previous_hash) + + + response = { + 'message': "New Block Forged", + 'index': block['index'], + 'transactions': block['transactions'], + 'proof': block['proof'], + 'previous_hash': block['previous_hash'], + } + return jsonify(response), 200 + else: + response = {'message': 'Invalid proof'} + return jsonify(response), 200 + + +@app.route('/chain', methods=['GET']) +def full_chain(): + response = { + 'length': len(blockchain.chain), + 'chain': blockchain.chain + } + return jsonify(response), 200 + + +@app.route('/last_block', methods=['GET']) +def get_last_block(): + response = { + 'last_block': blockchain.last_block + } + return jsonify(response), 200 + + +@app.route('/transactions/new', methods=['POST']) +def new_transaction(): + values = request.get_json() + + required = ['sender', 'recipient', 'amount'] + if not all(k in values for k in required): + return 'Missing Values', 400 + + index = blockchain.new_transaction(values["sender"], + values["recipient"], + values["amount"]) + + response = {'message': f"Transaction will be added to Block {index}"} + return jsonify(response), 200 + + + + + +# Run the program on port 5000 +if __name__ == '__main__': + app.run(host='0.0.0.0', port=5000) diff --git a/basic_transactions_gp/read.txt b/basic_transactions_gp/read.txt new file mode 100644 index 00000000..d0383e51 --- /dev/null +++ b/basic_transactions_gp/read.txt @@ -0,0 +1,9 @@ +Explain in detail the workings of a dynamic array: +What is the runtime complexity to access an array, add or remove from the front, +and add or remove from the back? +What is the worse case scenario if you try to extend the storage size of a dynamic array? +Explain how a blockchain is structured. What are the blocks, what is the chain? +How is the data organized? +Explain how proof of work functions. +How does it operate. +How does this protect the chain from attack. What kind of attack is possible? \ No newline at end of file diff --git a/basic_wallet_p/basic_wallet.py b/basic_wallet_p/basic_wallet.py new file mode 100644 index 00000000..0cee8b36 --- /dev/null +++ b/basic_wallet_p/basic_wallet.py @@ -0,0 +1,74 @@ +# Account balances in a blockchain currency are not real values that are stored somewhere. +# Instead, wallet programs derive this balance +# by adding and subtracting all of the transactions +# for the user that are recorded in the ledger, +# to calculate the current balance. + +# Build a simple wallet app using the front-end technology of your choice. +# You will not be evaluated on the aesthetics of your app. + +# This app should: +# * Allow the user to enter, save, or change the `id` used for the program +# * Display the current balance for that user +# * Display a list of all transactions for this user, including sender and recipient + +import hashlib +import requests + +import sys +import json +from flask import Flask, jsonify, request + + + + # check for data +def check_for_data(data): + try: + data = data.json() + except ValueError: + print(jsonify({'message':'please input your proof'})) + return + return data + +def get_transactions(): + r = requests.get(url=node +"/transactions") + data = check_for_data(r) + if data: + return data['transactions'] + transactions = data['transactions'] + print(f'{transactions.sender}, {transactions.recepient}, {transactions.ammount}') + +def get_my_transactions(id): + transactions = get_transactions() + if transactions: + my_transactions = [] + for action in transactions: + if action['recepient'] == id: + my_transactions.append(action) + elif action['sender'] == id: + my_transactions.append(action) + return my_transactions + else: + print("something went wrong") + +def get_my_balance(id): + my_transactions = get_my_transactions(id) + coins = 0 + for action in my_transactions: + if action["recepient"] == id: + coins += action['amount'] + elif action['sender'] == id: + coins += action['amount'] + print(f'{id}:{coins}') +if __name__ == '__main__': + # What is the server address? IE `python3 miner.py https://server.com/api/` + if len(sys.argv) > 1: + node = sys.argv[1] + else: + node = "http://localhost:5000" + +print(get_transactions()) +print(get_my_transactions("albert-yakubov")) +print(get_my_balance("albert-yakubov")) + + \ No newline at end of file diff --git a/client_mining_p/blockchain.py b/client_mining_p/blockchain.py index a0a26551..1adc3ac2 100644 --- a/client_mining_p/blockchain.py +++ b/client_mining_p/blockchain.py @@ -1,2 +1,181 @@ -# Paste your version of blockchain.py from the basic_block_gp -# folder here +import hashlib +import json +from time import time +from uuid import uuid4 + +from flask import Flask, jsonify, request + + +class Blockchain(object): + def __init__(self): + self.chain = [] + self.current_transactions = [] + + # Create the genesis block + self.new_block(previous_hash=1, proof=100) + + def new_block(self, proof, previous_hash=None): + """ + Create a new Block in the Blockchain + + A block should have: + * Index + * Timestamp + * List of current transactions + * The proof used to mine this block + * The hash of the previous block + + :param proof: The proof given by the Proof of Work algorithm + :param previous_hash: (Optional) Hash of previous Block + :return: New Block + """ + + if len(self.chain) > 0: + block_string = json.dumps(self.last_block, sort_keys=True) + guess = f'{block_string}{proof}'.encode() + current_hash = hashlib.sha256(guess).hexdigest() + else: + current_hash = "" + + block = { + 'index': len(self.chain) + 1, + 'timestamp': time(), + 'transactions': self.current_transactions, + 'proof': proof, + 'previous_hash': previous_hash or self.hash(self.chain[-1]), + 'hash': current_hash, + } + + # Reset the current list of transactions + self.current_transactions = [] + # Append the chain to the block + self.chain.append(block) + # Return the new block + return block + + def hash(self, block): + """ + Creates a SHA-256 hash of a Block + + :param block": Block + "return": + """ + + # Use json.dumps to convert json into a string + # Use hashlib.sha256 to create a hash + # It requires a `bytes-like` object, which is what + # .encode() does. + # It converts the Python string into a byte string. + # We must make sure that the Dictionary is Ordered, + # or we'll have inconsistent hashes + + # TODO: Create the block_string + string_object = json.dumps(block, sort_keys=True) + block_string = string_object.encode() + + # TODO: Hash this string using sha256 + raw_hash = hashlib.sha256(block_string) + hex_hash = raw_hash.hexdigest() + + # By itself, the sha256 function returns the hash in a raw string + # that will likely include escaped characters. + # This can be hard to read, but .hexdigest() converts the + # hash to a string of hexadecimal characters, which is + # easier to work with and understand + + # TODO: Return the hashed block string in hexadecimal format + return hex_hash + + @property + def last_block(self): + return self.chain[-1] + + # def proof_of_work(self, block, proof): + # """ + # Simple Proof of Work Algorithm + # Stringify the block and look for a proof. + # Loop through possibilities, checking each one against `valid_proof` + # in an effort to find a number that is a valid proof + # :return: A valid proof for the provided block + # """ + # block_string = json.dumps(self.last_block, sort_keys=True) + # proof = 0 + # while not self.valid_proof(block_string, proof): + # proof += 1 + # return proof + + @staticmethod + def valid_proof(block_string, proof): + """ + Validates the Proof: Does hash(block_string, proof) contain 3 + leading zeroes? Return true if the proof is valid + :param block_string: The stringified block to use to + check in combination with `proof` + :param proof: The value that when combined with the + stringified previous block results in a hash that has the + correct number of leading zeroes. + :return: True if the resulting hash is a valid proof, False otherwise + """ + guess = f"{block_string}{proof}".encode() + guess_hash = hashlib.sha256(guess).hexdigest() + return guess_hash[:6] == "000000" + + +# Instantiate our Node +app = Flask(__name__) + +# Generate a globally unique address for this node +node_identifier = str(uuid4()).replace('-', '') + +# Instantiate the Blockchain +blockchain = Blockchain() + + +@app.route('/mine', methods=['POST']) +def mine(): + data = request.get_json() + guess = data['proof'] + user_id = data['id'] + if user_id == None: + return jsonify({'message':'please input your id'}) , 400 + # Run the proof of work algorithm to get the next proof + # proof = blockchain.proof_of_work(blockchain.last_block, guess) + proof = data['proof'] + if proof == None: + return jsonify({'message':'please input your proof'}) , 400 + + + # Forge the new Block by adding it to the chain with the proof + previous_hash = blockchain.hash(blockchain.last_block) + block = blockchain.new_block(proof, previous_hash) + + response = { + 'message': "New Block Forged", + 'index': block['index'], + 'transactions': block['transactions'], + 'proof': block['proof'], + 'previous_hash': block['previous_hash'], + } + + return jsonify(response), 200 + + +@app.route('/chain', methods=['GET']) +def full_chain(): + response = { + 'length': len(blockchain.chain), + 'chain': blockchain.chain + } + return jsonify(response), 200 + +@app.route('/last_block', methods=['GET']) +def last_block(): + response = { + 'last_block': blockchain.last_block + } + return jsonify(response), 200 + + +# Run the program on port 5000 +if __name__ == '__main__': + app.run(host='0.0.0.0', port=5000) diff --git a/client_mining_p/blockchain2.py b/client_mining_p/blockchain2.py new file mode 100644 index 00000000..cab8897d --- /dev/null +++ b/client_mining_p/blockchain2.py @@ -0,0 +1,238 @@ +import hashlib +import json +from time import time +from uuid import uuid4 + +from flask import Flask, jsonify, request + + +class Blockchain(object): + def __init__(self): + self.chain = [] + self.current_transactions = [] + + # Create the genesis block + self.new_block(previous_hash=1, proof=100) + + def new_block(self, proof, previous_hash=None): + """ + Create a new Block in the Blockchain + + A block should have: + * Index + * Timestamp + * List of current transactions + * The proof used to mine this block + * The hash of the previous block + + :param proof: The proof given by the Proof of Work algorithm + :param previous_hash: (Optional) Hash of previous Block + :return: New Block + """ + + if len(self.chain) > 0: + block_string = json.dumps(self.last_block, sort_keys=True) + guess = f'{block_string}{proof}'.encode() + current_hash = hashlib.sha256(guess).hexdigest() + else: + current_hash = "" + + block = { + 'index': len(self.chain) + 1, + 'timestamp': time(), + 'transactions': self.current_transactions, + 'proof': proof, + 'previous_hash': previous_hash or self.hash(self.chain[-1]), + 'hash': current_hash, + } + + # Reset the current list of transactions + self.current_transactions = [] + # Append the chain to the block + self.chain.append(block) + # Return the new block + return block + + def new_transaction(self, sender, recipient, amount): + """ + Creates a new transaction to go into the next mined Block + :param sender: Address of the Recipient + :param recipient: Address of the Recipient + :param amount: Amount + :return: The index of the Block that will hold this transaction + """ + self.current_transactions.append({ + 'sender': sender, + 'recipient': recipient, + 'amount': amount + }) + return self.last_block['index'] + 1 + + def hash(self, block): + """ + Creates a SHA-256 hash of a Block + + :param block": Block + "return": + """ + + # Use json.dumps to convert json into a string + # Use hashlib.sha256 to create a hash + # It requires a `bytes-like` object, which is what + # .encode() does. + # It converts the Python string into a byte string. + # We must make sure that the Dictionary is Ordered, + # or we'll have inconsistent hashes + + # TODO: Create the block_string + string_object = json.dumps(block, sort_keys=True) + block_string = string_object.encode() + + # TODO: Hash this string using sha256 + raw_hash = hashlib.sha256(block_string) + hex_hash = raw_hash.hexdigest() + + # By itself, the sha256 function returns the hash in a raw string + # that will likely include escaped characters. + # This can be hard to read, but .hexdigest() converts the + # hash to a string of hexadecimal characters, which is + # easier to work with and understand + + # TODO: Return the hashed block string in hexadecimal format + return hex_hash + + @property + def last_block(self): + return self.chain[-1] + + # def proof_of_work(self, block, proof): + # """ + # Simple Proof of Work Algorithm + # Stringify the block and look for a proof. + # Loop through possibilities, checking each one against `valid_proof` + # in an effort to find a number that is a valid proof + # :return: A valid proof for the provided block + # """ + # block_string = json.dumps(self.last_block, sort_keys=True) + # proof = 0 + # while not self.valid_proof(block_string, proof): + # proof += 1 + # return proof + + @staticmethod + def valid_proof(block_string, proof): + """ + Validates the Proof: Does hash(block_string, proof) contain 6 + leading zeroes? Return true if the proof is valid + :param block_string: The stringified block to use to + check in combination with `proof` + :param proof: The value that when combined with the + stringified previous block results in a hash that has the + correct number of leading zeroes. + :return: True if the resulting hash is a valid proof, False otherwise + """ + guess = f"{block_string}{proof}".encode() + guess_hash = hashlib.sha256(guess).hexdigest() + return guess_hash[:6] == "000000" + + +# Instantiate our Node +app = Flask(__name__) + +# Generate a globally unique address for this node +node_identifier = str(uuid4()).replace('-', '') + +# Instantiate the Blockchain +blockchain = Blockchain() + + +@app.route('/mine', methods=['POST']) +def mine(): + # Run the proof of work algorithm to get the next proof + # proof = blockchain.proof_of_work() + + # TODO: GET PROOF FROM CLIENT + # data is a dictionary with the POST variables + data = request.get_json() + + # Check that 'proof', and 'id' are present + if 'proof' not in data or 'id' not in data: + response = {'message': 'Must contain "proof" and "id"'} + return jsonify(response), 400 + + proof = data['proof'] + + # Determine if the proof is valid + last_block = blockchain.last_block + last_block_string = json.dumps(last_block, sort_keys=True) + + if blockchain.valid_proof(last_block_string, proof): + blockchain.new_transaction(sender="0", recipient=data["id"].strip(), amount=1) + + # Forge the new Block by adding it to the chain with the proof + previous_hash = blockchain.hash(last_block) + block = blockchain.new_block(proof, previous_hash) + + + response = { + 'message': "New Block Forged", + 'index': block['index'], + 'transactions': block['transactions'], + 'proof': block['proof'], + 'previous_hash': block['previous_hash'], + } + return jsonify(response), 200 + else: + response = {'message': 'Invalid proof'} + return jsonify(response), 200 + + +@app.route('/chain', methods=['GET']) +def full_chain(): + response = { + 'length': len(blockchain.chain), + 'chain': blockchain.chain + } + return jsonify(response), 200 + + +@app.route('/last_block', methods=['GET']) +def get_last_block(): + response = { + 'last_block': blockchain.last_block + } + return jsonify(response), 200 + + +@app.route('/transactions/new', methods=['POST']) +def new_transaction(): + values = request.get_json() + + required = ['sender', 'recipient', 'amount'] + if not all(k in values for k in required): + return 'Missing Values', 400 + + index = blockchain.new_transaction(values["sender"], + values["recipient"], + values["amount"]) + + response = {'message': f"Transaction will be added to Block {index}"} + return jsonify(response), 200 + + +@app.route('/transactions', methods=['GET']) +def return_transactions(): + response = { + 'transactions': blockchain.current_transactions + + } + return jsonify(response), 200 + + + + + + +# Run the program on port 5000 +if __name__ == '__main__': + app.run(host='0.0.0.0', port=5000) \ No newline at end of file diff --git a/client_mining_p/miner.py b/client_mining_p/miner.py index 8e211b88..07158f4c 100644 --- a/client_mining_p/miner.py +++ b/client_mining_p/miner.py @@ -5,15 +5,24 @@ import json +@property +def last_block(self): + return self.chain[-1] + def proof_of_work(block): - """ - Simple Proof of Work Algorithm - Stringify the block and look for a proof. - Loop through possibilities, checking each one against `valid_proof` - in an effort to find a number that is a valid proof - :return: A valid proof for the provided block - """ - pass + + # Simple Proof of Work Algorithm + # Stringify the block and look for a proof + block_string = json.dumps(block, sort_keys=True) + # proof with 6 leading zeros + proof = 000000 + # Loop through possibilities, checking each one against `valid_proof` + while valid_proof(block_string, proof) is False: + proof += 1 + # in an effort to find a number that is a valid proof + # :return: A valid proof for the provided block + + return proof def valid_proof(block_string, proof): @@ -27,7 +36,10 @@ def valid_proof(block_string, proof): correct number of leading zeroes. :return: True if the resulting hash is a valid proof, False otherwise """ - pass + guess = f"{block_string}{proof}".encode() + guess_hash = hashlib.sha256(guess).hexdigest() + return guess_hash[:6] == "000000" + if __name__ == '__main__': @@ -36,12 +48,12 @@ def valid_proof(block_string, proof): node = sys.argv[1] else: node = "http://localhost:5000" - # Load ID f = open("my_id.txt", "r") id = f.read() print("ID is", id) f.close() + coins_mined = 0 # Run forever until interrupted while True: @@ -56,15 +68,26 @@ def valid_proof(block_string, proof): break # TODO: Get the block from `data` and use it to look for a new proof + block = data['last_block'] + print('Success') + print('Starting proof') # new_proof = ??? - + new_proof = proof_of_work(data['last_block']) + # new_proof = proof_of_work(block) # When found, POST it to the server {"proof": new_proof, "id": id} post_data = {"proof": new_proof, "id": id} r = requests.post(url=node + "/mine", json=post_data) - data = r.json() + try: + data = r.json() + if data['message'] == 'New Block Forged': + coins_mined += 1 + print(f'Success! {coins_mined}') + print(data) + except ValueError: + print("Error: Non-json response") + print("Response returned:") + print(r) + break - # TODO: If the server responds with a 'message' 'New Block Forged' - # add 1 to the number of coins mined and print it. Otherwise, - # print the message from the server. - pass + diff --git a/client_mining_p/my_id.txt b/client_mining_p/my_id.txt index 757227a3..f24b17d3 100644 --- a/client_mining_p/my_id.txt +++ b/client_mining_p/my_id.txt @@ -1 +1 @@ -your-name-here +albert-yakubov diff --git a/hello.py b/hello.py new file mode 100644 index 00000000..b3cfcbcc --- /dev/null +++ b/hello.py @@ -0,0 +1,12 @@ +from flask import Flask +app = Flask(__name__) + +@app.route("/") +def hello(): + return "Hello World" +@app.route("/newpage") +def newpage(): + return "Hello World" + +if __name__ == "__main__": + app.run() \ No newline at end of file