Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
5d58c2c
Clean implementation of core setup and contract tests
aniket866 Feb 14, 2026
da8d108
Update consensus/__init__.py
aniket866 Feb 14, 2026
dfbcd95
Enhance mine_block with timeout and cancellation support
aniket866 Feb 14, 2026
8fb795d
Code rabbit follow-up
aniket866 Feb 14, 2026
c51717a
Code rabbit follow-up
aniket866 Feb 14, 2026
d06f2b9
Code rabbit follow-up
aniket866 Feb 14, 2026
b1639cb
Implement Blockchain class for managing state and blocks
aniket866 Feb 14, 2026
2d43e2a
Apply suggestion from @coderabbitai[bot]
aniket866 Feb 14, 2026
1349d3a
Enhance contract execution with improved safety measures
aniket866 Feb 14, 2026
a07c68c
Enhance Transaction class with error handling and data preservation
aniket866 Feb 14, 2026
ff89df6
Replace print statements with logging calls
aniket866 Feb 14, 2026
d2135fd
Update test_contract.py
aniket866 Feb 14, 2026
a9c965a
Update test_contract.py
aniket866 Feb 14, 2026
d0e44a6
Update setup.py
aniket866 Feb 14, 2026
1c46fca
Update main.py
aniket866 Feb 14, 2026
6fbb613
fixing-all-code-rabbit-suggestions
aniket866 Feb 14, 2026
d49a985
fixing-code-rabbit-suggestions
aniket866 Feb 14, 2026
b32d077
fixing-code-rabbit-suggestions
aniket866 Feb 14, 2026
0f40e6a
core-contract-base-setup
aniket866 Feb 14, 2026
e6a4db9
Update core/chain.py
aniket866 Feb 14, 2026
d5cf36b
Update core/chain.py
aniket866 Feb 14, 2026
60cd231
Update core/contract.py
aniket866 Feb 14, 2026
c7cf0b1
Code rabbit follow-up
aniket866 Feb 14, 2026
fc938cd
updating
aniket866 Feb 17, 2026
3da2997
fixed header
aniket866 Feb 17, 2026
cccf341
Update main.py
aniket866 Feb 17, 2026
2231368
Update core/block.py
aniket866 Feb 17, 2026
dd209dc
Update main.py
aniket866 Feb 17, 2026
8252bc5
Update core/block.py
aniket866 Feb 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import sys
import os

sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
3 changes: 3 additions & 0 deletions consensus/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .pow import mine_block, calculate_hash, MiningExceededError

__all__ = ["mine_block", "calculate_hash", "MiningExceededError"]
75 changes: 75 additions & 0 deletions consensus/pow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import json
import time
import hashlib


class MiningExceededError(Exception):
"""Raised when max_nonce, timeout, or cancellation is exceeded during mining."""


def calculate_hash(block_dict):
"""Calculates SHA256 hash of a block header."""
block_string = json.dumps(block_dict, sort_keys=True).encode("utf-8")
return hashlib.sha256(block_string).hexdigest()


def mine_block(
block,
difficulty=4,
max_nonce=10_000_000,
timeout_seconds=None,
logger=None,
progress_callback=None
):
"""Mines a block using Proof-of-Work without mutating input block until success."""

if not isinstance(difficulty, int) or difficulty <= 0:
raise ValueError("Difficulty must be a positive integer.")

target = "0" * difficulty
local_nonce = 0
header_dict = block.to_header_dict() # Construct header dict once outside loop
start_time = time.monotonic()

if logger:
logger.info(
"Mining block %s (Difficulty: %s)",
block.index,
difficulty,
)

while True:

# Enforce max_nonce limit before hashing
if local_nonce >= max_nonce:
if logger:
logger.warning("Max nonce exceeded during mining.")
raise MiningExceededError("Mining failed: max_nonce exceeded")

# Enforce timeout if specified
if timeout_seconds is not None and (time.monotonic() - start_time) > timeout_seconds:
if logger:
logger.warning("Mining timeout exceeded.")
raise MiningExceededError("Mining failed: timeout exceeded")

header_dict["nonce"] = local_nonce
block_hash = calculate_hash(header_dict)

# Check difficulty target
if block_hash.startswith(target):
block.nonce = local_nonce # Assign only on success
block.hash = block_hash
if logger:
logger.info("Success! Hash: %s", block_hash)
return block

# Allow cancellation via progress callback (pass nonce explicitly)
if progress_callback:
should_continue = progress_callback(local_nonce, block_hash)
if should_continue is False:
if logger:
logger.info("Mining cancelled via progress_callback.")
raise MiningExceededError("Mining cancelled")

