diff --git a/main.py b/main.py index 015f1d8..0a9793e 100644 --- a/main.py +++ b/main.py @@ -1,36 +1,43 @@ import os import asyncio +# TODO: replace json with a serializer +import json from src.transaction import Transaction, TransactionSchema from src.validation import validate_transactions +from marshmallow import ValidationError from src.mine import Mine -async def main() -> None: - """ - This will be responsible for kick-starting the mining process - :return: None - """ - # Read and deserialize transactions from mempool directory +async def main(): + # Step 1: Read and deserialize transactions from the mempool directory transactions = [] + errors = 0 for filename in os.listdir("mempool"): filepath = os.path.join("mempool", filename) with open(filepath, "r") as file: json_data = file.read() + # TODO: This would be reimplemented after I fix deserizlizing with marshmallow transaction_schema = TransactionSchema() try: - transaction = transaction_schema.load(json_data) + transaction = transaction_schema.load(json.loads(json_data)) transactions.append(transaction) - except Exception as e: - print(f"Error deserializing transaction {filename}: {e}") - - # Validate transactions asynchronously + print(f"Deserialized transaction from {filename}") + except ValidationError as e: + errors = errors + 1 + print(f"Error deserializing transaction {filename}:") + print(e.messages) + + print(f"Total transactions deserialized: {len(transactions)}") + print(f"Total failed transactions:{errors}") + + # Step 2: Validate transactions asynchronously valid_transactions = await validate_transactions(transactions) - # Mine the block + # Step 3: Mine the block block_data = mine_block(valid_transactions) + # Step 4: Write the block data to the output.txt file with open("output.txt", "w") as output_file: output_file.write(block_data) - if __name__ == "__main__": asyncio.run(main()) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..d2703fd --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +marshmallow==3.21.1 +packaging==24.0 diff --git a/run.sh b/run.sh index 1b6ee8f..2faabd9 100755 --- a/run.sh +++ b/run.sh @@ -1,13 +1,13 @@ #!/bin/bash -# Create a virtual environment -python3 -m venv venv +# # Create a virtual environment +# python3 -m venv venv -# Activate the virtual environment -source venv/bin/activate +# # Activate the virtual environment +# source venv/bin/activate -# Install project dependencies -pip install -r requirements.txt +# # Install project dependencies +# pip install -r requirements.txt # Run the mine_block.py script -python3 src/mine_block.py \ No newline at end of file +python3 main.py \ No newline at end of file diff --git a/src/__pycache__/__init__.cpython-310.pyc b/src/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..ca4af97 Binary files /dev/null and b/src/__pycache__/__init__.cpython-310.pyc differ diff --git a/src/__pycache__/mine.cpython-310.pyc b/src/__pycache__/mine.cpython-310.pyc new file mode 100644 index 0000000..6064a94 Binary files /dev/null and b/src/__pycache__/mine.cpython-310.pyc differ diff --git a/src/__pycache__/transaction.cpython-310.pyc b/src/__pycache__/transaction.cpython-310.pyc new file mode 100644 index 0000000..abdb1cb Binary files /dev/null and b/src/__pycache__/transaction.cpython-310.pyc differ diff --git a/src/__pycache__/validation.cpython-310.pyc b/src/__pycache__/validation.cpython-310.pyc new file mode 100644 index 0000000..834e45a Binary files /dev/null and b/src/__pycache__/validation.cpython-310.pyc differ diff --git a/src/mine.py b/src/mine.py index b266170..c654d66 100644 --- a/src/mine.py +++ b/src/mine.py @@ -11,7 +11,9 @@ def mine_block(valid_transactions) -> str: :param valid_transactions: all the validated transactions :return: str """ + # TODO: implement block mining here + block_data = "Block header\nCoinbase transaction\n" - for transaction in valid_transactions: - block_data += f"{transaction.txid}\n" + for tx in valid_transactions: + block_data += f"{tx.txid}\n" return block_data diff --git a/src/transaction.py b/src/transaction.py index 063f33c..8926044 100644 --- a/src/transaction.py +++ b/src/transaction.py @@ -1,10 +1,36 @@ from marshmallow import Schema, fields, post_load +class PrevOutSchema(Schema): + scriptpubkey = fields.Str(required=True) + scriptpubkey_asm = fields.Str(required=True) + scriptpubkey_type = fields.Str(required=True) + scriptpubkey_address = fields.Str(required=True) + value = fields.Int(required=True) + +class VinSchema(Schema): + txid = fields.Str(required=True) + vout = fields.Int(required=True) + prevout = fields.Nested(PrevOutSchema, required=True) + scriptsig = fields.Str(required=True) + scriptsig_asm = fields.Str(required=True) + witness = fields.List(fields.Str(), required=False) + is_coinbase = fields.Bool(required=True) + sequence = fields.Int(required=True) + inner_redeemscript_asm = fields.Str(required=False) + inner_witnessscript_asm = fields.Str(required=False) + +class VoutSchema(Schema): + scriptpubkey = fields.Str(required=True) + scriptpubkey_asm = fields.Str(required=True) + scriptpubkey_type = fields.Str(required=True) + scriptpubkey_address = fields.Str(required=False) + value = fields.Int(required=True) + class TransactionSchema(Schema): version = fields.Int(required=True) locktime = fields.Int(required=True) - vin = fields.List(fields.Dict(), required=True) - vout = fields.List(fields.Dict(), required=True) + vin = fields.List(fields.Nested(VinSchema), required=True) + vout = fields.List(fields.Nested(VoutSchema), required=True) @post_load def make_transaction(self, data, **kwargs): @@ -16,6 +42,6 @@ class Transaction: """ def __init__(self, version, locktime, vin, vout) -> None: self.version = version - self.locktime = version + self.locktime = locktime self.vin = vin self.vout = vout diff --git a/src/validation.py b/src/validation.py index a0d291b..23b5287 100644 --- a/src/validation.py +++ b/src/validation.py @@ -7,7 +7,7 @@ async def validate_transaction(transaction) -> bool: :param transaction: a transaction to be validated :return: bool """ - pass + async def validate_transactions(transactions) -> list: """ @@ -27,5 +27,5 @@ async def validate_transactions(transactions) -> list: for tx, is_valid in zip(transactions, validation_results): if is_valid: valid_transactions.append(tx) - + return valid_transactions diff --git a/venv/lib/python3.10/site-packages/marshmallow_oneofschema-3.1.1.dist-info/INSTALLER b/venv/lib/python3.10/site-packages/marshmallow_oneofschema-3.1.1.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/venv/lib/python3.10/site-packages/marshmallow_oneofschema-3.1.1.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/venv/lib/python3.10/site-packages/marshmallow_oneofschema-3.1.1.dist-info/LICENSE b/venv/lib/python3.10/site-packages/marshmallow_oneofschema-3.1.1.dist-info/LICENSE new file mode 100644 index 0000000..d0c0544 --- /dev/null +++ b/venv/lib/python3.10/site-packages/marshmallow_oneofschema-3.1.1.dist-info/LICENSE @@ -0,0 +1,21 @@ +Copyright 2016-2017 Maxim Kulkin +Copyright 2018 Alex Rothberg and contributors +Copyright 2024 Steven Loria and contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/venv/lib/python3.10/site-packages/marshmallow_oneofschema-3.1.1.dist-info/METADATA b/venv/lib/python3.10/site-packages/marshmallow_oneofschema-3.1.1.dist-info/METADATA new file mode 100644 index 0000000..f770ce3 --- /dev/null +++ b/venv/lib/python3.10/site-packages/marshmallow_oneofschema-3.1.1.dist-info/METADATA @@ -0,0 +1,153 @@ +Metadata-Version: 2.1 +Name: marshmallow-oneofschema +Version: 3.1.1 +Summary: marshmallow multiplexing schema +Author-email: Maxim Kulkin +Maintainer-email: Steven Loria +Requires-Python: >=3.8 +Description-Content-Type: text/x-rst +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Requires-Dist: marshmallow>=3.0.0,<4.0.0 +Requires-Dist: marshmallow-oneofschema[tests] ; extra == "dev" +Requires-Dist: tox ; extra == "dev" +Requires-Dist: pre-commit~=3.5 ; extra == "dev" +Requires-Dist: pytest ; extra == "tests" +Project-URL: Funding, https://opencollective.com/marshmallow +Project-URL: Issues, https://github.com/marshmallow-code/marshmallow-oneofschema/issues +Project-URL: Source, https://github.com/marshmallow-code/marshmallow-oneofschema +Provides-Extra: dev +Provides-Extra: tests + +======================= +marshmallow-oneofschema +======================= + +.. image:: https://github.com/marshmallow-code/marshmallow-oneofschema/actions/workflows/build-release.yml/badge.svg + :target: https://github.com/marshmallow-code/flask-marshmallow/actions/workflows/build-release.yml + :alt: Build Status + +.. image:: https://badgen.net/badge/marshmallow/3 + :target: https://marshmallow.readthedocs.io/en/latest/upgrading.html + :alt: marshmallow 3 compatible + +An extension to marshmallow to support schema (de)multiplexing. + +marshmallow is a fantastic library for serialization and deserialization of data. +For more on that project see its `GitHub `_ +page or its `Documentation `_. + +This library adds a special kind of schema that actually multiplexes other schemas +based on object type. When serializing values, it uses get_obj_type() method +to get object type name. Then it uses ``type_schemas`` name-to-Schema mapping +to get schema for that particular object type, serializes object using that +schema and adds an extra field with name of object type. Deserialization is reverse. + +Installing +---------- + +:: + + $ pip install marshmallow-oneofschema + +Example +------- + +The code below demonstrates how to set up a polymorphic schema. For the full context check out the tests. +Once setup the schema should act like any other schema. If it does not then please file an Issue. + +.. code:: python + + import marshmallow + import marshmallow.fields + from marshmallow_oneofschema import OneOfSchema + + + class Foo: + def __init__(self, foo): + self.foo = foo + + + class Bar: + def __init__(self, bar): + self.bar = bar + + + class FooSchema(marshmallow.Schema): + foo = marshmallow.fields.String(required=True) + + @marshmallow.post_load + def make_foo(self, data, **kwargs): + return Foo(**data) + + + class BarSchema(marshmallow.Schema): + bar = marshmallow.fields.Integer(required=True) + + @marshmallow.post_load + def make_bar(self, data, **kwargs): + return Bar(**data) + + + class MyUberSchema(OneOfSchema): + type_schemas = {"foo": FooSchema, "bar": BarSchema} + + def get_obj_type(self, obj): + if isinstance(obj, Foo): + return "foo" + elif isinstance(obj, Bar): + return "bar" + else: + raise Exception("Unknown object type: {}".format(obj.__class__.__name__)) + + + MyUberSchema().dump([Foo(foo="hello"), Bar(bar=123)], many=True) + # => [{'type': 'foo', 'foo': 'hello'}, {'type': 'bar', 'bar': 123}] + + MyUberSchema().load( + [{"type": "foo", "foo": "hello"}, {"type": "bar", "bar": 123}], many=True + ) + # => [Foo('hello'), Bar(123)] + +By default get_obj_type() returns obj.__class__.__name__, so you can just reuse that +to save some typing: + +.. code:: python + + class MyUberSchema(OneOfSchema): + type_schemas = {"Foo": FooSchema, "Bar": BarSchema} + +You can customize type field with `type_field` class property: + +.. code:: python + + class MyUberSchema(OneOfSchema): + type_field = "object_type" + type_schemas = {"Foo": FooSchema, "Bar": BarSchema} + + + MyUberSchema().dump([Foo(foo="hello"), Bar(bar=123)], many=True) + # => [{'object_type': 'Foo', 'foo': 'hello'}, {'object_type': 'Bar', 'bar': 123}] + +You can use resulting schema everywhere marshmallow.Schema can be used, e.g. + +.. code:: python + + import marshmallow as m + import marshmallow.fields as f + + + class MyOtherSchema(m.Schema): + items = f.List(f.Nested(MyUberSchema)) + +License +------- + +MIT licensed. See the bundled `LICENSE `_ file for more details. + diff --git a/venv/lib/python3.10/site-packages/marshmallow_oneofschema-3.1.1.dist-info/RECORD b/venv/lib/python3.10/site-packages/marshmallow_oneofschema-3.1.1.dist-info/RECORD new file mode 100644 index 0000000..727b5e4 --- /dev/null +++ b/venv/lib/python3.10/site-packages/marshmallow_oneofschema-3.1.1.dist-info/RECORD @@ -0,0 +1,11 @@ +marshmallow_oneofschema-3.1.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +marshmallow_oneofschema-3.1.1.dist-info/LICENSE,sha256=Dj6i54GCxVk8glcTykJvh2Mga2HVVWw0_0cARUXh_uk,1148 +marshmallow_oneofschema-3.1.1.dist-info/METADATA,sha256=cPVqGW5WZduc5r9TBHC-fxFMu8Axv0XO1cd9LIuLZCI,5018 +marshmallow_oneofschema-3.1.1.dist-info/RECORD,, +marshmallow_oneofschema-3.1.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +marshmallow_oneofschema-3.1.1.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81 +marshmallow_oneofschema/__init__.py,sha256=KQjXt0W26CH8CvBBTA0YFEMsIHwR9_oMfBGppTnoTlI,47 +marshmallow_oneofschema/__pycache__/__init__.cpython-310.pyc,, +marshmallow_oneofschema/__pycache__/one_of_schema.cpython-310.pyc,, +marshmallow_oneofschema/one_of_schema.py,sha256=cdaUaBSYXhwN7vusKCJFr9N0D_iyKIAmEsfzdBnISYM,6722 +marshmallow_oneofschema/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 diff --git a/venv/lib/python3.10/site-packages/marshmallow_oneofschema-3.1.1.dist-info/REQUESTED b/venv/lib/python3.10/site-packages/marshmallow_oneofschema-3.1.1.dist-info/REQUESTED new file mode 100644 index 0000000..e69de29 diff --git a/venv/lib/python3.10/site-packages/marshmallow_oneofschema-3.1.1.dist-info/WHEEL b/venv/lib/python3.10/site-packages/marshmallow_oneofschema-3.1.1.dist-info/WHEEL new file mode 100644 index 0000000..3b5e64b --- /dev/null +++ b/venv/lib/python3.10/site-packages/marshmallow_oneofschema-3.1.1.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: flit 3.9.0 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/venv/lib/python3.10/site-packages/marshmallow_oneofschema/__init__.py b/venv/lib/python3.10/site-packages/marshmallow_oneofschema/__init__.py new file mode 100644 index 0000000..62413dd --- /dev/null +++ b/venv/lib/python3.10/site-packages/marshmallow_oneofschema/__init__.py @@ -0,0 +1 @@ +from .one_of_schema import OneOfSchema # noqa diff --git a/venv/lib/python3.10/site-packages/marshmallow_oneofschema/__pycache__/__init__.cpython-310.pyc b/venv/lib/python3.10/site-packages/marshmallow_oneofschema/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000..49e4e13 Binary files /dev/null and b/venv/lib/python3.10/site-packages/marshmallow_oneofschema/__pycache__/__init__.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/marshmallow_oneofschema/__pycache__/one_of_schema.cpython-310.pyc b/venv/lib/python3.10/site-packages/marshmallow_oneofschema/__pycache__/one_of_schema.cpython-310.pyc new file mode 100644 index 0000000..b04cb47 Binary files /dev/null and b/venv/lib/python3.10/site-packages/marshmallow_oneofschema/__pycache__/one_of_schema.cpython-310.pyc differ diff --git a/venv/lib/python3.10/site-packages/marshmallow_oneofschema/one_of_schema.py b/venv/lib/python3.10/site-packages/marshmallow_oneofschema/one_of_schema.py new file mode 100644 index 0000000..a6e3f96 --- /dev/null +++ b/venv/lib/python3.10/site-packages/marshmallow_oneofschema/one_of_schema.py @@ -0,0 +1,193 @@ +import typing + +from marshmallow import Schema, ValidationError + + +class OneOfSchema(Schema): + """ + This is a special kind of schema that actually multiplexes other schemas + based on object type. When serializing values, it uses get_obj_type() method + to get object type name. Then it uses `type_schemas` name-to-Schema mapping + to get schema for that particular object type, serializes object using that + schema and adds an extra "type" field with name of object type. + Deserialization is reverse. + + Example: + + class Foo(object): + def __init__(self, foo): + self.foo = foo + + class Bar(object): + def __init__(self, bar): + self.bar = bar + + class FooSchema(marshmallow.Schema): + foo = marshmallow.fields.String(required=True) + + @marshmallow.post_load + def make_foo(self, data, **kwargs): + return Foo(**data) + + class BarSchema(marshmallow.Schema): + bar = marshmallow.fields.Integer(required=True) + + @marshmallow.post_load + def make_bar(self, data, **kwargs): + return Bar(**data) + + class MyUberSchema(marshmallow.OneOfSchema): + type_schemas = { + 'foo': FooSchema, + 'bar': BarSchema, + } + + def get_obj_type(self, obj): + if isinstance(obj, Foo): + return 'foo' + elif isinstance(obj, Bar): + return 'bar' + else: + raise Exception('Unknown object type: %s' % repr(obj)) + + MyUberSchema().dump([Foo(foo='hello'), Bar(bar=123)], many=True) + # => [{'type': 'foo', 'foo': 'hello'}, {'type': 'bar', 'bar': 123}] + + You can control type field name added to serialized object representation by + setting `type_field` class property. + """ + + type_field = "type" + type_field_remove = True + type_schemas: typing.Mapping[str, typing.Union[typing.Type[Schema], Schema]] = {} + + def get_obj_type(self, obj): + """Returns name of the schema during dump() calls, given the object + being dumped.""" + return obj.__class__.__name__ + + def get_data_type(self, data): + """Returns name of the schema during load() calls, given the data being + loaded. Defaults to looking up `type_field` in the data.""" + data_type = data.get(self.type_field) + if self.type_field in data and self.type_field_remove: + data.pop(self.type_field) + return data_type + + def dump(self, obj, *, many=None, **kwargs): + errors = {} + result_data = [] + result_errors = {} + many = self.many if many is None else bool(many) + if not many: + result = result_data = self._dump(obj, **kwargs) + else: + for idx, o in enumerate(obj): + try: + result = self._dump(o, **kwargs) + result_data.append(result) + except ValidationError as error: + result_errors[idx] = error.normalized_messages() + result_data.append(error.valid_data) + + result = result_data + errors = result_errors + + if not errors: + return result + else: + exc = ValidationError(errors, data=obj, valid_data=result) + raise exc + + def _dump(self, obj, *, update_fields=True, **kwargs): + obj_type = self.get_obj_type(obj) + if obj_type is None: + return ( + None, + {"_schema": "Unknown object class: %s" % obj.__class__.__name__}, + ) + + type_schema = self.type_schemas.get(obj_type) + if not type_schema: + return None, {"_schema": "Unsupported object type: %s" % obj_type} + + schema = type_schema if isinstance(type_schema, Schema) else type_schema() + + schema.context.update(getattr(self, "context", {})) + + result = schema.dump(obj, many=False, **kwargs) + if result is not None: + result[self.type_field] = obj_type + return result + + def load(self, data, *, many=None, partial=None, unknown=None, **kwargs): + errors = {} + result_data = [] + result_errors = {} + many = self.many if many is None else bool(many) + if partial is None: + partial = self.partial + if not many: + try: + result = result_data = self._load( + data, partial=partial, unknown=unknown, **kwargs + ) + # result_data.append(result) + except ValidationError as error: + result_errors = error.normalized_messages() + result_data.append(error.valid_data) + else: + for idx, item in enumerate(data): + try: + result = self._load(item, partial=partial, **kwargs) + result_data.append(result) + except ValidationError as error: + result_errors[idx] = error.normalized_messages() + result_data.append(error.valid_data) + + result = result_data + errors = result_errors + + if not errors: + return result + else: + exc = ValidationError(errors, data=data, valid_data=result) + raise exc + + def _load(self, data, *, partial=None, unknown=None, **kwargs): + if not isinstance(data, dict): + raise ValidationError({"_schema": "Invalid data type: %s" % data}) + + data = dict(data) + unknown = unknown or self.unknown + data_type = self.get_data_type(data) + + if data_type is None: + raise ValidationError( + {self.type_field: ["Missing data for required field."]} + ) + + try: + type_schema = self.type_schemas.get(data_type) + except TypeError as error: + # data_type could be unhashable + raise ValidationError( + {self.type_field: ["Invalid value: %s" % data_type]} + ) from error + if not type_schema: + raise ValidationError( + {self.type_field: ["Unsupported value: %s" % data_type]} + ) + + schema = type_schema if isinstance(type_schema, Schema) else type_schema() + + schema.context.update(getattr(self, "context", {})) + + return schema.load(data, many=False, partial=partial, unknown=unknown, **kwargs) + + def validate(self, data, *, many=None, partial=None): + try: + self.load(data, many=many, partial=partial) + except ValidationError as ve: + return ve.messages + return {} diff --git a/venv/lib/python3.10/site-packages/marshmallow_oneofschema/py.typed b/venv/lib/python3.10/site-packages/marshmallow_oneofschema/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/venv/pyvenv.cfg b/venv/pyvenv.cfg index 0537ffc..6d66b89 100644 --- a/venv/pyvenv.cfg +++ b/venv/pyvenv.cfg @@ -1,3 +1,3 @@ -home = /usr/bin +home = /home/netweaver/Desktop/.bitcoin_summer_of_code/code-challenge-2024-obamwonyi/venv/bin include-system-site-packages = false version = 3.10.12