more core implementation in Cython

This commit is contained in:
Florian Förster 2025-12-11 16:01:29 +01:00
parent d3d5d04ef2
commit 68356e06a5
2 changed files with 3509 additions and 737 deletions

File diff suppressed because it is too large Load Diff

View File

@ -16,92 +16,40 @@ import dopt_basics.datetime
from polluck_blockchain.block cimport Block from polluck_blockchain.block cimport Block
from libcpp.unordered_map cimport unordered_map from libcpp.unordered_map cimport unordered_map
from libcpp.string cimport string from libcpp.string cimport string
from libc.stdint cimport uint64_t
ctypedef unsigned long ULong ctypedef unsigned long ULong
ctypedef unordered_map[uint64_t, Block*] BcHashmap
# cdef struct PyStringC:
# char* ptr
# Py_ssize_t length
# cdef PyStringC_to_unicode(PyStringC string):
# return string.ptr[: string.length].decode("UTF-8", "strict")
# cdef PyStringC_to_bytes(PyStringC string):
# return <bytes>string.ptr[: string.length]
cdef timestamp_to_datetime(double ts): cdef timestamp_to_datetime(double ts):
return datetime.datetime.fromtimestamp(ts, dopt_basics.datetime.TIMEZONE_UTC) return datetime.datetime.fromtimestamp(ts, dopt_basics.datetime.TIMEZONE_UTC)
# cdef class StringHolder:
# cdef readonly PyStringC c
# cdef object _py_data
# def __cinit__(self, data: str):
# """accepting Python str
# Parameters
# ----------
# data : str
# _description_
# """
# if not isinstance(data, str):
# raise TypeError("Data not 'str'")
# self._py_data = data.encode("UTF-8")
# self.c = PyStringC(self._py_data, len(self._py_data))
# @property
# def py_string(self):
# return PyStringC_to_unicode(self.c)
# @property
# def py_bytes(self):
# return PyStringC_to_bytes(self.c)
# BlockC = cython.struct(
# index=cython.ulonglong,
# timestamp=cython.double,
# data=PyStringC,
# previous_hash=PyStringC,
# nonce=cython.ulonglong,
# )
cdef float_to_bytes(double num): cdef float_to_bytes(double num):
return struct.pack(">d", num) return struct.pack(">d", num)
# cdef struct BlockC:
cdef class PyBlock:
cdef:
# ULong _index # ULong _index
# double _timestamp # double _timestamp
# string _data # string _data
# string _prev_hash # string _prev_hash
# ULong _nonce # ULong _nonce
# string _hash # string _hash
Block *BlockC
bint ptr_owner
cdef class BlockPy:
cdef:
ULong _index
double _timestamp
string _data
string _prev_hash
ULong _nonce
string _hash
Block* Block
def __cinit__(self, def __cinit__(self,
index: int, index,
data: str, nonce,
previous_hash: str, data,
nonce: int, previous_hash,
from_ptr=False,
): ):
self.Block = new Block( self.ptr_owner = False
if not from_ptr:
self.BlockC = new Block(
index, index,
datetime.datetime(2025, 12, 1, 12, 0, 0).timestamp(), datetime.datetime(2025, 12, 1, 12, 0, 0).timestamp(),
nonce, nonce,
@ -109,25 +57,13 @@ cdef class BlockPy:
previous_hash.encode("UTF-8"), previous_hash.encode("UTF-8"),
"".encode("UTF-8"), "".encode("UTF-8"),
) )
if self.BlockC == NULL:
raise MemoryError("Allocation of block object failed")
def get_data(self): self.ptr_owner = True
return self.Block.data.decode("UTF-8")
# def __init__( def __init__(self, *args, **kwargs):
# self, pass
# index: int,
# data: str,
# previous_hash: str,
# nonce: int,
# ):
# self.C = new BlockC(
# index,
# datetime.datetime(2025, 12, 1, 12, 0, 0).timestamp(),
# data.encode("UTF-8"),
# previous_hash.encode("UTF-8"),
# nonce,
# "".encode("UTF-8"),
# )
# self._index = index # self._index = index
# # self._timestamp = dopt_basics.datetime.current_time_tz().timestamp() # # self._timestamp = dopt_basics.datetime.current_time_tz().timestamp()
# self._timestamp = datetime.datetime(2025, 12, 1, 12, 0, 0).timestamp() # self._timestamp = datetime.datetime(2025, 12, 1, 12, 0, 0).timestamp()
@ -136,6 +72,26 @@ cdef class BlockPy:
# self._nonce = nonce # self._nonce = nonce
# self._hash = "".encode("UTF-8") # self._hash = "".encode("UTF-8")
def __dealloc__(self):
if self.BlockC is not NULL and self.ptr_owner is True:
del self.BlockC
self.BlockC = NULL
@staticmethod
cdef PyBlock from_ptr(Block *block, bint owner=False):
cdef PyBlock py_block = PyBlock.__new__(
PyBlock,
block.index,
block.nonce,
block.data,
block.prev_hash,
True,
)
py_block.BlockC = block
py_block.ptr_owner = owner
return py_block
# cpdef _perform_hash(self): # cpdef _perform_hash(self):
# parts = bytearray() # parts = bytearray()
# parts.extend(self._index.to_bytes(8, "big")) # parts.extend(self._index.to_bytes(8, "big"))
@ -152,51 +108,124 @@ cdef class BlockPy:
# def compute_hash(self): # def compute_hash(self):
# return self._perform_hash().hexdigest() # return self._perform_hash().hexdigest()
# # Python public API # Python public API
# @property @property
# def index(self): def index(self):
# return self._index return self.BlockC.index
# @property @property
# def timestamp(self): def timestamp(self):
# return timestamp_to_datetime(self._timestamp) return timestamp_to_datetime(self.BlockC.timestamp)
# @property @property
# def data(self): def data(self):
# return self._data.decode("UTF-8") return self.BlockC.data.decode("UTF-8")
# @property @property
# def prev_hash(self): def prev_hash(self):
# return self._prev_hash.decode("UTF-8") return self.BlockC.prev_hash.decode("UTF-8")
# @property @property
# def nonce(self): def nonce(self):
# return self._nonce return self.BlockC.nonce
# @nonce.setter # @nonce.setter
# def nonce(self, value: int): # def nonce(self, value: int):
# self._nonce = value # self._nonce = value
# @property @property
# def hash(self): def hash(self):
# return self._hash.decode("UTF-8") return self.BlockC.hash.decode("UTF-8")
# @hash.setter # @hash.setter
# def hash(self, value: str): # def hash(self, value):
# if not isinstance(value, str): # if not isinstance(value, str):
# raise TypeError("No string") # raise TypeError("No string")
# self._hash = value.encode("UTF-8") # self._hash = value.encode("UTF-8")
# cdef void add_block(BcHashmap *map, Block *block):
# map[block.index] = block
# cdef class Blockchain:
# cdef int _difficulty
# cdef size_t _index
# cdef unordered_map[size_t, int] _chain
# def __cinit__(self): cdef class Blockchain:
# self._difficulty = 1 cdef int _difficulty
# self._index = <size_t>0 cdef uint64_t _index
# # self._chain = new unordered_map[size_t, Block]() cdef BcHashmap *_chain
cdef bint _genesis_done
def __cinit__(self):
self._difficulty = 1
self._index = <uint64_t>0
self._genesis_done = <bint>0
self._chain = new unordered_map[uint64_t, Block*]()
if self._chain is NULL:
raise MemoryError("Could not allocate hasmap")
def __init__(self, *args, **kwargs):
pass
def __dealloc__(self):
if self._chain is not NULL:
del self._chain
self._chain = NULL
def __len__(self):
return self._index + 1
@property
def genesis_done(self):
return self._genesis_done
@property
def index(self):
return self._index
# TODO error handling
cdef Block* get_block_c(self, uint64_t idx):
return self._chain[0][idx]
def get_block(self, idx):
if idx < 0 or idx > self._index:
raise IndexError("Index value is out of bounds")
cdef Block *block = self.get_block_c(idx)
return PyBlock.from_ptr(block, owner=False)
cdef void add_block(self, Block *block):
self._chain[0][block.index] = block
if self._genesis_done:
self._index += 1
def create_genesis_block(self):
cdef Block *block = new Block(
self._index,
datetime.datetime(2025, 12, 1, 12, 0, 0).timestamp(),
0,
"Genesis Block".encode("UTF-8"),
"0".encode("UTF-8"),
"".encode("UTF-8"),
)
block.hash = <string>"dummy hash".encode("UTF-8")
self.add_block(block)
self._genesis_done = <bint>1
def new_block(self, data):
if not self._genesis_done:
raise RuntimeError("Create a genesis block first.")
cdef Block *prev_block = self.get_block_c(self._index)
cdef string prev_hash = prev_block.prev_hash
cdef uint64_t new_idx = self._index + 1
cdef Block *block = new Block(
new_idx,
datetime.datetime(2025, 12, 1, 12, 0, 0).timestamp(),
0,
data.encode("UTF-8"),
prev_hash,
"".encode("UTF-8"),
)
self.add_block(block)
# def __init__( # def __init__(
# self, # self,