Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 10 additions & 0 deletions Minichain_v0/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Wallet keys (private keys - never commit!)
*.key

# Python
venv/
__pycache__/
*.pyc

# Environment
.env
104 changes: 104 additions & 0 deletions Minichain_v0/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# MiniChain v0 - Educational Blockchain

A minimal, educational blockchain implementation in Python

## Quick Start

```bash
cd Minichain_v0
pip install -r requirements.txt

# Using the launcher (recommended)
./minichain start 8000 # Bootstrap node
./minichain start 8001 8000 # Connect to port 8000
./minichain start 8002 8000 # Third node

# Or using Python directly
python3 main.py --port 8000
python3 main.py --port 8001 --connect localhost:8000
```

## Test Scenarios

### 1. Mining for rewards
```
mine # Mine block (earns 50 coins)
balance # Check balance
```

### 2. Distribute from treasury
```
treasury # Check treasury balance
faucet <addr> 1000
mine # Confirm transaction
```

### 3. Send coins
```
address # Show your address (copy recipient's)
send <addr> 100
mempool # View mempool
mine # Confirm
```
Comment on lines +23 to +42
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Markdown lint violations in Test Scenarios section.

Several markdownlint warnings apply to this section:

  • MD022: The ### headings at lines 23, 29, and 36 need a blank line below them before the opening fence.
  • MD040: The three fenced blocks are missing a language specifier (use bash or text).
🧰 Tools
🪛 markdownlint-cli2 (0.21.0)

[warning] 23-23: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 24-24: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)


[warning] 24-24: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


[warning] 29-29: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 30-30: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)


[warning] 30-30: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


[warning] 36-36: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 37-37: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)


[warning] 37-37: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Minichain_v0/README.md` around lines 23 - 42, The README's Test Scenarios
section has markdownlint issues: add a blank line after each level-3 heading
(the "### 1. Mining for rewards", "### 2. Distribute from treasury", and "### 3.
Send coins" headings) and update each fenced code block to include a language
specifier (e.g., change ``` to ```bash) so the three blocks under those headings
comply with MD022 and MD040; adjust only the headings and the opening backtick
fences in the blocks shown.


## Commands

|---------|----------|-------------|
| `balance` | `b` | Show your balance |
| `address` | `a` | Show your wallet address |
| `send <addr> <amt>` | - | Send coins |
| `mine` | `m` | Mine block (+50 reward) |
| `faucet <addr> <amt>` | - | Treasury send |
| `treasury` | `t` | Show treasury balance |
| `chain` | `c` | Show blockchain |
| `peers` | `p` | Show connected peers |
| `mempool` | `mp` | Show pending transactions |
| `quit` | `q` | Exit |

Comment on lines +46 to +57
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Commands table is missing its header row.

GFM tables require a header row before the separator (|---|---|---|). Without it, most renderers (including GitHub) will render this as plain text rather than a table.

📝 Proposed fix
+| Command | Shortcut | Description |
 |---------|----------|-------------|
 | `balance` | `b` | Show your balance |
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
|---------|----------|-------------|
| `balance` | `b` | Show your balance |
| `address` | `a` | Show your wallet address |
| `send <addr> <amt>` | - | Send coins |
| `mine` | `m` | Mine block (+50 reward) |
| `faucet <addr> <amt>` | - | Treasury send |
| `treasury` | `t` | Show treasury balance |
| `chain` | `c` | Show blockchain |
| `peers` | `p` | Show connected peers |
| `mempool` | `mp` | Show pending transactions |
| `quit` | `q` | Exit |
| Command | Shortcut | Description |
|---------|----------|-------------|
| `balance` | `b` | Show your balance |
| `address` | `a` | Show your wallet address |
| `send <addr> <amt>` | - | Send coins |
| `mine` | `m` | Mine block (+50 reward) |
| `faucet <addr> <amt>` | - | Treasury send |
| `treasury` | `t` | Show treasury balance |
| `chain` | `c` | Show blockchain |
| `peers` | `p` | Show connected peers |
| `mempool` | `mp` | Show pending transactions |
| `quit` | `q` | Exit |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Minichain_v0/README.md` around lines 46 - 57, The table listing CLI commands
lacks a header row so Markdown renderers won't treat it as a table; add a header
row (e.g., "Command | Alias | Description") directly above the existing
separator row and ensure the separator remains as the next line (e.g.,
"|---|---|---|") so entries like `balance`, `address`, `send <addr> <amt>`,
`mine`, etc. render correctly as a proper three-column table.


