Include prototyping steps #1

Merged
foefl merged 15 commits from native_hashing into main 2025-12-16 12:50:07 +00:00
16 changed files with 27004 additions and 1771 deletions
Showing only changes of commit d155d1afad - Show all commits

99
pdm.lock generated
View File

@ -5,7 +5,7 @@
groups = ["default", "dev", "lint", "nb", "tests"] groups = ["default", "dev", "lint", "nb", "tests"]
strategy = ["inherit_metadata"] strategy = ["inherit_metadata"]
lock_version = "4.5.0" lock_version = "4.5.0"
content_hash = "sha256:c0680aa00cf7474d542ab72daaea39fe9f3c23b3b68f724f49da67b2d74ff588" content_hash = "sha256:813bab76055ae1081c8e05c3f2d6b429ac1caf20ae5353aa06c90e70a17f75cb"
[[metadata.targets]] [[metadata.targets]]
requires_python = ">=3.11" requires_python = ">=3.11"
@ -831,6 +831,56 @@ files = [
{file = "fqdn-1.5.1.tar.gz", hash = "sha256:105ed3677e767fb5ca086a0c1f4bb66ebc3c100be518f0e0d755d9eae164d89f"}, {file = "fqdn-1.5.1.tar.gz", hash = "sha256:105ed3677e767fb5ca086a0c1f4bb66ebc3c100be518f0e0d755d9eae164d89f"},
] ]
[[package]]
name = "greenlet"
version = "3.3.0"
requires_python = ">=3.10"
summary = "Lightweight in-process concurrent programming"
groups = ["default"]
marker = "platform_machine == \"win32\" or platform_machine == \"WIN32\" or platform_machine == \"AMD64\" or platform_machine == \"amd64\" or platform_machine == \"x86_64\" or platform_machine == \"ppc64le\" or platform_machine == \"aarch64\""
files = [
{file = "greenlet-3.3.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:e29f3018580e8412d6aaf5641bb7745d38c85228dacf51a73bd4e26ddf2a6a8e"},
{file = "greenlet-3.3.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a687205fb22794e838f947e2194c0566d3812966b41c78709554aa883183fb62"},
{file = "greenlet-3.3.0-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4243050a88ba61842186cb9e63c7dfa677ec146160b0efd73b855a3d9c7fcf32"},
{file = "greenlet-3.3.0-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:670d0f94cd302d81796e37299bcd04b95d62403883b24225c6b5271466612f45"},
{file = "greenlet-3.3.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6cb3a8ec3db4a3b0eb8a3c25436c2d49e3505821802074969db017b87bc6a948"},
{file = "greenlet-3.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2de5a0b09eab81fc6a382791b995b1ccf2b172a9fec934747a7a23d2ff291794"},
{file = "greenlet-3.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4449a736606bd30f27f8e1ff4678ee193bc47f6ca810d705981cfffd6ce0d8c5"},
{file = "greenlet-3.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:7652ee180d16d447a683c04e4c5f6441bae7ba7b17ffd9f6b3aff4605e9e6f71"},
{file = "greenlet-3.3.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:b01548f6e0b9e9784a2c99c5651e5dc89ffcbe870bc5fb2e5ef864e9cc6b5dcb"},
{file = "greenlet-3.3.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:349345b770dc88f81506c6861d22a6ccd422207829d2c854ae2af8025af303e3"},
{file = "greenlet-3.3.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e8e18ed6995e9e2c0b4ed264d2cf89260ab3ac7e13555b8032b25a74c6d18655"},
{file = "greenlet-3.3.0-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c024b1e5696626890038e34f76140ed1daf858e37496d33f2af57f06189e70d7"},
{file = "greenlet-3.3.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:047ab3df20ede6a57c35c14bf5200fcf04039d50f908270d3f9a7a82064f543b"},
{file = "greenlet-3.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2d9ad37fc657b1102ec880e637cccf20191581f75c64087a549e66c57e1ceb53"},
{file = "greenlet-3.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:83cd0e36932e0e7f36a64b732a6f60c2fc2df28c351bae79fbaf4f8092fe7614"},
{file = "greenlet-3.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a7a34b13d43a6b78abf828a6d0e87d3385680eaf830cd60d20d52f249faabf39"},
{file = "greenlet-3.3.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:a1e41a81c7e2825822f4e068c48cb2196002362619e2d70b148f20a831c00739"},
{file = "greenlet-3.3.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9f515a47d02da4d30caaa85b69474cec77b7929b2e936ff7fb853d42f4bf8808"},
{file = "greenlet-3.3.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7d2d9fd66bfadf230b385fdc90426fcd6eb64db54b40c495b72ac0feb5766c54"},
{file = "greenlet-3.3.0-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30a6e28487a790417d036088b3bcb3f3ac7d8babaa7d0139edbaddebf3af9492"},
{file = "greenlet-3.3.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:087ea5e004437321508a8d6f20efc4cfec5e3c30118e1417ea96ed1d93950527"},
{file = "greenlet-3.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ab97cf74045343f6c60a39913fa59710e4bd26a536ce7ab2397adf8b27e67c39"},
{file = "greenlet-3.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5375d2e23184629112ca1ea89a53389dddbffcf417dad40125713d88eb5f96e8"},
{file = "greenlet-3.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:9ee1942ea19550094033c35d25d20726e4f1c40d59545815e1128ac58d416d38"},
{file = "greenlet-3.3.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:60c2ef0f578afb3c8d92ea07ad327f9a062547137afe91f38408f08aacab667f"},
{file = "greenlet-3.3.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a5d554d0712ba1de0a6c94c640f7aeba3f85b3a6e1f2899c11c2c0428da9365"},
{file = "greenlet-3.3.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3a898b1e9c5f7307ebbde4102908e6cbfcb9ea16284a3abe15cab996bee8b9b3"},
{file = "greenlet-3.3.0-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:dcd2bdbd444ff340e8d6bdf54d2f206ccddbb3ccfdcd3c25bf4afaa7b8f0cf45"},
{file = "greenlet-3.3.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5773edda4dc00e173820722711d043799d3adb4f01731f40619e07ea2750b955"},
{file = "greenlet-3.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ac0549373982b36d5fd5d30beb8a7a33ee541ff98d2b502714a09f1169f31b55"},
{file = "greenlet-3.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d198d2d977460358c3b3a4dc844f875d1adb33817f0613f663a656f463764ccc"},
{file = "greenlet-3.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:73f51dd0e0bdb596fb0417e475fa3c5e32d4c83638296e560086b8d7da7c4170"},
{file = "greenlet-3.3.0-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:d6ed6f85fae6cdfdb9ce04c9bf7a08d666cfcfb914e7d006f44f840b46741931"},
{file = "greenlet-3.3.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9125050fcf24554e69c4cacb086b87b3b55dc395a8b3ebe6487b045b2614388"},
{file = "greenlet-3.3.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:87e63ccfa13c0a0f6234ed0add552af24cc67dd886731f2261e46e241608bee3"},
{file = "greenlet-3.3.0-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2662433acbca297c9153a4023fe2161c8dcfdcc91f10433171cf7e7d94ba2221"},
{file = "greenlet-3.3.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3c6e9b9c1527a78520357de498b0e709fb9e2f49c3a513afd5a249007261911b"},
{file = "greenlet-3.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:286d093f95ec98fdd92fcb955003b8a3d054b4e2cab3e2707a5039e7b50520fd"},
{file = "greenlet-3.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c10513330af5b8ae16f023e8ddbfb486ab355d04467c4679c5cfe4659975dd9"},
{file = "greenlet-3.3.0.tar.gz", hash = "sha256:a82bb225a4e9e4d653dd2fb7b8b2d36e4fb25bc0165422a11e48b88e9e6f78fb"},
]
[[package]] [[package]]
name = "h11" name = "h11"
version = "0.16.0" version = "0.16.0"
@ -2426,6 +2476,51 @@ files = [
{file = "soupsieve-2.8.tar.gz", hash = "sha256:e2dd4a40a628cb5f28f6d4b0db8800b8f581b65bb380b97de22ba5ca8d72572f"}, {file = "soupsieve-2.8.tar.gz", hash = "sha256:e2dd4a40a628cb5f28f6d4b0db8800b8f581b65bb380b97de22ba5ca8d72572f"},
] ]
[[package]]
name = "sqlalchemy"
version = "2.0.45"
requires_python = ">=3.7"
summary = "Database Abstraction Library"
groups = ["default"]
dependencies = [
"greenlet>=1; platform_machine == \"win32\" or platform_machine == \"WIN32\" or platform_machine == \"AMD64\" or platform_machine == \"amd64\" or platform_machine == \"x86_64\" or platform_machine == \"ppc64le\" or platform_machine == \"aarch64\"",
"importlib-metadata; python_version < \"3.8\"",
"typing-extensions>=4.6.0",
]
files = [
{file = "sqlalchemy-2.0.45-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2e90a344c644a4fa871eb01809c32096487928bd2038bf10f3e4515cb688cc56"},
{file = "sqlalchemy-2.0.45-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8c8b41b97fba5f62349aa285654230296829672fc9939cd7f35aab246d1c08b"},
{file = "sqlalchemy-2.0.45-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:12c694ed6468333a090d2f60950e4250b928f457e4962389553d6ba5fe9951ac"},
{file = "sqlalchemy-2.0.45-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f7d27a1d977a1cfef38a0e2e1ca86f09c4212666ce34e6ae542f3ed0a33bc606"},
{file = "sqlalchemy-2.0.45-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d62e47f5d8a50099b17e2bfc1b0c7d7ecd8ba6b46b1507b58cc4f05eefc3bb1c"},
{file = "sqlalchemy-2.0.45-cp311-cp311-win32.whl", hash = "sha256:3c5f76216e7b85770d5bb5130ddd11ee89f4d52b11783674a662c7dd57018177"},
{file = "sqlalchemy-2.0.45-cp311-cp311-win_amd64.whl", hash = "sha256:a15b98adb7f277316f2c276c090259129ee4afca783495e212048daf846654b2"},
{file = "sqlalchemy-2.0.45-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b3ee2aac15169fb0d45822983631466d60b762085bc4535cd39e66bea362df5f"},
{file = "sqlalchemy-2.0.45-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba547ac0b361ab4f1608afbc8432db669bd0819b3e12e29fb5fa9529a8bba81d"},
{file = "sqlalchemy-2.0.45-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:215f0528b914e5c75ef2559f69dca86878a3beeb0c1be7279d77f18e8d180ed4"},
{file = "sqlalchemy-2.0.45-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:107029bf4f43d076d4011f1afb74f7c3e2ea029ec82eb23d8527d5e909e97aa6"},
{file = "sqlalchemy-2.0.45-cp312-cp312-win32.whl", hash = "sha256:0c9f6ada57b58420a2c0277ff853abe40b9e9449f8d7d231763c6bc30f5c4953"},
{file = "sqlalchemy-2.0.45-cp312-cp312-win_amd64.whl", hash = "sha256:8defe5737c6d2179c7997242d6473587c3beb52e557f5ef0187277009f73e5e1"},
{file = "sqlalchemy-2.0.45-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fe187fc31a54d7fd90352f34e8c008cf3ad5d064d08fedd3de2e8df83eb4a1cf"},
{file = "sqlalchemy-2.0.45-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:672c45cae53ba88e0dad74b9027dddd09ef6f441e927786b05bec75d949fbb2e"},
{file = "sqlalchemy-2.0.45-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:470daea2c1ce73910f08caf10575676a37159a6d16c4da33d0033546bddebc9b"},
{file = "sqlalchemy-2.0.45-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9c6378449e0940476577047150fd09e242529b761dc887c9808a9a937fe990c8"},
{file = "sqlalchemy-2.0.45-cp313-cp313-win32.whl", hash = "sha256:4b6bec67ca45bc166c8729910bd2a87f1c0407ee955df110d78948f5b5827e8a"},
{file = "sqlalchemy-2.0.45-cp313-cp313-win_amd64.whl", hash = "sha256:afbf47dc4de31fa38fd491f3705cac5307d21d4bb828a4f020ee59af412744ee"},
{file = "sqlalchemy-2.0.45-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:83d7009f40ce619d483d26ac1b757dfe3167b39921379a8bd1b596cf02dab4a6"},
{file = "sqlalchemy-2.0.45-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d8a2ca754e5415cde2b656c27900b19d50ba076aa05ce66e2207623d3fe41f5a"},
{file = "sqlalchemy-2.0.45-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7f46ec744e7f51275582e6a24326e10c49fbdd3fc99103e01376841213028774"},
{file = "sqlalchemy-2.0.45-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:883c600c345123c033c2f6caca18def08f1f7f4c3ebeb591a63b6fceffc95cce"},
{file = "sqlalchemy-2.0.45-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2c0b74aa79e2deade948fe8593654c8ef4228c44ba862bb7c9585c8e0db90f33"},
{file = "sqlalchemy-2.0.45-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8a420169cef179d4c9064365f42d779f1e5895ad26ca0c8b4c0233920973db74"},
{file = "sqlalchemy-2.0.45-cp314-cp314-win32.whl", hash = "sha256:e50dcb81a5dfe4b7b4a4aa8f338116d127cb209559124f3694c70d6cd072b68f"},
{file = "sqlalchemy-2.0.45-cp314-cp314-win_amd64.whl", hash = "sha256:4748601c8ea959e37e03d13dcda4a44837afcd1b21338e637f7c935b8da06177"},
{file = "sqlalchemy-2.0.45-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd337d3526ec5298f67d6a30bbbe4ed7e5e68862f0bf6dd21d289f8d37b7d60b"},
{file = "sqlalchemy-2.0.45-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9a62b446b7d86a3909abbcd1cd3cc550a832f99c2bc37c5b22e1925438b9367b"},
{file = "sqlalchemy-2.0.45-py3-none-any.whl", hash = "sha256:5225a288e4c8cc2308dbdd874edad6e7d0fd38eac1e9e5f23503425c8eee20d0"},
{file = "sqlalchemy-2.0.45.tar.gz", hash = "sha256:1632a4bda8d2d25703fdad6363058d882541bdaaee0e5e3ddfa0cd3229efce88"},
]
[[package]] [[package]]
name = "stack-data" name = "stack-data"
version = "0.6.3" version = "0.6.3"
@ -2519,7 +2614,7 @@ name = "typing-extensions"
version = "4.15.0" version = "4.15.0"
requires_python = ">=3.9" requires_python = ">=3.9"
summary = "Backported and Experimental Type Hints for Python 3.9+" summary = "Backported and Experimental Type Hints for Python 3.9+"
groups = ["dev", "nb"] groups = ["default", "dev", "nb"]
files = [ files = [
{file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"},
{file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"},

View File

@ -1,11 +1,11 @@
[project] [project]
name = "polluck-blockchain" name = "dopt-pollublock-blockchain"
version = "0.1.0" version = "0.1.0"
description = "blockchain module of the research project >Polluck<" description = "blockchain module of the research project >Polluck<"
authors = [ authors = [
{name = "d-opt GmbH, resp. Florian Foerster", email = "f.foerster@d-opt.com"}, {name = "d-opt GmbH, resp. Florian Foerster", email = "f.foerster@d-opt.com"},
] ]
dependencies = ["dopt-basics>=0.2.4"] dependencies = ["dopt-basics>=0.2.4", "sqlalchemy>=2.0.45"]
requires-python = ">=3.11" requires-python = ">=3.11"
readme = "README.md" readme = "README.md"
license = {text = "LicenseRef-Proprietary"} license = {text = "LicenseRef-Proprietary"}

View File

@ -27,8 +27,8 @@ openssl_lib = OPENSSL_DIR / "lib"
assert openssl_lib.exists() assert openssl_lib.exists()
ext = Extension( ext = Extension(
name="polluck_blockchain.placeholder_native", name="dopt_pollublock_blockchain.blockchain",
sources=["src/polluck_blockchain/placeholder_native.pyx"], sources=["src/dopt_pollublock_blockchain/blockchain.pyx"],
include_dirs=[str(openssl_include)], include_dirs=[str(openssl_include)],
library_dirs=[str(openssl_lib)], library_dirs=[str(openssl_lib)],
libraries=["libssl", "libcrypto"], libraries=["libssl", "libcrypto"],

File diff suppressed because one or more lines are too long

View File

@ -5,40 +5,39 @@
from __future__ import annotations from __future__ import annotations
import datetime import datetime
import hashlib from pathlib import Path
import struct
import time
import cython
import dopt_basics.datetime import dopt_basics.datetime
import sqlalchemy as sql
from dopt_pollublock_blockchain import db
# from cython.cimports.cpython.ref import Py_INCREF, PyObject
from polluck_blockchain.block cimport Block from dopt_pollublock_blockchain.block cimport Block
from libcpp.unordered_map cimport unordered_map from libcpp.unordered_map cimport unordered_map
from libcpp.vector cimport vector
from libcpp.string cimport string from libcpp.string cimport string
from libc.stdint cimport uint64_t from libc.stdint cimport uint64_t
from libc.stdlib cimport malloc, free from libc.stdlib cimport malloc, free
from libc.string cimport memcpy from libc.string cimport memcpy
from cython.operator import postincrement, dereference from cython.operator import postincrement, dereference
cimport polluck_blockchain.openssl_evp as ossl cimport dopt_pollublock_blockchain.openssl_evp as ossl
# from cython.parallel cimport prange
ctypedef unsigned long ULong ctypedef unsigned long ULong
ctypedef unordered_map[uint64_t, Block*] BcHashmap ctypedef unordered_map[uint64_t, Block*] BcHashmap
cdef const size_t NONCE_OFFSET = <size_t>16 cdef const size_t NONCE_OFFSET = <size_t>16
cdef timestamp_to_datetime(uint64_t ts): cdef timestamp_to_datetime(uint64_t ts):
return datetime.datetime.fromtimestamp(float(ts), dopt_basics.datetime.TIMEZONE_UTC) return datetime.datetime.fromtimestamp(float(ts), dopt_basics.datetime.TIMEZONE_UTC)
cdef uint64_t current_timestamp_integer(): cdef uint64_t current_timestamp_integer():
cdef uint64_t ts cdef uint64_t ts
dt = dopt_basics.datetime.current_time_tz(cut_microseconds=True) dt = dopt_basics.datetime.current_time_tz(cut_microseconds=True)
ts = <uint64_t>int(dt.timestamp()) ts = <uint64_t>int(dt.timestamp())
return ts return ts
# cdef float_to_bytes(double num):
# return struct.pack(">d", num)
cdef int serialize_uint32(unsigned char* out, unsigned int v) except -1 nogil: cdef int serialize_uint32(unsigned char* out, unsigned int v) except -1 nogil:
out[0] = (v >> 24) & 0xFF out[0] = (v >> 24) & 0xFF
@ -46,6 +45,7 @@ cdef int serialize_uint32(unsigned char* out, unsigned int v) except -1 nogil:
out[2] = (v >> 8) & 0xFF out[2] = (v >> 8) & 0xFF
out[3] = v & 0xFF out[3] = v & 0xFF
cdef int serialize_uint64(unsigned char* out, unsigned long long v) except -1 nogil: cdef int serialize_uint64(unsigned char* out, unsigned long long v) except -1 nogil:
out[0] = (v >> 56) & 0xFF out[0] = (v >> 56) & 0xFF
out[1] = (v >> 48) & 0xFF out[1] = (v >> 48) & 0xFF
@ -71,6 +71,7 @@ cdef inline bint has_leading_zero_bits(const unsigned char *digest, int num_bits
return True return True
cdef inline bint has_leading_zero_bytes(const unsigned char *digest, int num_bytes) nogil: cdef inline bint has_leading_zero_bytes(const unsigned char *digest, int num_bytes) nogil:
cdef int i, full_bytes = num_bytes // 8 cdef int i, full_bytes = num_bytes // 8
cdef int rem_bits = num_bytes % 8 cdef int rem_bits = num_bytes % 8
@ -98,13 +99,13 @@ cdef class PyBlock:
if not from_ptr: if not from_ptr:
self.BlockC = new Block( self.BlockC = new Block(
index, index,
int(datetime.datetime(2025, 12, 1, 12, 0, 0).timestamp()), current_timestamp_integer(),
nonce, nonce,
data.encode("UTF-8"), data.encode("UTF-8"),
previous_hash.encode("UTF-8"), previous_hash.encode("UTF-8"),
"".encode("UTF-8"), "".encode("UTF-8"),
) )
if self.BlockC == NULL: if self.BlockC is NULL:
raise MemoryError() raise MemoryError()
self.ptr_owner = True self.ptr_owner = True
@ -133,11 +134,26 @@ cdef class PyBlock:
return py_block return py_block
def __repr__(self): def __repr__(self):
return f"PyBlock({self.index, self.timestamp, self.nonce, self.data})" return (
f"PyBlock(\n\tIndex:\t\t{self.index}\n\ttimestamp:\t{self.timestamp}\n\tnonce:\t\t{self.nonce}\n\t"
f"Prev Hash:\t{self.prev_hash}\n\tHash:\t\t{self.hash}\n\tData:\t\t{self.data}\n)"
)
def __str__(self): def __str__(self):
return self.__repr__() return self.__repr__()
def serialize_dict(self):
contents = {}
contents["index"] = self.BlockC.index
contents["timestamp"] = self.BlockC.timestamp
contents["nonce"] = self.BlockC.nonce
contents["previous_hash"] = self.prev_hash
contents["hash"] = self.hash
contents["data"] = self.data
return contents
# Python public API # Python public API
@property @property
def index(self): def index(self):
@ -181,7 +197,7 @@ cdef class PyBlock:
try: try:
digest = perform_hash_c(self.BlockC, &digest_size) digest = perform_hash_c(self.BlockC, &digest_size)
if digest == NULL: if digest is NULL:
raise MemoryError() raise MemoryError()
# TODO out: hash assignment in blockchain # TODO out: hash assignment in blockchain
self.BlockC.hash = bytes(digest[:digest_size]).hex().encode("UTF-8") self.BlockC.hash = bytes(digest[:digest_size]).hex().encode("UTF-8")
@ -205,7 +221,7 @@ cdef unsigned char* bytes_serialize_c(Block *block, size_t *size) nogil:
) )
buf = <unsigned char*>malloc(size[0] * sizeof(unsigned char)) buf = <unsigned char*>malloc(size[0] * sizeof(unsigned char))
if buf == NULL: if buf is NULL:
return NULL return NULL
serialize_uint64(buf + pos, block.index) serialize_uint64(buf + pos, block.index)
@ -233,10 +249,10 @@ cdef unsigned char* bytes_serialize_c(Block *block, size_t *size) nogil:
cdef unsigned char* SHA256_digest(const void *data, size_t data_size, size_t *digest_size) nogil: cdef unsigned char* SHA256_digest(const void *data, size_t data_size, size_t *digest_size) nogil:
cdef ossl.EVP_MD_CTX *ctx = ossl.EVP_MD_CTX_new() cdef ossl.EVP_MD_CTX *ctx = ossl.EVP_MD_CTX_new()
if ctx == NULL: if ctx is NULL:
return NULL return NULL
cdef const ossl.EVP_MD *algo = ossl.EVP_sha256() cdef const ossl.EVP_MD *algo = ossl.EVP_sha256()
if algo == NULL: if algo is NULL:
return NULL return NULL
cdef: cdef:
@ -263,11 +279,11 @@ cdef unsigned char* perform_hash_c(Block *block, size_t *digest_size) nogil:
unsigned char *digest unsigned char *digest
serialize_res = bytes_serialize_c(block, &serialize_size) serialize_res = bytes_serialize_c(block, &serialize_size)
if serialize_res == NULL: if serialize_res is NULL:
return NULL return NULL
digest = SHA256_digest(serialize_res, serialize_size, digest_size) digest = SHA256_digest(serialize_res, serialize_size, digest_size)
free(serialize_res) free(serialize_res)
if digest == NULL: if digest is NULL:
return NULL return NULL
return digest return digest
@ -305,34 +321,116 @@ cdef int mine_block(Block *block, unsigned int difficulty, uint64_t *nonce_solut
cdef class Blockchain: cdef class Blockchain:
cdef unsigned int _difficulty cdef:
cdef uint64_t _index unsigned int _difficulty
cdef BcHashmap *_chain uint64_t _index
cdef bint _genesis_done BcHashmap *_chain_map
vector[Block*] *_chain
bint _genesis_done
bint _loaded
readonly object db_path
readonly object _engine
def __cinit__(self): def __cinit__(self, *args, **kwargs):
self._difficulty = 26 self._difficulty = 26
self._index = <uint64_t>0 self._index = <uint64_t>0
self._genesis_done = <bint>0 self._genesis_done = False
self._chain = new unordered_map[uint64_t, Block*]() self._loaded = False
if self._chain is NULL: self._chain_map = new unordered_map[uint64_t, Block*]()
self._chain = new vector[Block*]()
if self._chain_map is NULL:
raise MemoryError("Could not allocate hasmap") raise MemoryError("Could not allocate hasmap")
if self._chain is NULL:
raise MemoryError("Could not allocate vector")
def __init__(self, *args, **kwargs): def __init__(self, db_path):
# self.db_path = db_path self.db_path = Path(db_path).resolve()
pass if not self.db_path.parent.exists():
raise FileNotFoundError(
"The parent directory of the provided database path does not exist"
)
self._engine = sql.create_engine(f"sqlite:///{str(self.db_path)}")
db.metadata_blockchain.create_all(self._engine)
def __dealloc__(self): def __dealloc__(self):
# ownership is typically not transferred from the Blockchain extension class # ownership is typically not transferred from the Blockchain extension class
cdef BcHashmap.iterator it = self._chain.begin() cdef BcHashmap.iterator it = self._chain_map.begin()
if self._chain is not NULL: if self._chain_map is not NULL:
while it != self._chain.end(): while it != self._chain_map.end():
del dereference(it).second del dereference(it).second
postincrement(it) postincrement(it)
del self._chain_map
self._chain_map = NULL
if self._chain is not NULL:
del self._chain del self._chain
self._chain = NULL self._chain = NULL
cdef Block* get_block_c(self, uint64_t idx) nogil:
if idx > self._index:
return NULL
return self._chain_map[0][idx]
cdef void add_block_from_loading(self, Block *block) nogil:
self._chain[0].push_back(block)
self._chain_map[0][block.index] = block
self._index = block.index
if not self._genesis_done:
self._genesis_done = True
cdef int add_block(self, Block *block) nogil:
cdef:
uint64_t mined_nonce
size_t digest_size
unsigned char *sha256_digest
# mine block
if mine_block(block, self._difficulty, &mined_nonce) != 0:
return 1
block.nonce = mined_nonce
# hash block, add hash to block, add block to blockchain hashmap
sha256_digest = perform_hash_c(block, &digest_size)
with gil:
block.hash = bytes(sha256_digest[:digest_size]).hex().encode("UTF-8")
free(sha256_digest)
self._chain[0].push_back(block)
self._chain_map[0][block.index] = block
if self._genesis_done:
self._index += 1
return 0
cdef string hash_data(self, data):
cdef:
string data_str
unsigned char *data_digest
size_t digest_size
data_str = data.encode("UTF-8")
data_digest = SHA256_digest(data_str.c_str(), data_str.size(), &digest_size)
if data_digest is NULL:
raise RuntimeError("Failed to hash data")
data_str = bytes(data_digest[:digest_size]).hex().encode("UTF-8")
free(data_digest)
return data_str
cdef load_from_batch(self, batch):
cdef Block *block
for entry in batch:
block = new Block(
entry[0],
entry[1],
entry[2],
entry[5].encode("UTF-8"),
entry[3].encode("UTF-8"),
entry[4].encode("UTF-8"),
)
self.add_block_from_loading(block)
# // Python public API
def __len__(self): def __len__(self):
return self._index + 1 return self._index + 1
@ -356,73 +454,55 @@ cdef class Blockchain:
def index(self): def index(self):
return self._index return self._index
def print_key_value_pair(self): def _print_key_value_pair(self):
cdef BcHashmap.iterator it = self._chain.begin() cdef BcHashmap.iterator it = self._chain_map.begin()
cdef Block *block cdef Block *block
while it != self._chain.end(): while it != self._chain_map.end():
print(dereference(it).first) print(dereference(it).first)
block = dereference(it).second block = dereference(it).second
py_block = PyBlock.from_ptr(block) py_block = PyBlock.from_ptr(block)
print(py_block) print(py_block)
postincrement(it) postincrement(it)
cdef Block* get_block_c(self, uint64_t idx) nogil: def print_blocks(self, max_num):
if idx > self._index:
return NULL
return self._chain[0][idx]
cdef int add_block(self, Block *block) nogil:
cdef: cdef:
uint64_t mined_nonce Block *block
size_t digest_size int max_nummber = max_num
unsigned char *sha256_digest int idx, num = 0
# mine block
if mine_block(block, self._difficulty, &mined_nonce) != 0:
return 1
block.nonce = mined_nonce
# hash block, add hash to block, add block to blockchain hashmap
sha256_digest = perform_hash_c(block, &digest_size)
with gil:
block.hash = bytes(sha256_digest[:digest_size]).hex().encode("UTF-8")
free(sha256_digest)
self._chain[0][block.index] = block
if self._genesis_done: if max_num <= 0:
self._index += 1 raise ValueError("Maximum number must be greater than 0")
for idx in range(self._chain[0].size()):
block = self._chain[0][idx]
py_block = PyBlock.from_ptr(block)
print(py_block)
num += 1
if num == max_nummber:
break
return 0
# // Python public API
def get_block(self, idx): def get_block(self, idx):
if idx < 0 or idx > self._index: if idx < 0 or idx > self._index:
raise IndexError("Index value is out of bounds") raise IndexError("Index value is out of bounds")
cdef Block *block = self.get_block_c(idx) cdef Block *block = self.get_block_c(idx)
if block == NULL: if block is NULL:
raise IndexError("Provided index not found") raise IndexError("Provided index not found")
return PyBlock.from_ptr(block, owner=False) return PyBlock.from_ptr(block, owner=False)
cdef string hash_data(self, data):
cdef:
string data_str
unsigned char *data_digest
size_t digest_size
data_str = data.encode("UTF-8")
data_digest = SHA256_digest(data_str.c_str(), data_str.size(), &digest_size)
if data_digest == NULL:
raise RuntimeError("Failed to hash data")
data_str = bytes(data_digest[:digest_size]).hex().encode("UTF-8")
free(data_digest)
return data_str
def create_genesis_block(self): def create_genesis_block(self):
if self._genesis_done:
raise RuntimeError(
("Blockchain already has a genesis block. "
"Either it was created or loaded.")
)
genesis_prev_hash = ("0" * 64).encode("UTF-8") genesis_prev_hash = ("0" * 64).encode("UTF-8")
cdef string data_str = self.hash_data("Genesis Block") cdef string data_str = self.hash_data("Genesis Block")
cdef Block *block = new Block( cdef Block *block = new Block(
self._index, self._index,
int(datetime.datetime(2025, 12, 1, 12, 0, 0).timestamp()), current_timestamp_integer(),
0, 0,
data_str, data_str,
genesis_prev_hash, genesis_prev_hash,
@ -449,12 +529,12 @@ cdef class Blockchain:
data_str = self.hash_data(data) data_str = self.hash_data(data)
prev_block = self.get_block_c(self._index) prev_block = self.get_block_c(self._index)
prev_hash = prev_block.prev_hash prev_hash = prev_block.hash
new_idx = self._index + 1 new_idx = self._index + 1
cdef Block *block = new Block( cdef Block *block = new Block(
new_idx, new_idx,
int(datetime.datetime(2025, 12, 1, 12, 0, 0).timestamp()), current_timestamp_integer(),
0, 0,
data_str, data_str,
prev_hash, prev_hash,
@ -464,3 +544,87 @@ cdef class Blockchain:
if res != 0: if res != 0:
raise RuntimeError("Could not mine block. No nonce found") raise RuntimeError("Could not mine block. No nonce found")
def validate(self):
cdef:
Block *block
Block *prev_block = NULL
int idx = 0
unsigned char *digest
size_t digest_size
for idx in range(self._chain[0].size()):
block = self._chain[0][idx]
py_bytes = bytes.fromhex(block.hash.decode("UTF-8"))
digest = perform_hash_c(block, &digest_size)
py_bytes_rehashed = bytes(digest[:digest_size])
free(digest)
if py_bytes != py_bytes_rehashed:
print(f"Index {idx}: Hashes to not match. Abort.")
return False
if prev_block is not NULL:
if prev_block.hash != block.prev_hash:
print(
(f"Index {idx}: Hash mismatch. Hash of previous block does not "
"match the saved one in the current block. Abort.")
)
return False
prev_block = block
return True
def get_saving_entries(self, max_idx):
entries = []
cdef:
Block *block
int idx = 0
int _max_idx
if max_idx is None:
_max_idx = -1
else:
_max_idx = max_idx
for idx in range(self._chain[0].size()):
if idx <= _max_idx:
continue
block = self._chain[0][idx]
contents = {}
contents["index"] = block.index
contents["timestamp"] = block.timestamp
contents["nonce"] = block.nonce
contents["previous_hash"] = block.prev_hash.decode("UTF-8")
contents["hash"] = block.hash.decode("UTF-8")
contents["data"] = block.data.decode("UTF-8")
entries.append(contents)
return entries
def save(self):
# get max index from db
# only retrieve indices greater than max value
stmt = sql.select(sql.func.max(db.blocks.c.index))
with self._engine.connect() as conn:
result = conn.execute(stmt)
max_value = result.scalar()
entries = self.get_saving_entries(max_value)
if not entries:
return
with self._engine.begin() as con:
con.execute(sql.insert(db.blocks), entries)
def close_db_connections(self):
self._engine.dispose()
def load(self, batch_size):
if self._loaded:
raise RuntimeError("Blockchain was already loaded")
with self._engine.connect() as con:
res = con.execute(sql.select(db.blocks).order_by(db.blocks.c.index.asc()))
for batch in res.partitions(batch_size):
self.load_from_batch(batch)
self._loaded = True

View File

@ -0,0 +1,16 @@
from __future__ import annotations
import sqlalchemy as sql
metadata_blockchain: sql.MetaData = sql.MetaData()
blocks = sql.Table(
"blocks",
metadata_blockchain,
sql.Column("index", sql.BigInteger, primary_key=True),
sql.Column("timestamp", sql.BigInteger, nullable=False),
sql.Column("nonce", sql.BigInteger, nullable=False),
sql.Column("previous_hash", sql.String(64), nullable=False),
sql.Column("hash", sql.String(64), nullable=False),
sql.Column("data", sql.String(64), nullable=False),
)