-
-
Notifications
You must be signed in to change notification settings - Fork 7
[Feature] MiniChain v0 - Educational Blockchain Prototype #16
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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 |
| 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 | ||||||||||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| ## 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
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Commands table is missing its header row. GFM tables require a header row before the separator ( 📝 Proposed fix+| Command | Shortcut | Description |
|---------|----------|-------------|
| `balance` | `b` | Show your balance |📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| ## 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 | | ||||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "CLI interface" is redundant — "CLI" already expands to "Command Line Interface". Change to simply 🧰 Tools🪛 LanguageTool[style] ~71-~71: This phrase is redundant (‘I’ stands for ‘interface’). Use simply “CLI”. (ACRONYM_TAUTOLOGY) 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||
| | `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) | ||||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 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 |
||||||||||||||||||||||||||||||||||||||||||||||||
| 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 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Replace deprecated ♻️ 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: (UP035) 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
♻️ 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 Convert to (RUF013) 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial
💡 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
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial
This method is called on every CLI command ( 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Misleading docstring: 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 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
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # 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
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial
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, 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
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hardcoded private key will be permanently embedded in git history. The Prefer loading from an environment variable with a generated fallback, matching the 🔑 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 🤖 Prompt for AI Agents |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Markdown lint violations in Test Scenarios section.
Several
markdownlintwarnings apply to this section:###headings at lines 23, 29, and 36 need a blank line below them before the opening fence.bashortext).🧰 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