## File and folder structure

| File | Lines | Description |
|------|-------|-------------|
| `config.py` | 19 | Configuration constants |
| `transaction.py` | 91 | Signed transactions (Ed25519) |
| `state.py` | 70 | Account balances and nonces |
| `block.py` | 80 | Block structure |
| `blockchain.py` | 133 | Chain validation and storage |
| `mempool.py` | 66 | Pending transaction pool |
| `consensus.py` | 40 | Proof-of-Work mining |
| `network.py` | 154 | P2P networking (TCP sockets) |
| `main.py` | 185 | CLI interface |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

"CLI interface" is redundant — "CLI" already expands to "Command Line Interface".

Change to simply CLI or Command-line interface.

🧰 Tools
🪛 LanguageTool

[style] ~71-~71: This phrase is redundant (‘I’ stands for ‘interface’). Use simply “CLI”.
Context: ...ing (TCP sockets) | | main.py | 185 | CLI interface | | minichain | - | Bash launcher scr...

(ACRONYM_TAUTOLOGY)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Minichain_v0/README.md` at line 71, Edit the README table row that lists
`main.py` (the entry showing "CLI interface") and replace the redundant phrase
"CLI interface" with either "CLI" or "Command-line interface" (preferably "CLI")
so the description reads simply `CLI`; update the table cell containing `CLI
interface` accordingly.

| `minichain` | - | Bash launcher script |


## What Users/Dev Learn

1. **Transactions** - Ed25519 digital signatures for authentication
2. **State** - Account-based ledger (balances + nonces)
3. **Blocks** - Linking transactions with hashes
4. **Consensus** - Proof-of-Work mining with rewards
5. **Networking** - P2P communication with TCP sockets


## Architecture

```
Transaction (signed) → Mempool → Block → Blockchain
Consensus (PoW)
Network → Peers
```


## Not Included (v0 Simplifications)

- ❌ Merkle trees (optimization)
- ❌ State snapshots (optimization)
- ❌ Persistence (in-memory only)
- ❌ GossipSub (using simpler streams)

## Progression Path

`v0` (currently we are here) → `v1` (optimizations) → `v2` (smart contracts)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

File must end with a single trailing newline (MD047).

🧰 Tools
🪛 markdownlint-cli2 (0.21.0)

[warning] 104-104: Files should end with a single newline character

(MD047, single-trailing-newline)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Minichain_v0/README.md` at line 104, The file ends without the required final
newline (MD047); update README.md so the last line containing "`v0` (currently
we are here) → `v1` (optimizations) → `v2` (smart contracts)" is followed by a
single trailing newline character (ensure exactly one newline at EOF and no
extra blank lines) to satisfy the linter rule.

77 changes: 77 additions & 0 deletions Minichain_v0/block.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
"""
block.py - Block structure for MiniChain.
A block contains transactions and links to the previous block via hash.
"""

import json
import hashlib
import time
from typing import List
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Replace deprecated typing.List with built-in list (Ruff UP035).

♻️ Proposed fix
-from typing import List
 ...
