Skip to content

Commit 1c9fa4c

Browse files
addressreview comments
1 parent 02f8d0b commit 1c9fa4c

5 files changed

Lines changed: 29 additions & 28 deletions

File tree

minichain/block.py

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import time
22
import hashlib
3-
from typing import Optional
4-
from collections.abc import Sequence
3+
from typing import Sequence, Optional
54

65
from .transaction import Transaction
76
from .receipt import Receipt
@@ -39,27 +38,27 @@ def __init__(
3938
self,
4039
index: int,
4140
previous_hash: str,
42-
transactions: Optional[Sequence[Transaction]] = None,
43-
timestamp: Optional[float] = None,
44-
target: Optional[int] = None,
41+
target: int,
42+
transactions: Sequence[Transaction] = (),
43+
timestamp: int = 0,
4544
state_root: Optional[str] = None,
4645
receipt_root: Optional[str] = None,
47-
receipts: Optional[Sequence[Receipt]] = None,
46+
receipts: Sequence[Receipt] = (),
4847
miner: Optional[str] = None,
4948
):
5049
self.index = index
5150
self.previous_hash = previous_hash
5251
# Freeze transactions into an immutable tuple to prevent header/body mismatch
53-
self.transactions = tuple(transactions) if transactions else ()
54-
self.receipts = tuple(receipts) if receipts else ()
52+
self.transactions = tuple(transactions)
53+
self.receipts = tuple(receipts)
5554
self.miner = miner
5655
# Deterministic timestamp (ms)
5756
self.timestamp: int = (
5857
round(time.time() * 1000)
59-
if timestamp is None
58+
if timestamp == 0
6059
else int(timestamp)
6160
)
62-
self.target: Optional[int] = target
61+
self.target: int = target
6362
self.nonce: int = 0
6463
self.hash: Optional[str] = None
6564
self.state_root: Optional[str] = state_root
@@ -83,7 +82,7 @@ def to_header_dict(self):
8382
"state_root": self.state_root,
8483
"receipt_root": self.receipt_root,
8584
"timestamp": self.timestamp,
86-
"target": hex(self.target) if self.target is not None else None,
85+
"target": hex(self.target),
8786
"nonce": self.nonce,
8887
}
8988
# Include miner in header only when present (optional field)
@@ -138,10 +137,10 @@ def from_dict(cls, payload: dict):
138137
if not isinstance(parsed_target, int) or parsed_target <= 0 or parsed_target > MAX_TARGET:
139138
raise ValueError(f"invalid target in payload: {parsed_target}")
140139
else:
141-
parsed_target = None
140+
raise ValueError("missing target in payload")
142141

143142
raw_ts = payload.get("timestamp")
144-
parsed_ts = int(raw_ts) if raw_ts is not None else None
143+
parsed_ts = int(raw_ts) if raw_ts is not None else 0
145144
block = cls(
146145
index=int(payload["index"]),
147146
previous_hash=payload["previous_hash"],
@@ -153,18 +152,18 @@ def from_dict(cls, payload: dict):
153152
receipts=receipts,
154153
miner=payload.get("miner"),
155154
)
156-
block.nonce = int(payload.get("nonce", 0))
155+
block.nonce = int(payload.get("nonce") or 0)
157156
block.hash = payload.get("hash")
158157

159158
# Verify the block hash
160159
expected_hash = block.compute_hash()
161-
if block.hash is not None and block.hash != expected_hash:
160+
if block.hash and block.hash != expected_hash:
162161
raise ValueError("block hash does not match header")
163162

164163
# Recalculate and verify the Merkle root!
165164
if "merkle_root" in payload and payload["merkle_root"] != block.merkle_root:
166165
raise ValueError("merkle_root does not match transactions")
167-
166+
168167
if "receipt_root" in payload:
169168
expected_receipt_root = calculate_receipt_root(block.receipts)
170169
if payload["receipt_root"] != expected_receipt_root:
@@ -176,7 +175,7 @@ def from_dict(cls, payload: dict):
176175
def canonical_payload(self) -> bytes:
177176
"""Returns the full block (header + body) as canonical bytes for networking."""
178177
# Sanity checks to prevent broadcasting invalid blocks
179-
if self.hash is None:
178+
if not self.hash:
180179
raise ValueError("block hash is missing")
181180
if self.hash != self.compute_hash():
182181
raise ValueError("block hash does not match header")

