-
-
Notifications
You must be signed in to change notification settings - Fork 16
feat: complete MiniChain core implementation with PoW, Ed25519, and smart contract engine #7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Zahnentferner
merged 29 commits into
StabilityNexus:main
from
aniket866:core-contract-base-setup
Feb 21, 2026
Merged
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 da8d108
Update consensus/__init__.py
aniket866 dfbcd95
Enhance mine_block with timeout and cancellation support
aniket866 8fb795d
Code rabbit follow-up
aniket866 c51717a
Code rabbit follow-up
aniket866 d06f2b9
Code rabbit follow-up
aniket866 b1639cb
Implement Blockchain class for managing state and blocks
aniket866 2d43e2a
Apply suggestion from @coderabbitai[bot]
aniket866 1349d3a
Enhance contract execution with improved safety measures
aniket866 a07c68c
Enhance Transaction class with error handling and data preservation
aniket866 ff89df6
Replace print statements with logging calls
aniket866 d2135fd
Update test_contract.py
aniket866 a9c965a
Update test_contract.py
aniket866 d0e44a6
Update setup.py
aniket866 1c46fca
Update main.py
aniket866 6fbb613
fixing-all-code-rabbit-suggestions
aniket866 d49a985
fixing-code-rabbit-suggestions
aniket866 b32d077
fixing-code-rabbit-suggestions
aniket866 0f40e6a
core-contract-base-setup
aniket866 e6a4db9
Update core/chain.py
aniket866 d5cf36b
Update core/chain.py
aniket866 60cd231
Update core/contract.py
aniket866 c7cf0b1
Code rabbit follow-up
aniket866 fc938cd
updating
aniket866 3da2997
fixed header
aniket866 cccf341
Update main.py
aniket866 2231368
Update core/block.py
aniket866 dd209dc
Update main.py
aniket866 8252bc5
Update core/block.py
aniket866 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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__))) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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"] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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() | ||
aniket866 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
|
|
||
| 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 | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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", | ||
| ] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | ||
aniket866 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 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] | ||
aniket866 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
|
|
||
| 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) | ||
| ) | ||
aniket866 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| 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) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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() | ||
aniket866 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 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 | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| # 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 | ||
aniket866 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| # 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 | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.