# Increment nonce after attempt
local_nonce += 1
13 changes: 13 additions & 0 deletions core/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from .block import Block
from .chain import Blockchain
from .transaction import Transaction
from .state import State
from .contract import ContractMachine

__all__ = [
"Block",
"Blockchain",
"Transaction",
"State",
"ContractMachine",
]
105 changes: 105 additions & 0 deletions core/block.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import time
import hashlib
import json
from typing import List, Optional
from core.transaction import Transaction


def _sha256(data: str) -> str:
return hashlib.sha256(data.encode()).hexdigest()


def _calculate_merkle_root(transactions: List[Transaction]) -> Optional[str]:
if not transactions:
return None

# Hash each transaction deterministically
tx_hashes = [
_sha256(json.dumps(tx.to_dict(), sort_keys=True))
for tx in transactions
]

# Build Merkle tree
while len(tx_hashes) > 1:
if len(tx_hashes) % 2 != 0:
tx_hashes.append(tx_hashes[-1]) # duplicate last if odd

new_level = []
for i in range(0, len(tx_hashes), 2):
combined = tx_hashes[i] + tx_hashes[i + 1]
new_level.append(_sha256(combined))

tx_hashes = new_level

return tx_hashes[0]


class Block:
def __init__(
self,
index: int,
previous_hash: str,
transactions: Optional[List[Transaction]] = None,
timestamp: Optional[float] = None,
difficulty: Optional[int] = None,
):
self.index = index
self.previous_hash = previous_hash
self.transactions: List[Transaction] = transactions or []

# Deterministic timestamp (ms)
self.timestamp: int = (
round(time.time() * 1000)
if timestamp is None
else int(timestamp)
)

self.difficulty: Optional[int] = difficulty
self.nonce: int = 0
self.hash: Optional[str] = None

# NEW: compute merkle root once
self.merkle_root: Optional[str] = _calculate_merkle_root(self.transactions)

# -------------------------
# HEADER (used for mining)
# -------------------------
def to_header_dict(self):
return {
"index": self.index,
"previous_hash": self.previous_hash,
"merkle_root": self.merkle_root,
"timestamp": self.timestamp,
"difficulty": self.difficulty,
"nonce": self.nonce,
}

# -------------------------
# BODY (transactions only)
# -------------------------
def to_body_dict(self):
return {
"transactions": [
tx.to_dict() for tx in self.transactions
]
}

# -------------------------
# FULL BLOCK
# -------------------------
def to_dict(self):
return {
**self.to_header_dict(),
**self.to_body_dict(),
"hash": self.hash,
}

# -------------------------
# HASH CALCULATION
# -------------------------
def compute_hash(self) -> str:
header_string = json.dumps(
self.to_header_dict(),
sort_keys=True
)
return _sha256(header_string)
77 changes: 77 additions & 0 deletions core/chain.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
from core.block import Block
from core.state import State
from consensus import calculate_hash
import logging
import threading

logger = logging.getLogger(__name__)


class Blockchain:
"""
Manages the blockchain, validates blocks, and commits state transitions.
"""

def __init__(self):
self.chain = []
self.state = State()
self._lock = threading.RLock()
self._create_genesis_block()

def _create_genesis_block(self):
"""
Creates the genesis block with a fixed hash.
"""
genesis_block = Block(
index=0,
previous_hash="0",
transactions=[]
)
genesis_block.hash = "0" * 64
self.chain.append(genesis_block)

@property
def last_block(self):
"""
Returns the most recent block in the chain.
"""
with self._lock: # Acquire lock for thread-safe access
return self.chain[-1]

def add_block(self, block):
"""
Validates and adds a block to the chain if all transactions succeed.
Uses a copied State to ensure atomic validation.
"""

with self._lock:
# Check previous hash linkage
if block.previous_hash != self.last_block.hash:
logger.warning("Block %s rejected: Invalid previous hash %s != %s", block.index, block.previous_hash, self.last_block.hash)
return False

# Check index linkage
if block.index != self.last_block.index + 1:
logger.warning("Block %s rejected: Invalid index %s != %s", block.index, block.index, self.last_block.index + 1)
return False

# Verify block hash
if block.hash != calculate_hash(block.to_header_dict()):
logger.warning("Block %s rejected: Invalid hash %s", block.index, block.hash)
return False

# Validate transactions on a temporary state copy
temp_state = self.state.copy()

for tx in block.transactions:
result = temp_state.validate_and_apply(tx)

# Reject block if any transaction fails
if not result:
logger.warning("Block %s rejected: Transaction failed validation", block.index)
return False

# All transactions valid → commit state and append block
self.state = temp_state
self.chain.append(block)
return True
Loading