minichain/chain.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,9 @@ def get_total_work(self, chain_list=None):
148148
if chain_list is None:
149149
with self._lock:
150150
chain_list = self.chain
151+
# The expected number of hashes required to find a block is (1 << 256) / target.
152+
# This sums the expected number of hashes for all blocks in the chain,
153+
# which represents the total computational work put into the chain.
151154
return sum((1 << 256) // (block.target or 1) for block in chain_list)
152155

153156
def _next_target(self, target, avg_block_time):

minichain/receipt.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
from typing import List, Optional
1+
from typing import List, Sequence
22

33
class Receipt:
44
"""
55
Represents the execution result of a transaction.
66
"""
7-
def __init__(self, tx_hash: str, status: int, gas_used: int = 0, error_message: Optional[str] = None, logs: Optional[List[dict]] = None, contract_address: Optional[str] = None):
7+
def __init__(self, tx_hash: str, status: int, gas_used: int = 0, error_message: str = "", logs: Sequence[dict] = (), contract_address: str = ""):
88
self.tx_hash = tx_hash
99
self.status = status # 1 for success, 0 for failure
1010
self.gas_used = gas_used
1111
self.error_message = error_message
12-
self.logs = logs or []
12+
self.logs = list(logs)
1313
self.contract_address = contract_address
1414

1515
def to_dict(self) -> dict:
@@ -27,8 +27,8 @@ def from_dict(cls, payload: dict) -> 'Receipt':
2727
return cls(
2828
tx_hash=payload["tx_hash"],
2929
status=payload["status"],
30-
gas_used=payload.get("gas_used", 0),
31-
error_message=payload.get("error_message"),
32-
logs=payload.get("logs", []),
33-
contract_address=payload.get("contract_address")
30+
gas_used=payload.get("gas_used") or 0,
31+
error_message=payload.get("error_message") or "",
32+
logs=payload.get("logs") or [],
33+
contract_address=payload.get("contract_address") or ""
3434
)

tests/test_protocol_hardening.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@ def test_canonical_json_is_order_independent(self):
1616
self.assertEqual(calculate_hash(left), calculate_hash(right))
1717

1818
def test_block_hash_matches_compute_hash(self):
19-
block = Block(index=1, previous_hash="abc", transactions=[], timestamp=1234567890)
20-
block.target = 2
19+
block = Block(index=1, previous_hash="abc", target=2, transactions=[], timestamp=1234567890)
2120
block.nonce = 7
2221

2322
self.assertEqual(block.compute_hash(), calculate_hash(block.to_header_dict()))

tests/test_target.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ def test_reorg_target_validation(self):
5656
chain2.chain[0].hash = chain2.chain[0].compute_hash()
5757

5858
# Chain 2 mines a fast block
59-
block1 = Block(1, chain2.last_block.hash, [], timestamp=chain2.last_block.timestamp + 1, target=chain2.current_target, state_root=chain2.state.state_root())
59+
block1 = Block(1, chain2.last_block.hash, chain2.current_target, [], timestamp=chain2.last_block.timestamp + 1, state_root=chain2.state.state_root())
6060
mine_block(block1)
6161
chain2.add_block(block1)
6262

@@ -71,7 +71,7 @@ def test_reorg_target_validation(self):
7171
# Forging a chain with wrong target should be rejected
7272
forged_chain = list(chain2.chain)
7373
# Should be expected_target_fast but we provide start_target instead!
74-
forged_block = Block(2, chain2.last_block.hash, [], timestamp=chain2.last_block.timestamp + 1000, target=start_target, state_root=chain2.state.state_root())
74+
forged_block = Block(2, chain2.last_block.hash, start_target, [], timestamp=chain2.last_block.timestamp + 1000, state_root=chain2.state.state_root())
7575
mine_block(forged_block)
7676
forged_chain.append(forged_block)
7777

0 commit comments

Comments
 (0)