1919import argparse
2020import asyncio
2121import logging
22- import re
2322import sys
2423
2524from nacl .signing import SigningKey
2625from nacl .encoding import HexEncoder
2726
2827from minichain import Transaction , Blockchain , Block , State , Mempool , P2PNetwork , mine_block
28+ from minichain .validators import is_valid_receiver
2929
3030
3131logger = logging .getLogger (__name__ )
@@ -49,21 +49,41 @@ def create_wallet():
4949
5050def mine_and_process_block (chain , mempool , miner_pk ):
5151 """Mine pending transactions into a new block."""
52- pending_txs = mempool .get_transactions_for_block (chain . state )
52+ pending_txs = mempool .get_transactions_for_block ()
5353 if not pending_txs :
5454 logger .info ("Mempool is empty — nothing to mine." )
5555 return None
5656
57+ # Filter queue candidates against a temporary state snapshot.
58+ temp_state = chain .state .copy ()
59+ mineable_txs = []
60+ stale_txs = []
61+ for tx in pending_txs :
62+ expected_nonce = temp_state .get_account (tx .sender ).get ("nonce" , 0 )
63+ if tx .nonce < expected_nonce :
64+ stale_txs .append (tx )
65+ continue
66+ if temp_state .validate_and_apply (tx ):
67+ mineable_txs .append (tx )
68+
69+ if stale_txs :
70+ mempool .remove_transactions (stale_txs )
71+
72+ if not mineable_txs :
73+ logger .info ("No mineable transactions in current queue window." )
74+ return None
75+
5776 block = Block (
5877 index = chain .last_block .index + 1 ,
5978 previous_hash = chain .last_block .hash ,
60- transactions = pending_txs ,
79+ transactions = mineable_txs ,
6180 )
6281
6382 mined_block = mine_block (block )
6483
6584 if chain .add_block (mined_block ):
66- logger .info ("✅ Block #%d mined and added (%d txs)" , mined_block .index , len (pending_txs ))
85+ logger .info ("✅ Block #%d mined and added (%d txs)" , mined_block .index , len (mineable_txs ))
86+ mempool .remove_transactions (mineable_txs )
6787 chain .state .credit_mining_reward (miner_pk )
6888 return mined_block
6989 else :
@@ -97,8 +117,8 @@ async def handler(data):
97117 logger .info ("📥 Received tx from %s... (amount=%s)" , tx .sender [:8 ], tx .amount )
98118
99119 elif msg_type == "block" :
100- txs_raw = payload .pop ("transactions" , [])
101- block_hash = payload .pop ("hash" , None )
120+ txs_raw = payload .get ("transactions" , [])
121+ block_hash = payload .get ("hash" )
102122 transactions = [Transaction (** t ) for t in txs_raw ]
103123
104124 block = Block (
@@ -147,7 +167,7 @@ async def handler(data):
147167"""
148168
149169
150- async def cli_loop (sk , pk , chain , mempool , network , nonce_counter ):
170+ async def cli_loop (sk , pk , chain , mempool , network ):
151171 """Read commands from stdin asynchronously."""
152172 loop = asyncio .get_event_loop ()
153173 print (HELP_TEXT )
@@ -179,18 +199,23 @@ async def cli_loop(sk, pk, chain, mempool, network, nonce_counter):
179199 print (" Usage: send <receiver_address> <amount>" )
180200 continue
181201 receiver = parts [1 ]
202+ if not is_valid_receiver (receiver ):
203+ print (" Invalid receiver format. Expected 40 or 64 hex characters." )
204+ continue
182205 try :
183206 amount = int (parts [2 ])
184207 except ValueError :
185208 print (" Amount must be an integer." )
186209 continue
210+ if amount <= 0 :
211+ print (" Amount must be greater than 0." )
212+ continue
187213
188- nonce = nonce_counter [ 0 ]
214+ nonce = chain . state . get_account ( pk ). get ( "nonce" , 0 )
189215 tx = Transaction (sender = pk , receiver = receiver , amount = amount , nonce = nonce )
190216 tx .sign (sk )
191217
192218 if mempool .add_transaction (tx ):
193- nonce_counter [0 ] += 1
194219 await network .broadcast_transaction (tx )
195220 print (f" ✅ Tx sent: { amount } coins → { receiver [:12 ]} ..." )
196221 else :
@@ -201,9 +226,6 @@ async def cli_loop(sk, pk, chain, mempool, network, nonce_counter):
201226 mined = mine_and_process_block (chain , mempool , pk )
202227 if mined :
203228 await network .broadcast_block (mined , miner = pk )
204- # Sync local nonce from chain state
205- acc = chain .state .get_account (pk )
206- nonce_counter [0 ] = acc .get ("nonce" , 0 )
207229
208230 # ── peers ──
209231 elif cmd == "peers" :
@@ -288,11 +310,8 @@ async def on_peer_connected(writer):
288310 except ValueError :
289311 logger .error ("Invalid --connect format. Use host:port" )
290312
291- # Nonce counter kept as a mutable list so the CLI closure can mutate it
292- nonce_counter = [0 ]
293-
294313 try :
295- await cli_loop (sk , pk , chain , mempool , network , nonce_counter )
314+ await cli_loop (sk , pk , chain , mempool , network )
296315 finally :
297316 await network .stop ()
298317
0 commit comments