From 7477feee8801e4e408a472622c1176b737b531ce Mon Sep 17 00:00:00 2001 From: "A.A. Suvorov" Date: Wed, 30 Oct 2024 00:21:50 +0700 Subject: [PATCH] blockchain v0.2.0 --- .gitignore | 39 ++++++- README.md | 167 ++++++++++++++++++++++++++- blockchain_base.py | 59 ++++++++++ blockchain.py => blockchain_flask.py | 92 ++++++++------- requirements.txt | 14 ++- 5 files changed, 318 insertions(+), 53 deletions(-) create mode 100644 blockchain_base.py rename blockchain.py => blockchain_flask.py (58%) diff --git a/.gitignore b/.gitignore index fe9836c..b0b6f3a 100644 --- a/.gitignore +++ b/.gitignore @@ -20,7 +20,6 @@ parts/ sdist/ var/ wheels/ -pip-wheel-metadata/ share/python-wheels/ *.egg-info/ .installed.cfg @@ -50,6 +49,7 @@ coverage.xml *.py,cover .hypothesis/ .pytest_cache/ +cover/ # Translations *.mo @@ -72,6 +72,7 @@ instance/ docs/_build/ # PyBuilder +.pybuilder/ target/ # Jupyter Notebook @@ -82,7 +83,9 @@ profile_default/ ipython_config.py # pyenv -.python-version +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. @@ -91,7 +94,22 @@ ipython_config.py # install all needed dependencies. #Pipfile.lock -# PEP 582; used by e.g. github.com/David-OConnor/pyflow +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm __pypackages__/ # Celery stuff @@ -127,5 +145,16 @@ dmypy.json # Pyre type checker .pyre/ -.idea -.idea/* \ No newline at end of file + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +.idea/ \ No newline at end of file diff --git a/README.md b/README.md index 5460fa8..95b1e82 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,184 @@ -# BlockChain +# Blockchain in Python -*** +--- + +This project is an implementation of a blockchain in Python, +which includes core features such as block creation, transaction processing, and a consensus algorithm. +It consists of two modules: one implements the blockchain using Flask to create an API, +while the other represents a simpler implementation of the blockchain using classes. + +--- + +> Warning! This is a fairly old version that I moved to my historical repository and may continue to develop. +> So this version is still in development and research phase. + +## Modules: + +### 1. Flask Blockchain + +This module creates a blockchain using Flask, allowing you to interact with it via HTTP requests. Key features include: + +- **Creating a new block**: The `/mine` method runs the proof-of-work algorithm and creates a new block. +- **Creating a new transaction**: The `/transactions/new` method allows you to add new transactions to the blockchain. +- **Getting the full chain of blocks**: The `/chain` method returns the current chain of blocks and its length. +- **Registering new nodes**: The `/nodes/register` method allows you to add new nodes to the network. +- **Consensus algorithm**: The `/nodes/resolve` method checks and resolves conflicts in the chain of blocks. + +### 2. Simple Blockchain Implementation +This module implements the basic structure of a blockchain with the `Block` and `Blockchain` classes. +The main functions include: + +- **Creating a genesis block**: The `create_genesis_block` method creates the first block in the chain. +- **Adding a new block**: The `add_block` method adds a new block to the chain, setting the correct hash of the previous block. + +--- - `pip install -r requirements.txt` -- `python3 blockchain.py` +- `python blockchain.py` - `curl http://localhost:5000/mine` + +```json +{ + "index": 2, + "message": "New Block Forged", + "previous_hash": "5d9d222c768d6c501ae28670a7333e0ae39c14d7884650bf12cf32d3961e2682", + "proof": 35293, + "transactions": [ + { + "amount": 1, + "recipient": "902a4bd96aa44abcb0d9a097bd78c462", + "sender": "0" + } + ] +} + +``` + - `curl http://localhost:5000/mine` +```json +{ + "index": 3, + "message": "New Block Forged", + "previous_hash": "c67d3b156c5c3db264331196bb48efb5e6fa0fcfb31f98cf663e38d69089ffb4", + "proof": 35089, + "transactions": [ + { + "amount": 1, + "recipient": "902a4bd96aa44abcb0d9a097bd78c462", + "sender": "0" + } + ] +} + +``` + - `curl http://localhost:5000/chain` +```json +{ + "chain": [ + { + "index": 1, + "previous_hash": 1, + "proof": 100, + "timestamp": 1730220820.929162, + "transactions": [] + }, + { + "index": 2, + "previous_hash": "5d9d222c768d6c501ae28670a7333e0ae39c14d7884650bf12cf32d3961e2682", + "proof": 35293, + "timestamp": 1730220836.4981325, + "transactions": [ + { + "amount": 1, + "recipient": "902a4bd96aa44abcb0d9a097bd78c462", + "sender": "0" + } + ] + }, + { + "index": 3, + "previous_hash": "c67d3b156c5c3db264331196bb48efb5e6fa0fcfb31f98cf663e38d69089ffb4", + "proof": 35089, + "timestamp": 1730220846.5631032, + "transactions": [ + { + "amount": 1, + "recipient": "902a4bd96aa44abcb0d9a097bd78c462", + "sender": "0" + } + ] + } + ], + "length": 3 +} + +``` + - `curl -X POST -H "Content-Type: application/json" -d '{ "sender": "d4ee26eee15148ee92c6cd394edd974e", "recipient": "someone-other-address", "amount": 5 }' "http://localhost:5000/transactions/new"` + +```json +{ + "message": "Transaction will be added to Block 4" +} + +``` + +- `python blockchain_base.py` + +```text +Index: 0 +Timestamp: 2024-10-30 00:01:20.180414 +Data: Genesis Block +Previous Hash: 0 +Hash: 02a478085aca11b94a6ea0b5c2cfe9df56dffbe5153df269f3609faa19fc5641 + +Index: 1 +Timestamp: 2024-10-30 00:01:20.180433 +Data: Transaction Data +Previous Hash: 02a478085aca11b94a6ea0b5c2cfe9df56dffbe5153df269f3609faa19fc5641 +Hash: 3a7bc11cc8e213468fc25b6708b39e41780d5673ffffb995bbfdaa909d37b73e + +Index: 2 +Timestamp: 2024-10-30 00:01:20.180441 +Data: Another Transaction +Previous Hash: 3a7bc11cc8e213468fc25b6708b39e41780d5673ffffb995bbfdaa909d37b73e +Hash: 477333623f8b9819e23412dfda01aee127d63a2744b32515cc2d0678fcf7fac1 +``` + +*** + +Author and developer: ___A.A. Suvorov.___ + +*** + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*** + + Licensed under the terms of the BSD 3-Clause License + (see LICENSE for details). + Copyright © 2018-2024, A.A. Suvorov + All rights reserved. \ No newline at end of file diff --git a/blockchain_base.py b/blockchain_base.py new file mode 100644 index 0000000..8443419 --- /dev/null +++ b/blockchain_base.py @@ -0,0 +1,59 @@ +# -------------------------------------------------------- +# Licensed under the terms of the BSD 3-Clause License +# (see LICENSE for details). +# Copyright © 2018-2024, A.A. Suvorov +# All rights reserved. +# -------------------------------------------------------- +import datetime +import hashlib + + +class Block: + def __init__(self, index, timestamp, data, previous_hash): + self.index = index + self.timestamp = timestamp + self.data = data + self.previous_hash = previous_hash + self.hash = self.calculate_hash() + + def calculate_hash(self): + return hashlib.sha256( + str(self.index).encode() + + str(self.timestamp).encode() + + str(self.data).encode() + + str(self.previous_hash).encode() + ).hexdigest() + + +class Blockchain: + def __init__(self): + self.chain = [self.create_genesis_block()] + + def create_genesis_block(self): + return Block(0, datetime.datetime.now(), "Genesis Block", "0") + + def get_latest_block(self): + return self.chain[-1] + + def add_block(self, new_block): + new_block.previous_hash = self.get_latest_block().hash + new_block.hash = new_block.calculate_hash() + self.chain.append(new_block) + + +def main(): + my_blockchain = Blockchain() + my_blockchain.add_block(Block(1, datetime.datetime.now(), "Transaction Data", "")) + my_blockchain.add_block(Block(2, datetime.datetime.now(), "Another Transaction", "")) + + for block in my_blockchain.chain: + print("Index:", block.index) + print("Timestamp:", block.timestamp) + print("Data:", block.data) + print("Previous Hash:", block.previous_hash) + print("Hash:", block.hash) + print() + + +if __name__ == '__main__': + main() diff --git a/blockchain.py b/blockchain_flask.py similarity index 58% rename from blockchain.py rename to blockchain_flask.py index 52f836c..fa62ff8 100644 --- a/blockchain.py +++ b/blockchain_flask.py @@ -1,3 +1,9 @@ +# -------------------------------------------------------- +# Licensed under the terms of the BSD 3-Clause License +# (see LICENSE for details). +# Copyright © 2018-2024, A.A. Suvorov +# All rights reserved. +# -------------------------------------------------------- import hashlib import json @@ -15,16 +21,16 @@ def __init__(self): self.current_transactions = [] self.chain = [] self.nodes = set() - # Создание блока генезиса + # Creating a Genesis Block self.new_block(previous_hash=1, proof=100) def new_block(self, proof, previous_hash=None): """ - Создание нового блока в блокчейне + Creating a new block in the blockchain - :param proof: Доказательства проведенной работы - :param previous_hash: (Опционально) хеш предыдущего блока - :return: Новый блок + :param proof: Evidence of work performed + :param previous_hash: (Optional) hash of the previous block + :return: New block """ block = { @@ -35,7 +41,7 @@ def new_block(self, proof, previous_hash=None): 'previous_hash': previous_hash or self.hash(self.chain[-1]), } - # Перезагрузка текущего списка транзакций + # Reloading the current transaction list self.current_transactions = [] self.chain.append(block) @@ -43,12 +49,12 @@ def new_block(self, proof, previous_hash=None): def new_transaction(self, sender, recipient, amount): """ - Направляет новую транзакцию в следующий блок + Directs the new transaction to the next block - :param sender: Адрес отправителя - :param recipient: Адрес получателя - :param amount: Сумма - :return: Индекс блока, который будет хранить эту транзакцию + :param sender: Sender's address + :param recipient: Address of the recipient + :param amount: Sum + :return: Index of the block that will store this transaction """ self.current_transactions.append({ 'sender': sender, @@ -65,21 +71,21 @@ def last_block(self): @staticmethod def hash(block): """ - Создает хэш SHA-256 блока + Creates a SHA-256 hash of a block - :param block: Блок + :param block: Block :return: """ - # Мы должны убедиться в том, что словарь упорядочен, иначе у нас будут непоследовательные хеши + # We need to make sure the dictionary is in order, otherwise we will have inconsistent hashes block_string = json.dumps(block, sort_keys=True).encode() return hashlib.sha256(block_string).hexdigest() def proof_of_work(self, last_proof): """ - Простая проверка алгоритма: - - Поиска числа p`, так как hash(pp`) содержит 4 заглавных нуля, где p - предыдущий - - p является предыдущим доказательством, а p` - новым + A simple check of the algorithm: + - Search for the number p`, since hash(pp`) contains 4 capital zeros, where p is the previous one + - p is the previous proof, and p` is the new one :param last_proof: :return: @@ -94,11 +100,11 @@ def proof_of_work(self, last_proof): @staticmethod def valid_proof(last_proof, proof): """ - Подтверждение доказательства: Содержит ли hash(last_proof, proof) 4 заглавных нуля? + Proof: Does hash(last_proof, proof) contain 4 leading zeros? - :param last_proof: Предыдущее доказательство - :param proof: Текущее доказательство - :return: True, если правильно, False, если нет. + :param last_proof: Previous proof + :param proof: Current proof + :return: True, if correct, False if not. """ guess = f'{last_proof}{proof}'.encode() @@ -107,9 +113,9 @@ def valid_proof(last_proof, proof): def register_node(self, address): """ - Вносим новый узел в список узлов + Adding a new node to the list of nodes - :param address: адрес узла , другими словами: 'http://192.168.0.5:5000' + :param address: node address, in other words: 'http://192.168.0.5:5000' :return: None """ @@ -118,10 +124,10 @@ def register_node(self, address): def valid_chain(self, chain): """ - Проверяем, является ли внесенный в блок хеш корректным + Checking whether the hash included in the block is correct :param chain: blockchain - :return: True если она действительна, False, если нет + :return: True if it is valid, False if not """ last_block = chain[0] @@ -132,11 +138,11 @@ def valid_chain(self, chain): print(f'{last_block}') print(f'{block}') print("\n-----------\n") - # Проверьте правильность хеша блока + # Check the block hash is correct if block['previous_hash'] != self.hash(last_block): return False - # Проверяем, является ли подтверждение работы корректным + # Checking whether the proof of work is correct if not self.valid_proof(last_block['proof'], block['proof']): return False @@ -147,19 +153,19 @@ def valid_chain(self, chain): def resolve_conflicts(self): """ - Это наш алгоритм Консенсуса, он разрешает конфликты, - заменяя нашу цепь на самую длинную в цепи + This is our Consensus algorithm, it resolves conflicts, + replacing our chain with the longest one in the chain - :return: True, если бы наша цепь была заменена, False, если нет. + :return: True if our chain had been replaced, False if not. """ neighbours = self.nodes new_chain = None - # Ищем только цепи, длиннее нашей + # We are looking only for chains longer than ours max_length = len(self.chain) - # Захватываем и проверяем все цепи из всех узлов сети + # We capture and check all circuits from all network nodes for node in neighbours: response = requests.get(f'http://{node}/chain') @@ -167,12 +173,12 @@ def resolve_conflicts(self): length = response.json()['length'] chain = response.json()['chain'] - # Проверяем, является ли длина самой длинной, а цепь - валидной + # Checking if the length is the longest and the chain is valid if length > max_length and self.valid_chain(chain): max_length = length new_chain = chain - # Заменяем нашу цепь, если найдем другую валидную и более длинную + # We replace our chain if we find another valid and longer one if new_chain: self.chain = new_chain return True @@ -180,32 +186,32 @@ def resolve_conflicts(self): return False -# Создаем экземпляр узла +# Create a node instance app = Flask(__name__) -# Генерируем уникальный на глобальном уровне адрес для этого узла +# Generate a globally unique address for this node node_identifier = str(uuid4()).replace('-', '') -# Создаем экземпляр блокчейна +# Creating a Blockchain Instance block_chain = BlockChain() @app.route('/mine', methods=['GET']) def mine(): - # Мы запускаем алгоритм подтверждения работы, чтобы получить следующее подтверждение… + # We run the proof-of-work algorithm to get the next proof... last_block = block_chain.last_block last_proof = last_block['proof'] proof = block_chain.proof_of_work(last_proof) - # Мы должны получить вознаграждение за найденное подтверждение - # Отправитель “0” означает, что узел заработал крипто-монету + # We should receive a reward for finding confirmation + # Sender “0” means that the node has earned a crypto coin block_chain.new_transaction( sender="0", recipient=node_identifier, amount=1, ) - # Создаем новый блок, путем внесения его в цепь + # We create a new block by adding it to the chain previous_hash = block_chain.hash(last_block) block = block_chain.new_block(proof, previous_hash) @@ -223,12 +229,12 @@ def mine(): def new_transaction(): values = request.get_json() - # Убедитесь в том, что необходимые поля находятся среди POST-данных + # Make sure the required fields are included in the POST data required = ['sender', 'recipient', 'amount'] if not all(k in values for k in required): return 'Missing values', 400 - # Создание новой транзакции + # Creating a new transaction index = block_chain.new_transaction(values['sender'], values['recipient'], values['amount']) response = {'message': f'Transaction will be added to Block {index}'} diff --git a/requirements.txt b/requirements.txt index 5eaf725..907f455 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,12 @@ -flask -requests \ No newline at end of file +blinker==1.8.2 +certifi==2024.8.30 +charset-normalizer==3.4.0 +click==8.1.7 +Flask==3.0.3 +idna==3.10 +itsdangerous==2.2.0 +Jinja2==3.1.4 +MarkupSafe==3.0.2 +requests==2.32.3 +urllib3==2.2.3 +Werkzeug==3.0.6