-    def __init__(self, index: int, prev_hash: str, transactions: List[Transaction],
+    def __init__(self, index: int, prev_hash: str, transactions: list[Transaction],
🧰 Tools
🪛 Ruff (0.15.1)

[warning] 9-9: typing.List is deprecated, use list instead

(UP035)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Minichain_v0/block.py` at line 9, The import of typing.List is deprecated;
remove "from typing import List" and replace usages of the symbol List in type
annotations (e.g., any annotations referencing List in this module such as
function signatures, class attributes, or variable annotations) with the
built-in generic form list[...] or plain list as appropriate so the file uses
the built-in list type instead of typing.List (update annotations in
Minichain_v0/block.py where List is referenced).

from transaction import Transaction, create_genesis_tx
from config import GENESIS_TIMESTAMP, TREASURY_ADDRESS, TREASURY_BALANCE


class Block:
"""A block in the blockchain containing transactions."""

def __init__(self, index: int, prev_hash: str, transactions: List[Transaction],
timestamp: float = None, nonce: int = 0):
self.index = index
self.prev_hash = prev_hash
self.transactions = transactions
self.timestamp = timestamp or time.time()
Comment on lines +18 to +22
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

timestamp or time.time() is falsy-zero sensitive; use explicit None check.

or treats 0.0 as falsy, so Block(..., timestamp=0.0) would silently use time.time() instead. Prefer an explicit is None guard, which also fixes the implicit Optional Ruff warning (RUF013).

♻️ Proposed fix
-    def __init__(self, index: int, prev_hash: str, transactions: List[Transaction],
-                 timestamp: float = None, nonce: int = 0):
+    def __init__(self, index: int, prev_hash: str, transactions: list[Transaction],
+                 timestamp: float | None = None, nonce: int = 0):
         ...
-        self.timestamp = timestamp or time.time()
+        self.timestamp = timestamp if timestamp is not None else time.time()
🧰 Tools
🪛 Ruff (0.15.1)

[warning] 18-18: PEP 484 prohibits implicit Optional

Convert to T | None

(RUF013)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Minichain_v0/block.py` around lines 18 - 22, The timestamp assignment in
Block.__init__ uses "timestamp or time.time()" which treats 0.0 as falsy; change
it to an explicit None check so a provided 0.0 is preserved—use the timestamp
parameter with "if timestamp is not None else time.time()" (update the
constructor in the Block class where self.timestamp is set and keep variable
name timestamp).

self.nonce = nonce
self.hash = self.compute_hash()

def compute_hash(self) -> str:
"""Compute SHA-256 hash of block header."""
header = {
"index": self.index,
"prev_hash": self.prev_hash,
"tx_hashes": [tx.hash() for tx in self.transactions],
"timestamp": self.timestamp,
"nonce": self.nonce
}
header_bytes = json.dumps(header, sort_keys=True).encode()
return hashlib.sha256(header_bytes).hexdigest()

def to_dict(self) -> dict:
"""Convert block to dictionary for serialization."""
return {
"index": self.index,
"prev_hash": self.prev_hash,
"transactions": [tx.to_dict() for tx in self.transactions],
"timestamp": self.timestamp,
"nonce": self.nonce,
"hash": self.hash
}

@staticmethod
def from_dict(data: dict) -> "Block":
"""Create block from dictionary."""
txs = [Transaction.from_dict(tx) for tx in data["transactions"]]
block = Block(
index=data["index"],
prev_hash=data["prev_hash"],
transactions=txs,
timestamp=data["timestamp"],
nonce=data["nonce"]
)
return block
Comment on lines +49 to +60
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

from_dict silently ignores the stored "hash" field.

Block.__init__ always calls compute_hash(), so data["hash"] from to_dict is never read or validated during deserialization. A tampered hash field in the JSON is discarded; only the block's other fields matter. This is correct for correctness (chain validation does the real check), but it makes the "hash" entry in to_dict purely cosmetic. A brief comment or an assertion would make the intent explicit:

💡 Suggested clarification
     `@staticmethod`
     def from_dict(data: dict) -> "Block":
         """Create block from dictionary."""
         txs = [Transaction.from_dict(tx) for tx in data["transactions"]]
         block = Block(
             index=data["index"],
             prev_hash=data["prev_hash"],
             transactions=txs,
             timestamp=data["timestamp"],
             nonce=data["nonce"]
         )
+        # Note: data["hash"] is intentionally not used; hash is recomputed
+        # from block fields and validated by blockchain.is_valid_chain().
         return block
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@staticmethod
def from_dict(data: dict) -> "Block":
"""Create block from dictionary."""
txs = [Transaction.from_dict(tx) for tx in data["transactions"]]
block = Block(
index=data["index"],
prev_hash=data["prev_hash"],
transactions=txs,
timestamp=data["timestamp"],
nonce=data["nonce"]
)
return block
`@staticmethod`
def from_dict(data: dict) -> "Block":
"""Create block from dictionary."""
txs = [Transaction.from_dict(tx) for tx in data["transactions"]]
block = Block(
index=data["index"],
prev_hash=data["prev_hash"],
transactions=txs,
timestamp=data["timestamp"],
nonce=data["nonce"]
)
# Note: data["hash"] is intentionally not used; hash is recomputed
# from block fields and validated by blockchain.is_valid_chain().
return block
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Minichain_v0/block.py` around lines 49 - 60, The from_dict method currently
ignores the stored "hash" field from serialized data; update Block.from_dict to
explicitly handle it by (a) reading data.get("hash") after constructing the
Block (using Block(...) which triggers compute_hash()) and then asserting or
raising a ValueError if the provided hash does not match block.hash (use
compute_hash() or the block.hash property for comparison), or if you prefer not
to enforce it, add a clear comment in Block.from_dict stating that the stored
"hash" is intentionally discarded because blocks recompute their hash in
Block.__init__ (which calls compute_hash()), referencing Block.from_dict,
Block.__init__, compute_hash, and to_dict so maintainers understand the
behavior.


def __repr__(self):
return f"Block(#{self.index}, txs={len(self.transactions)}, hash={self.hash[:8]})"


def create_genesis_block() -> Block:
"""Create the genesis (first) block with treasury funds."""
# Genesis funds go to the fixed treasury address
genesis_txs = [create_genesis_tx(TREASURY_ADDRESS, TREASURY_BALANCE)]

return Block(
index=0,
prev_hash="0" * 64,
transactions=genesis_txs,
timestamp=GENESIS_TIMESTAMP,
nonce=0
)
133 changes: 133 additions & 0 deletions Minichain_v0/blockchain.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
"""
blockchain.py - Chain management for MiniChain.
Stores blocks, validates new blocks, and handles longest-chain rule.
"""

from typing import List
from block import Block, create_genesis_block
from state import State, apply_tx
from config import DIFFICULTY


class Blockchain:
"""The blockchain: a list of validated blocks."""

def __init__(self):
genesis = create_genesis_block()
self.chain: List[Block] = [genesis]
self.difficulty = DIFFICULTY

@property
def latest_block(self) -> Block:
"""Get the most recent block."""
return self.chain[-1]

@property
def height(self) -> int:
"""Get chain length."""
return len(self.chain)

def get_state(self) -> State:
"""Recompute current state by replaying all transactions from genesis."""
state = State()
for block in self.chain:
for tx in block.transactions:
state = apply_tx(state, tx)
return state
Comment on lines +30 to +36
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

get_state() replays the entire chain on every call — will degrade as chain grows.

This method is called on every CLI command (balance, send, mine, faucet, treasury) and on every incoming block/transaction via the network. As the chain grows, each call becomes linearly more expensive. For v0 this is acceptable, but consider caching the state and updating it incrementally when blocks are added/replaced.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Minichain_v0/blockchain.py` around lines 30 - 36, get_state currently
recomputes State from scratch by iterating self.chain and apply_tx each call,
which will degrade; change to cache the latest state and update it
incrementally: add cached attributes (e.g., self._cached_state and
self._cached_length/index) and modify functions that mutate the chain (e.g., the
block/transaction intake methods that append or replace blocks) to apply_tx only
for the new blocks' transactions to advance self._cached_state, or invalidate
the cache on reorgs/replacements and recompute only when needed; ensure
get_state returns the cached state (recomputing once if cache is empty or
invalidated).


def validate_block(self, block: Block, prev_block: Block) -> bool:
"""Check if a block is valid (structure, PoW, transactions)."""
Comment on lines +38 to +39
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Misleading docstring: validate_block does not check transactions.

The docstring says "Check if a block is valid (structure, PoW, transactions)" but the method only validates structure and PoW. Transaction validation is performed separately in validate_block_transactions. Update the docstring to avoid confusion.

Proposed fix
     def validate_block(self, block: Block, prev_block: Block) -> bool:
-        """Check if a block is valid (structure, PoW, transactions)."""
+        """Check if a block is valid (structure and PoW)."""
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def validate_block(self, block: Block, prev_block: Block) -> bool:
"""Check if a block is valid (structure, PoW, transactions)."""
def validate_block(self, block: Block, prev_block: Block) -> bool:
"""Check if a block is valid (structure and PoW)."""
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Minichain_v0/blockchain.py` around lines 38 - 39, The docstring on
validate_block is misleading because it claims transaction validation is
included; update the validate_block method's docstring to state it only checks
block structure and proof-of-work (PoW) while deferring transaction checks to
validate_block_transactions, e.g., replace "(structure, PoW, transactions)" with
"(structure and PoW) — transaction validation is handled by
validate_block_transactions" so callers know where transaction validation
occurs.

# Check index is sequential
if block.index != prev_block.index + 1:
print(f"Invalid index: expected {prev_block.index + 1}, got {block.index}")
return False

# Check previous hash links correctly
if block.prev_hash != prev_block.hash:
print("Invalid previous hash")
return False

# Check hash is correct
if block.hash != block.compute_hash():
print("Invalid hash")
return False

# Check proof-of-work
if not block.hash.startswith("0" * self.difficulty):
print("Hash does not meet difficulty")
return False

return True

def validate_block_transactions(self, block: Block, state: State) -> bool:
"""Validate all transactions in block against given state."""
try:
for tx in block.transactions:
state = apply_tx(state, tx)
return True
except ValueError as e:
print(f"Transaction validation failed: {e}")
return False

def add_block(self, block: Block) -> bool:
"""Add a new block to the chain if valid."""
# Validate block structure and PoW
if not self.validate_block(block, self.latest_block):
return False

# Validate transactions against current state
current_state = self.get_state()
if not self.validate_block_transactions(block, current_state):
return False

self.chain.append(block)
return True

def is_valid_chain(self, chain: List[Block]) -> bool:
"""Validate an entire chain from genesis."""
if not chain:
return False

# Check genesis block matches expected
expected_genesis = create_genesis_block()
if chain[0].hash != expected_genesis.hash:
return False

# Validate each block
state = State()
for i, block in enumerate(chain):
# Apply transactions
try:
for tx in block.transactions:
state = apply_tx(state, tx)
except ValueError:
return False

# Validate structure (skip genesis)
if i > 0 and not self.validate_block(block, chain[i - 1]):
return False

return True
Comment on lines +86 to +110
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

is_valid_chain applies transactions before checking block structure — wasted work on invalid blocks.

In the loop (lines 98-108), transactions are applied to the state before the structural validation at line 107. If the block has an invalid structure, the transaction processing was wasted. Additionally, for a maliciously crafted chain, apply_tx could raise errors that mask structural issues. Consider validating structure first.

Proposed reordering
         for i, block in enumerate(chain):
+            # Validate structure (skip genesis)
+            if i > 0 and not self.validate_block(block, chain[i - 1]):
+                return False
+
             # Apply transactions
             try:
                 for tx in block.transactions:
                     state = apply_tx(state, tx)
             except ValueError:
                 return False
-
-            # Validate structure (skip genesis)
-            if i > 0 and not self.validate_block(block, chain[i - 1]):
-                return False
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def is_valid_chain(self, chain: List[Block]) -> bool:
"""Validate an entire chain from genesis."""
if not chain:
return False
# Check genesis block matches expected
expected_genesis = create_genesis_block()
if chain[0].hash != expected_genesis.hash:
return False
# Validate each block
state = State()
for i, block in enumerate(chain):
# Apply transactions
try:
for tx in block.transactions:
state = apply_tx(state, tx)
except ValueError:
return False
# Validate structure (skip genesis)
if i > 0 and not self.validate_block(block, chain[i - 1]):
return False
return True
def is_valid_chain(self, chain: List[Block]) -> bool:
"""Validate an entire chain from genesis."""
if not chain:
return False
# Check genesis block matches expected
expected_genesis = create_genesis_block()
if chain[0].hash != expected_genesis.hash:
return False
# Validate each block
state = State()
for i, block in enumerate(chain):
# Validate structure (skip genesis)
if i > 0 and not self.validate_block(block, chain[i - 1]):
return False
# Apply transactions
try:
for tx in block.transactions:
state = apply_tx(state, tx)
except ValueError:
return False
return True
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Minichain_v0/blockchain.py` around lines 86 - 110, The is_valid_chain method
currently applies transactions with apply_tx to state before checking block
structure, which wastes work and can mask structural issues; modify
is_valid_chain so that for each non-genesis block it first calls
self.validate_block(block, chain[i - 1]) and only if that returns True proceeds
to apply transactions via apply_tx (catching ValueError as before), keeping the
genesis check using create_genesis_block intact and preserving the State()
initialization and return semantics.


def replace_chain(self, new_chain: List[Block]) -> bool:
"""Replace chain if new one is longer and valid (longest-chain rule)."""
if len(new_chain) <= len(self.chain):
print("New chain is not longer")
return False

if not self.is_valid_chain(new_chain):
print("New chain is invalid")
return False

self.chain = new_chain
print(f"Chain replaced with {len(new_chain)} blocks")
return True

def to_dict(self) -> List[dict]:
"""Serialize chain for network transmission."""
return [block.to_dict() for block in self.chain]

@staticmethod
def from_dict(data: List[dict]) -> List[Block]:
"""Deserialize chain from network."""
return [Block.from_dict(b) for b in data]
31 changes: 31 additions & 0 deletions Minichain_v0/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""
config.py - MiniChain configuration constants.
Simple settings for the blockchain.
"""

# Proof-of-Work difficulty (number of leading zeros required)
DIFFICULTY = 4

# Genesis block timestamp (fixed for reproducibility)
GENESIS_TIMESTAMP = 1704067200.0

# Initial treasury balance (distributed via faucet to nodes)
TREASURY_BALANCE = 10000000

# Mining reward per block
MINING_REWARD = 50

# Maximum transactions per block
MAX_TXS_PER_BLOCK = 100

# Network protocol ID
PROTOCOL_ID = "/minichain/1.0.0"

# Special addresses
COINBASE_SENDER = "0" * 64 # Mining rewards come from "nowhere"

# Pre-generated treasury keypair (Ed25519, hex-encoded)
# This is a FIXED keypair for educational/testing purposes
# In production, this would be securely managed
TREASURY_PRIVATE_KEY = "b705c5f56f218a2003f940f3d7d825ee7369c504ba3ad5fda8a2303f4b3c5e26"
TREASURY_ADDRESS = "6b97d4ed320c6a8d1400dc034e183fd4678c4aa3f6301edf92e1cb4bd6337f44"
Comment on lines +27 to +31
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Hardcoded private key will be permanently embedded in git history.

The *.key rule in .gitignore does not protect this — it is a Python string constant, not a file. Once merged, the key cannot be expunged without a history rewrite. More critically, this sets a bad example for learners who may replicate the pattern in real projects.

Prefer loading from an environment variable with a generated fallback, matching the *.key pattern already established in .gitignore:

🔑 Proposed alternative
-# Pre-generated treasury keypair (Ed25519, hex-encoded)
-# This is a FIXED keypair for educational/testing purposes
-# In production, this would be securely managed
-TREASURY_PRIVATE_KEY = "b705c5f56f218a2003f940f3d7d825ee7369c504ba3ad5fda8a2303f4b3c5e26"
-TREASURY_ADDRESS = "6b97d4ed320c6a8d1400dc034e183fd4678c4aa3f6301edf92e1cb4bd6337f44"
+import os
+# Load treasury keypair from environment or a local key file (never commit private keys!)
+TREASURY_PRIVATE_KEY = os.environ.get("MINICHAIN_TREASURY_PRIVATE_KEY", "")
+TREASURY_ADDRESS = os.environ.get("MINICHAIN_TREASURY_ADDRESS", "")

Alternatively, generate a fresh keypair at first startup (as is already done for peer wallets) and persist it to treasury.key (which is already covered by .gitignore).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Minichain_v0/config.py` around lines 27 - 31, Replace the hardcoded
TREASURY_PRIVATE_KEY and TREASURY_ADDRESS constants with a runtime loader: check
for an environment variable (e.g., TREASURY_PRIVATE_KEY) and if absent generate
a new Ed25519 keypair, derive TREASURY_ADDRESS from the private key, and persist
the private key to a file named treasury.key (which is already in .gitignore)
for subsequent runs; update any references to TREASURY_PRIVATE_KEY and
TREASURY_ADDRESS in the codebase to use the values returned by this loader so
the secret is not embedded in source control.

Loading