From 5d58c2c93924cb67bbbc2fb51450c1f0cbada09d Mon Sep 17 00:00:00 2001 From: aniket866 Date: Sat, 14 Feb 2026 18:08:41 +0530 Subject: [PATCH 01/29] Clean implementation of core setup and contract tests --- consensus/__init__.py | 3 + .../__pycache__/__init__.cpython-311.pyc | Bin 0 -> 271 bytes consensus/__pycache__/pow.cpython-311.pyc | Bin 0 -> 1654 bytes consensus/pow.py | 68 +++++++ core/__init__.py | 7 + core/__pycache__/__init__.cpython-311.pyc | Bin 0 -> 483 bytes core/__pycache__/block.cpython-311.pyc | Bin 0 -> 1871 bytes core/__pycache__/chain.cpython-311.pyc | Bin 0 -> 1936 bytes core/__pycache__/contract.cpython-311.pyc | Bin 0 -> 2054 bytes core/__pycache__/state.cpython-311.pyc | Bin 0 -> 4196 bytes core/__pycache__/transaction.cpython-311.pyc | Bin 0 -> 2843 bytes core/block.py | 34 ++++ core/chain.py | 130 +++++++++++++ core/contract.py | 47 +++++ core/state.py | 122 ++++++++++++ core/transaction.py | 52 ++++++ main.py | 175 ++++++++++++++++++ network/__init__.py | 3 + network/__pycache__/__init__.cpython-311.pyc | Bin 0 -> 227 bytes network/__pycache__/p2p.cpython-311.pyc | Bin 0 -> 2622 bytes network/p2p.py | 73 ++++++++ node/__init__.py | 3 + node/__pycache__/__init__.cpython-311.pyc | Bin 0 -> 225 bytes node/__pycache__/mempool.cpython-311.pyc | Bin 0 -> 1325 bytes node/mempool.py | 49 +++++ requirements.txt | 2 + setup.py | 18 ++ tests/__init__.py | 0 .../__pycache__/test_contract.cpython-311.pyc | Bin 0 -> 3074 bytes tests/__pycache__/test_core.cpython-311.pyc | Bin 0 -> 4976 bytes tests/test_contract.py | 153 +++++++++++++++ tests/test_core.py | 70 +++++++ 32 files changed, 1009 insertions(+) create mode 100644 consensus/__init__.py create mode 100644 consensus/__pycache__/__init__.cpython-311.pyc create mode 100644 consensus/__pycache__/pow.cpython-311.pyc create mode 100644 consensus/pow.py create mode 100644 core/__init__.py create mode 100644 core/__pycache__/__init__.cpython-311.pyc create mode 100644 core/__pycache__/block.cpython-311.pyc create mode 100644 core/__pycache__/chain.cpython-311.pyc create mode 100644 core/__pycache__/contract.cpython-311.pyc create mode 100644 core/__pycache__/state.cpython-311.pyc create mode 100644 core/__pycache__/transaction.cpython-311.pyc create mode 100644 core/block.py create mode 100644 core/chain.py create mode 100644 core/contract.py create mode 100644 core/state.py create mode 100644 core/transaction.py create mode 100644 main.py create mode 100644 network/__init__.py create mode 100644 network/__pycache__/__init__.cpython-311.pyc create mode 100644 network/__pycache__/p2p.cpython-311.pyc create mode 100644 network/p2p.py create mode 100644 node/__init__.py create mode 100644 node/__pycache__/__init__.cpython-311.pyc create mode 100644 node/__pycache__/mempool.cpython-311.pyc create mode 100644 node/mempool.py create mode 100644 requirements.txt create mode 100644 setup.py create mode 100644 tests/__init__.py create mode 100644 tests/__pycache__/test_contract.cpython-311.pyc create mode 100644 tests/__pycache__/test_core.cpython-311.pyc create mode 100644 tests/test_contract.py create mode 100644 tests/test_core.py diff --git a/consensus/__init__.py b/consensus/__init__.py new file mode 100644 index 0000000..8c54d78 --- /dev/null +++ b/consensus/__init__.py @@ -0,0 +1,3 @@ +from .pow import mine_block, calculate_hash + +__all__ = ["mine_block", "calculate_hash"] \ No newline at end of file diff --git a/consensus/__pycache__/__init__.cpython-311.pyc b/consensus/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0c30acc6092fe3f3bc31d7c9408cb478abaf7363 GIT binary patch literal 271 zcmZ3^%ge<81P)#kG9!WXV-N=hn4pZ$azMs(h7^Vr#vF!R#wf;IrYI&xh7_h0=5(eg z<`kA-22IwNj6g-2Ot-jlGxJj8lXCKtvv2VwC*~xV<|LM+#%ClJXB065l@zf62|rDi zTg(Odej`1jn@y*Q3bk0c3%!^6R&nr&N zD=saLiH`>gmc+;F6;%G>u*uC&Da}c>D+2ijSNb(YcEdXe$XuzjTc%`>bSL)Qc`3E+f93j(~A&@_3qeOTiSJZ zR*fyzpo0&l_~1h!fi&Pt-4>_NV~+h3v_@dD5C{|>d=vPVQ{Jq;BDZ}Sy?Hw`@B26N zTQVskpu5Y5<^_k)KkTDNk*;ug3xr3=Kn5!A@_~FjlS$b z49_CR5ZOsDI&{>RK(S8-cMMu5ea&6$#`>Gc!)|==^^Ct_Q5ftEq^lMoKcS{(wX$9< zYc8S6%Hr3MqEgamNwL=yO<675`leDM8swUO0J$fq?YPBFvO{Y^)m{5!4%T9%Qw%j4 z@Eg>&0>P+OwrCLTuCtmrtT;i`AgpX^BoIT(MZ?tHV5B2c*D63c77kV$musY}pUfTRXw1-V49T!gk&Qb(TEM0jr~P`TEb9#~Xhw zG~{_-o^Q(YXY!p>`Oe?heR-)VFV*k8kj4%_{!?s7Grly_lxEJP%&C-l{)I1Pn^LyH zXD`7Jx?u6^Z}y4xBJB1x=n!BFd$2P+(YNsg@aAFQ!c$?m z(-}Pde{<5LYqUGc*)&%;TP*r&lyE^3V$W zxA8_#2^sNPl2vGs`N681sQn}=KuebHkP5L}s#vzA6QydoW?6Adq=Pl;Y7Xo@G~JSN z(=gZ8OhBZ&b5pUZl~v*}EY^r)({yO4m=Aj8fy(bUOW@CbN?(Rb3~PeyA)0zB0-MyO3%u5JE?R zumz}f1HrTmvK^u?7t=&!))s4b$=vBQj?e7}CTH7?h4d*7V%iH5lJnEbK{6O8BY zbY|c_zue@P8~k!Rk`N}(C#H^aXQ{c<)SREnG*cOW;&yZ5_P+R1nmCh`Q%O0x=1bE} zX}XQL#AIu7YCm_d{9zc%kD{#48Oy71G*DF8evVtbpJr1iLe&0;$K#5qgp0k wIa8p>dO}x$wK&8ZlUj max_nonce: + if logger: + logger.warning("Max nonce exceeded during mining.") + raise MiningExceededError("Mining failed: max_nonce exceeded") + + # Hash header (unchanged logic) + block_hash = calculate_hash(block.to_header_dict()) + + # Optional progress reporting + if progress_callback: + progress_callback(block, block_hash) + + if block_hash.startswith(target): + block.hash = block_hash + if logger: + logger.info(f"Success! Hash: {block_hash}") + return block + + block.nonce += 1 diff --git a/core/__init__.py b/core/__init__.py new file mode 100644 index 0000000..2122211 --- /dev/null +++ b/core/__init__.py @@ -0,0 +1,7 @@ +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"] \ No newline at end of file diff --git a/core/__pycache__/__init__.cpython-311.pyc b/core/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8b3aab2c7d7c97c4b1bf2edc047748f1e77a1cbe GIT binary patch literal 483 zcmZvXF-ycS6vtoMrtP&HhntI=3SImFA`W(u!xQ9mDTPqm;uX{ONbBI{M{sj@k^43s zOD4g|MTFaK@+I621(QGj{9efWzxHE1_5jz-^tJfG^Gg-GWo%(RMRa~z(=cTFyu_;MM~imC|JN5^R`@|ZEK zTgFb7&l~rwtg|PcpNlQq=)@(s2&cwp?Wy%hWfATsDpt%y{k^^#Zg;K GaO59PQh-4K literal 0 HcmV?d00001 diff --git a/core/__pycache__/block.cpython-311.pyc b/core/__pycache__/block.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..16bd63d7c06ae5161898656fd660542d30cbe2f4 GIT binary patch literal 1871 zcmb_c!Ef7S6n}Q?G)bG%L7Ocl1$S_05XwM898jq?R zFi}fnprWN@Rt~K+D-5*mtKn3-2XLFBNO^?#^hgK5chWQCS<%X}Guq3ijtuae>ISC- z^{H5QedgSW%U$k#>jphxZdzh9o(Z|-3(F4Oz!$L=x*aDj5{l63bYtBQeA|gN@~!K! zCLHf3rwp75CdB#W3yoD|%JRe-DyhpEd}VAP4B=U=nf#vI_~FZ+TR*jaZ11(RhkOp5_VcIB37^I- zK11+2IVrr1i=h4|HSq;h`{jvZ$X#4Q7{VwImBk0GXleOi=|Uu{_2em9*J%KH!eMd> zIyn9QEn~&Tug>vBPt;%f0B{;0K2K0VI8HpT;)DK5*5pmcVh)#+avJe9im^x_iPis` z*f+_E+>bFD&*DrS%g~=cE$>-+X%*praXgyN6TJx>(OXwG>UsKu@vV+ynsLcAI|1u? zq|cbjGy9Jiz+ZF-LGBUf?;~*l95^e%Cd)HMv{aHdWBC{Z2|fcJ+XXHkNdU+A42XT#fq>B zzLmM^mZus9a$LixXD$+$3ojKk1MdjDfWTH zN!L&ODG~?Z_9e<0msa5jxyK~o!xQF4G{83FvETmJ*eB99tcT5lMxS(O1NEdM^m82f z>XlB(2~zaf{2A>u4BTE>v5U2myQRy5Yzdk>p^{kKtX8cu*JEOYj;VnvdnuYD9+uuA zT}{(OlGKM0)!*)`ojRl06Xw9o!PG+PVGN!syBm}qCO#?Dn$SDKM`R} z>>?(NFY47rZ#PI9#0__d_5e7$)LH2v*@`?hsDYP&#|HJBL96m07w3hn!a7Z_4~=+8 z_3zu4qwyb|Tz%pbAu8go_Ide{S+cqIm9QS!m71eHFrBqeJ$WAC3ZY76MZ@)dMQnVd z4|%C-Pt90EHIYT%o=l{tRE1r3y@cbM!gU_o?pn~l42tMdFtExpJ#r3JrZ<@0u*rF^ zo1=Z*jBZKY5s0vvBiGv_w;D6QyxX2yJQ-PRjx4@N-e|rt-%2jDlMBt{LMM}b_Fgv* z*T?qjr|FrK^i1QsR{BmmeWwf3@c32IFZ-tWc1zXTs@7DsPCB#a`U8{GReFoc=B8yB zUcxXoE4)@BI%ya`*UVDD5h)DO|H?eIDk@b=xLba8gocdg;M)p5Me3U%L}TF;5bZ?2 zt|Tcl7;A`BS<$-?p(MnwbtR_U>H?xyF+A@65M0EQ%4ioZqrfq_%jt@jphtGj?}Gof zc&TEg>{w-|=FC+qkAtVi5AZf>Tm=1Ey%*&Dp_oMmeHzY>h`KSxSO+GWgR28G&B4`` S-(ncwW%P>f{<(tTJO2-)9j5aD literal 0 HcmV?d00001 diff --git a/core/__pycache__/contract.cpython-311.pyc b/core/__pycache__/contract.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..731dfd015e72a02aa75be4616dcfe3be416c0e8e GIT binary patch literal 2054 zcmZ`4U2oe|^!O`IGPhxAS(8mLYX#P@5hmzBizd)EZLpyYL<`bFk!5`ETBF1c*UtJe z7xiHe6`DkakUEJW@l-&2;U~bJcuc2Ai!4o=#1n5uWzw`K&b6JebZ{IWpL@^ux#ynz zI5Lt4goO(m_ErYq55DMwoDw_JNNfTDA~c}RzhXmZigi%{5#9kJT?Zm>i2%3pO`t9j zVFoJ7pL}_#B81r)pV`j*`S-0JV&{1zHi1H2AW#>HfcoNfsLR}TT_G~J6&B98 zj?YZXzi3(uwnJCm%j*cI^rr3DO|zlXC2F;O+jVs6T(z0&G^yk3S6rrluL~+`Fy0ix}UT1=DusESFJ4NgCDGa+oy?Wcr4&QreYWwa>|Nvm=Ps zp!e+lb@@hq9iJWC088OG1y097x3H|65Wwes4*bwB^k9b*fv|=9w+FmcB~X5NvJ3-h zvRg9qzmlR2!>zEY1{qHz(hLM@AO>G*St6DP>)}`gAekAM1Ya8nzze{hTh00W@KPXZkoyk47l*4Oz-1uW z)!dgko=ZY43X8BLU4})Wk~2?WA>&r!O?ftMMe6DP0v)s(5`^le=ed@RjYITB+g})x zFib)i_0pc?h6WaEEt8qefvvt|O+S^W=)aU1rYHa}_DfB%y{%EF8+*M@3?zcuJv!UPGmYBgLOxIncj3Y1R1vIA_`oijiKb zsPVlF_smn@FfEG*6iT>YW89F;XGS>DZlQH*^lzq-h7sn@E?Kn2M@l8n_(cq#8OWqZ z6^;&+-COg_>#P@DoxTjU`YSqrGZ~r>+d%1gRW?P%> zYO@_}wl_AhG5zR%p%j6bEB}k`YkNDz*MAWZ;cr`;?rPH=Z90x><9PBsLWmSp!>?Du z%zxYe+UZfaGnzkrT)I;#o<1hsIi?_esgmJi)iImYFv7fHG+okea9T5rFWP1!<*0^1 zT+1*RKLDm9`WYCnYkUNZkCzznzd`RLqP4*jiK3Fl@DXJM602l=)5>A^&2_!r4l0o^~1 N{^EOo{|6)X`44@21g8K1 literal 0 HcmV?d00001 diff --git a/core/__pycache__/state.cpython-311.pyc b/core/__pycache__/state.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4aa9288407531a1a5f6b4b96a8734cf7c573d50b GIT binary patch literal 4196 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 literal 0 HcmV?d00001 diff --git a/core/__pycache__/transaction.cpython-311.pyc b/core/__pycache__/transaction.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ad39fc3597b2eec75a3656e29f3c2b1be9de0189 GIT binary patch literal 2843 zcmbtW&1)M+6o30Atz_Aq66(f~}@aQ>sAimP8oDM!SyXNGo-A zmDoZ_eQ?3hP<#kY3O@Ky+y>`VD9xcgfKB|lEg8_cDNy}?P%)wMPH-x7!_1gBJ~S4c zz^4Yu%q}9Ua!&A)v2f8$=XHai6q(d?s;SvUVq7F7PtwgDS=ZWs=Uo&ZfC2QTNnV$A zewj-Oy08pM(U5d;88%R3(_5SL>5}1}<#pe(n3Q!t>bW-zI9=03Gc7G`XYytu?e~@d zJ`O;@-_BP^9zdBZL&}>j^C)pGu`Hm}x*e27l;}#<%2I*T>Drdf!R~6!0nAG+zVpVY zkC?fw>_f@BnGR@KKH-2WJ3h-W(Q=MVjI@!tg`7{z<%_26NM_zl8;+=Jw&sX-CTBPS zN-bN<6`X+8GP3G-M9av|5_%^RqNJOW9!kQL971Bz(_?OXp~l~w`NT4aHFK#j^HIjk z#OJh(Ig`#4W5(Vy=4fHjky9z0w^ON7Uz?dnTca}FdIHH3)CY!E#Cu)!!5DVkEvf$q zO2K;H;OgavlgI|1DZ^{ZH%HfwqR>-UPSS1#KRdVyq&+-PpnEg)W{^SjU>}Q}&>C8Ya{bEvBwFVF2X(Z`F-96;xi{DVmjr@j02g)` zgoYrv7idftmFiaV&-C4TJ!|WD=ZYqDHKY*?IxvKui&UPh6J-jWN15bVk>fD z{dDc*Yt_hjEi%61t1HKAin^_gY$+pEcY6074s9*smitDfnce=hNXN8 zZo`6b4Q_L)n0OCE!tXjZnN|aCE}~a;gMwx@$A_f@OELq@kyyw%{#laGA#8*`yA={a zSCK_mTT~E=QRz@Sa<1a5IBqQDF9nom%E|TdZ{PYQSW)6t zC0gdjSvH-QBfl`TBAqIuV1XvEan0@N|SjiUl;G25EgVwVaVkIl)vam)DC~ z%7;>^&x%^sogw|W0a1`Ra)IOv2C-c`5$Yz)W!dW=0dqFjk!kTEl+dJSeU4?7{o=#A7M zp}C1r$8kcYmd=hgvx)m4>KRTq8)r^~X@)Q#3A New Contract Deployed at: {contract_address[:10]}...") + + # Bob interacts with deployed contract + print("\n[5] Interaction: Bob sends data 'Hello Blockchain' to Contract") + + if contract_address is None: + print("ERROR: Contract not deployed. Skipping interaction.") + return + + nonce = get_next_nonce(bob_pk) + + tx_call = Transaction( + sender=bob_pk, + receiver=contract_address, + amount=0, + nonce=nonce, + data="Hello Blockchain" + ) + tx_call.sign(bob_sk) + increment_nonce(bob_pk) + + mempool.add_transaction(tx_call) + + # Mine block 2 + print("\n[6] Consensus: Mining Block 2...") + + pending_txs_2 = mempool.get_transactions_for_block() + + block_2 = Block( + index=chain.last_block.index + 1, + previous_hash=chain.last_block.hash, + transactions=pending_txs_2 + ) + + mined_block_2 = mine_block(block_2) + + if chain.add_block(mined_block_2): + print(f" Block #{mined_block_2.index} added!") + for tx in mined_block_2.transactions: + state.apply_transaction(tx) + else: + print("ERROR: Block 2 rejected by chain!") + + # Final balances and contract state + print("\n[7] Final State Check:") + print(f" Alice Balance: {state.get_account(alice_pk)['balance']}") + print(f" Bob Balance: {state.get_account(bob_pk)['balance']}") + + if contract_address is not None: + contract_acc = state.get_account(contract_address) + print(f" Contract Storage: {contract_acc['storage']}") + else: + print(" No contract deployed.") + + +if __name__ == "__main__": + asyncio.run(node_loop()) diff --git a/network/__init__.py b/network/__init__.py new file mode 100644 index 0000000..742fbe2 --- /dev/null +++ b/network/__init__.py @@ -0,0 +1,3 @@ +from .p2p import P2PNetwork + +__all__ = ["P2PNetwork"] \ No newline at end of file diff --git a/network/__pycache__/__init__.cpython-311.pyc b/network/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dbc6bae3b324e9a1b1aab2f7e3ad6965e8e45ab6 GIT binary patch literal 227 zcmZ3^%ge<81RY)zGR=YXV-N=hn4pZ$50)gBTDK9}g5NiI3MSsQkrYlbfGXnv-f*#0k_1vaMJN dNPJ*sWMsU-AbkN9J>VAa&}(1^!6J5`G5{LaH|_uc literal 0 HcmV?d00001 diff --git a/network/__pycache__/p2p.cpython-311.pyc b/network/__pycache__/p2p.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3462cd25416f0b8942861bda05daa2321239e12a GIT binary patch literal 2622 zcmcgt%WE4)7@vJfYeiNPnLL~}X4<&eOR?;xEi_J?AlspDHS`5BhGo6Gw%1G;7geGeO*Fw7i1nWk*h~|mAplB-2pBUYz_<|ul#MvdDEop3-E8f3~&X_pfXaS6iS(cTEjEoU9gnPDZDQgVCU4|H>){@Z4y=2Y)1D@A7*E;M7(eSB8=m}(LjTEk3X>m#OU7n3%bASqo&@_cg(Y8BRZw}VTI$;N zWTw%`P69myu!1%Y99k7GCpPfVs#uecR)VwzDx^1e3KeG4MfkV+0p3R(*ygq94eln? zZZ})F&^kU>>W^3)#vfSJGaVoX18;*?;f=weOz;hQdSP%#;lK~Xay*42GAu&bWSDdS z`V#dt;+6Kbm=d(4Sictl{(x@agMrKyDE$9yUPCvY<38d}%=yxk4VMPl4;w2T-JP{j)&n0?Bys?H zk%J6aV1^t?7ErX*k&LO6nw}s(h*@pQ<#{e{k_9ED>_i(RNR4#}-6HGqTp$k=r5d zG1eX*%X}HFH6yBZX!$udF2Y;(rSLhrB77qPGw@y=LtgL_hNF5BIl3%(EF>Z^__DAY ztLI_#vhbC_<_Jp*+yYt@&Z7lR5lXL&MYlFym^B>?_b|?zlxj03wkRg1ZdzZMMy|9s zItchIAubulN<5(6muv`TO1fYde#|g+7lM=BA%4f%Mcpj0_bE6OiWmkgI|yNK&ZNv0 z5VKLxrox9rjn*%9w;YIe6+2S&DnNK1>Y~`8Y<3;`VfaV=`s~l{RkzwRSnC9sm) zg3L+7U~2Ee(OsZUsNC(IrQ>+;C$GG;MG8nS3g= s`80VI6zuJy?*f4PIBo;&SqZ)klv)YCy4b_PsypE?*7o- bytes (JSON encoded) + """ + + try: + # --- Guard message shape --- + if not hasattr(msg, "data"): + raise TypeError("Incoming message missing 'data' attribute") + + if not isinstance(msg.data, (bytes, bytearray)): + raise TypeError("msg.data must be bytes") + + decoded = msg.data.decode() + data = json.loads(decoded) + + # Validate JSON structure + if not isinstance(data, dict) or "type" not in data or "data" not in data: + raise ValueError("Invalid message format") + + await self.handler_callback(data) + + except Exception as e: + print(f"Network Error: {e}") diff --git a/node/__init__.py b/node/__init__.py new file mode 100644 index 0000000..434db3e --- /dev/null +++ b/node/__init__.py @@ -0,0 +1,3 @@ +from .mempool import Mempool + +__all__ = ["Mempool"] \ No newline at end of file diff --git a/node/__pycache__/__init__.cpython-311.pyc b/node/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..99d0cb75cc2fe077f4c868c43b742e2c027f24ca GIT binary patch literal 225 zcmZ3^%ge<81k=1GWSRo$#~=<2FhLogC4h|S3@HpLj5!Rsj8Tk?3@J?Mj8RM}%)tzr zEH4>>(wdC7*nLxT3-a@GikN|XKTYOa?72t+x7g$36LWIn<5x0#2ATUy&Dkm@v^ce> zIL4zO#y2xB(>WtCGcP7DKP5FLJ|4&~iI3MSsQkrYlbfGXnv-f*#0k^}va47LNPJ*s ZWMsU-Aael~J>VAU&~9J{!6J5`G63VuH<$nb literal 0 HcmV?d00001 diff --git a/node/__pycache__/mempool.cpython-311.pyc b/node/__pycache__/mempool.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e3cb03852024a182f6ee8e519c41d8d6a82abf1a GIT binary patch literal 1325 zcmZ`&%}*0S6rb64OR39;)bOE5Mu;K3a4;Iaj0s{yP3S@N(lptuooQKYcgxH|q^Su9 zOwgFf4G$)A!VvzMVw%|O)p%cnCr-ZE?XE@iZTGitX5QEQ-s_j1o)iN4^kmQctRVDV zE<%a7l~V%BK2p#UqG$(Ed=SMPy~)RdxYIfRc2AsD(TKEk-=_U zuyRqzIOOc+;#~f@U|i%MRrAZHWiGB7rj@sB%JP*U<5YFaP3k(FIl5l!Z`Uyu?#LQM zKfpF>UAw*;ZzhKu{_j+lQHWT8g)G%CcL$VxRL2MKy(ovUbsTN;(K=d-E`87$FS4oQ zL$ElGtT6Oo1vy=j0d(MkMGWYP*OpSGEen(iVi1QLmM{vAXPcmk?(6Gl}w$5pG` zw498>WzDVvjgl87geT>?&!ANAD6BRVcHyrB6|BVrfNkU@k=FZR_TB8;`SJ)YkYnh>(G#lQd^7&d&+=GOOW<#cJdBwuqY{%nZZTWfIn1c zxx8k`RSdW~N7mI6b2`ZqdeP?ki?UsKSsM>|`@fAGWibu_6sAyLy4gGSaa}&m-W$8I zaKu}E1G_1opll`~{~nf6VY=?7biHEJjk44=U0>fY%E1ZmhLBta=V^c=hb}@8T?Wo(aFAd~W|1xRlb$8-28G_^!?5RP Zo(9E>VT?Ux5JT9D@Kf&n`p1DE_&3^aEA;>X literal 0 HcmV?d00001 diff --git a/node/mempool.py b/node/mempool.py new file mode 100644 index 0000000..07773c2 --- /dev/null +++ b/node/mempool.py @@ -0,0 +1,49 @@ +from consensus.pow import calculate_hash + + +class Mempool: + def __init__(self): + self.pending_txs = [] + self.seen_tx_ids = set() # Dedup tracking + + def _get_tx_id(self, tx): + """ + Compute a unique deterministic ID for a transaction. + Uses full serialized tx (payload + signature). + """ + return calculate_hash(tx.to_dict()) + + def add_transaction(self, tx): + """ + Adds a transaction to the pool if: + - Signature is valid + - Transaction is not a duplicate + """ + + if not tx.verify(): + print("Mempool: Invalid signature rejected") + return False + + tx_id = self._get_tx_id(tx) + + if tx_id in self.seen_tx_ids: + print("Mempool: Duplicate transaction rejected") + return False + + self.pending_txs.append(tx) + self.seen_tx_ids.add(tx_id) + + return True + + def get_transactions_for_block(self): + """ + Returns pending transactions and clears the pool. + """ + + txs = self.pending_txs[:] + + # Clear both list and dedup set to stay in sync + self.pending_txs = [] + self.seen_tx_ids.clear() + + return txs diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..819e170 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +pynacl==1.6.2 +libp2p==0.5.0 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..eeab7fa --- /dev/null +++ b/setup.py @@ -0,0 +1,18 @@ +from setuptools import setup, find_packages + +setup( + name="minichain", + version="0.1.0", + packages=find_packages(), # Will detect core, consensus, network, etc. + py_modules=["main"], + install_requires=[ + "pynacl", + "py-libp2p", + ], + entry_points={ + "console_scripts": [ + "minichain=main:main", # Points to main.py -> main() + ], + }, + python_requires=">=3.8", +) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/__pycache__/test_contract.cpython-311.pyc b/tests/__pycache__/test_contract.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..362872cf612fde9956aff72e4cd19317f83a299b GIT binary patch literal 3074 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 W&b8mXedp~ayH+AZ=mQ4ZxPJkx7xv5m literal 0 HcmV?d00001 diff --git a/tests/test_contract.py b/tests/test_contract.py new file mode 100644 index 0000000..204af06 --- /dev/null +++ b/tests/test_contract.py @@ -0,0 +1,153 @@ +import unittest +import sys +import os + +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from core import State, Transaction +from nacl.signing import SigningKey +from nacl.encoding import HexEncoder + + +class TestSmartContract(unittest.TestCase): + + def setUp(self): + self.state = State() + self.sk = SigningKey.generate() + self.pk = self.sk.verify_key.encode(encoder=HexEncoder).decode() + self.state.credit_mining_reward(self.pk, 100) + + def test_deploy_and_execute(self): + """Happy path: deploy and increment counter.""" + + code = """ +if msg['data'] == 'increment': + storage['counter'] = storage.get('counter', 0) + 1 +""" + + # Deploy contract + tx_deploy = Transaction(self.pk, None, 0, 0, data=code) + tx_deploy.sign(self.sk) + + contract_addr = self.state.apply_transaction(tx_deploy) + self.assertTrue(isinstance(contract_addr, str)) + + # Call contract + tx_call = Transaction(self.pk, contract_addr, 0, 1, data="increment") + tx_call.sign(self.sk) + + success = self.state.apply_transaction(tx_call) + self.assertTrue(success) + + contract_acc = self.state.get_account(contract_addr) + self.assertEqual(contract_acc["storage"]["counter"], 1) + + def test_deploy_insufficient_balance(self): + """Deploy should fail if sender balance is insufficient.""" + + poor_sk = SigningKey.generate() + poor_pk = poor_sk.verify_key.encode(encoder=HexEncoder).decode() + + code = "storage['x'] = 1" + + tx = Transaction(poor_pk, None, 1000, 0, data=code) + tx.sign(poor_sk) + + result = self.state.apply_transaction(tx) + self.assertFalse(result) + + def test_call_non_existent_contract(self): + """Calling unknown contract should fail.""" + + tx = Transaction(self.pk, "nonexistent_address", 0, 0, data="increment") + tx.sign(self.sk) + + result = self.state.apply_transaction(tx) + self.assertFalse(result) + + def test_contract_runtime_exception(self): + """Contract raising exception should fail and not mutate storage.""" + + code = """ +raise Exception("boom") +""" + + # Deploy contract + tx_deploy = Transaction(self.pk, None, 0, 0, data=code) + tx_deploy.sign(self.sk) + + contract_addr = self.state.apply_transaction(tx_deploy) + self.assertTrue(isinstance(contract_addr, str)) + + # Call contract that raises + tx_call = Transaction(self.pk, contract_addr, 0, 1, data="anything") + tx_call.sign(self.sk) + + result = self.state.apply_transaction(tx_call) + self.assertFalse(result) + + contract_acc = self.state.get_account(contract_addr) + self.assertEqual(contract_acc["storage"], {}) + + def test_redeploy_same_address(self): + """Redeploying same contract address should fail.""" + + code = "storage['x'] = 1" + + # First deploy + tx1 = Transaction(self.pk, None, 0, 0, data=code) + tx1.sign(self.sk) + + addr = self.state.apply_transaction(tx1) + self.assertTrue(isinstance(addr, str)) + + # Use correct next nonce to bypass nonce validation + sender_after = self.state.get_account(self.pk) + next_nonce = sender_after["nonce"] + + tx2 = Transaction(self.pk, None, 0, next_nonce, data=code) + tx2.sign(self.sk) + + result = self.state.apply_transaction(tx2) + self.assertFalse(result) + + def test_balance_and_nonce_updates(self): + """Verify sender balance and nonce after deploy and call.""" + + sender_before = self.state.get_account(self.pk) + initial_balance = sender_before["balance"] + initial_nonce = sender_before["nonce"] + + code = "storage['x'] = 1" + + # Deploy contract + tx_deploy = Transaction(self.pk, None, 10, initial_nonce, data=code) + tx_deploy.sign(self.sk) + + contract_addr = self.state.apply_transaction(tx_deploy) + self.assertTrue(isinstance(contract_addr, str)) + + sender_after_deploy = self.state.get_account(self.pk) + self.assertEqual(sender_after_deploy["nonce"], initial_nonce + 1) + self.assertEqual(sender_after_deploy["balance"], initial_balance - 10) + + # Call contract + tx_call = Transaction( + self.pk, + contract_addr, + 5, + sender_after_deploy["nonce"], + data="anything" + ) + tx_call.sign(self.sk) + + result = self.state.apply_transaction(tx_call) + self.assertTrue(result) + + sender_after_call = self.state.get_account(self.pk) + self.assertEqual(sender_after_call["nonce"], initial_nonce + 2) + self.assertEqual(sender_after_call["balance"], initial_balance - 15) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_core.py b/tests/test_core.py new file mode 100644 index 0000000..adc1e17 --- /dev/null +++ b/tests/test_core.py @@ -0,0 +1,70 @@ +import unittest +import time +from nacl.signing import SigningKey +from nacl.encoding import HexEncoder + +# Adjust import path to look at root directory +import sys +import os +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from core import Transaction, Blockchain, Block, State +from consensus import mine_block + +class TestCore(unittest.TestCase): + def setUp(self): + self.state = State() + self.chain = Blockchain() + + # Setup Alice + self.alice_sk = SigningKey.generate() + self.alice_pk = self.alice_sk.verify_key.encode(encoder=HexEncoder).decode() + + # Setup Bob + self.bob_sk = SigningKey.generate() + self.bob_pk = self.bob_sk.verify_key.encode(encoder=HexEncoder).decode() + + def test_genesis_block(self): + """Check if genesis block is created correctly.""" + self.assertEqual(len(self.chain.chain), 1) + self.assertEqual(self.chain.last_block.index, 0) + self.assertEqual(self.chain.last_block.previous_hash, "0") + + def test_transaction_signature(self): + """Check that valid signatures pass and invalid ones fail.""" + tx = Transaction(self.alice_pk, self.bob_pk, 10, 0) + tx.sign(self.alice_sk) + self.assertTrue(tx.verify()) + + # Tamper with amount + tx.amount = 100 + self.assertFalse(tx.verify()) + + def test_state_transfer(self): + """Test simple balance transfer.""" + # 1. Credit Alice + self.state.credit_mining_reward(self.alice_pk, 100) + + # 2. Transfer + tx = Transaction(self.alice_pk, self.bob_pk, 40, 0) + tx.sign(self.alice_sk) + + result = self.state.apply_transaction(tx) + self.assertTrue(result) + + # 3. Check Balances + self.assertEqual(self.state.get_account(self.alice_pk)['balance'], 60) + self.assertEqual(self.state.get_account(self.bob_pk)['balance'], 40) + + def test_insufficient_funds(self): + """Test that you cannot spend more than you have.""" + self.state.credit_mining_reward(self.alice_pk, 10) + + tx = Transaction(self.alice_pk, self.bob_pk, 50, 0) + tx.sign(self.alice_sk) + + result = self.state.apply_transaction(tx) + self.assertFalse(result) + +if __name__ == '__main__': + unittest.main() \ No newline at end of file From da8d1089b39a61f046e4ab39eceeb20ee4a75138 Mon Sep 17 00:00:00 2001 From: Aniket Date: Sat, 14 Feb 2026 18:17:41 +0530 Subject: [PATCH 02/29] Update consensus/__init__.py Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- consensus/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/consensus/__init__.py b/consensus/__init__.py index 8c54d78..5354380 100644 --- a/consensus/__init__.py +++ b/consensus/__init__.py @@ -1,3 +1,3 @@ -from .pow import mine_block, calculate_hash +from .pow import mine_block, calculate_hash, MiningExceededError -__all__ = ["mine_block", "calculate_hash"] \ No newline at end of file +__all__ = ["MiningExceededError", "calculate_hash", "mine_block"] \ No newline at end of file From dfbcd959574402eab715f26d8392d0026882da5a Mon Sep 17 00:00:00 2001 From: Aniket Date: Sat, 14 Feb 2026 19:25:22 +0530 Subject: [PATCH 03/29] Enhance mine_block with timeout and cancellation support Updated mining logic to include timeout and cancellation options. --- consensus/pow.py | 48 +++++++++++++++++++++++------------------------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/consensus/pow.py b/consensus/pow.py index ee89b71..ccb1059 100644 --- a/consensus/pow.py +++ b/consensus/pow.py @@ -1,10 +1,11 @@ import json +import time from nacl.hash import sha256 from nacl.encoding import HexEncoder class MiningExceededError(Exception): - """Raised when max_nonce is exceeded during mining.""" + """Raised when max_nonce, timeout, or cancellation is exceeded during mining.""" pass @@ -17,52 +18,49 @@ def calculate_hash(block_dict): def mine_block( block, difficulty=4, - max_nonce=None, + max_nonce=10_000_000, # Default upper bound to prevent infinite mining + timeout_seconds=None, # Optional timeout limit in seconds logger=None, progress_callback=None ): - """ - Mines a block using Proof-of-Work. - - Parameters: - block - Block object - difficulty - Number of leading zeros required - max_nonce - Optional upper bound for nonce attempts - logger - Optional logger instance - progress_callback - Optional callback(block, hash) - - Returns: - Mined block if successful - - Raises: - MiningExceededError if max_nonce is reached - """ + """Mines a block using Proof-of-Work with nonce, timeout, and cancellation limits.""" target = "0" * difficulty block.nonce = 0 + start_time = time.time() # Record mining start time if logger: logger.info(f"Mining block {block.index} (Difficulty: {difficulty})") while True: - # Check max_nonce limit - if max_nonce is not None and block.nonce > max_nonce: + # Enforce max_nonce limit + if block.nonce > max_nonce: if logger: logger.warning("Max nonce exceeded during mining.") raise MiningExceededError("Mining failed: max_nonce exceeded") - # Hash header (unchanged logic) - block_hash = calculate_hash(block.to_header_dict()) + # Enforce timeout if specified + if timeout_seconds is not None and (time.time() - start_time) > timeout_seconds: + if logger: + logger.warning("Mining timeout exceeded.") + raise MiningExceededError("Mining failed: timeout exceeded") + + block_hash = calculate_hash(block.to_header_dict()) # Compute current hash - # Optional progress reporting + # Allow cancellation via progress callback if progress_callback: - progress_callback(block, block_hash) + should_continue = progress_callback(block, block_hash) + if should_continue is False: + if logger: + logger.info("Mining cancelled via progress_callback.") + raise MiningExceededError("Mining cancelled") + # Check difficulty target if block_hash.startswith(target): block.hash = block_hash if logger: logger.info(f"Success! Hash: {block_hash}") return block - block.nonce += 1 + block.nonce += 1 # Increment nonce for next attempt From 8fb795daf09883b0a521614a1c45a41f74f293e3 Mon Sep 17 00:00:00 2001 From: Aniket Date: Sat, 14 Feb 2026 19:35:09 +0530 Subject: [PATCH 04/29] Code rabbit follow-up Replaced print statements with logging for better monitoring and debugging. --- main.py | 130 ++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 85 insertions(+), 45 deletions(-) diff --git a/main.py b/main.py index afc1ff4..802b379 100644 --- a/main.py +++ b/main.py @@ -1,4 +1,5 @@ import asyncio +import logging from nacl.signing import SigningKey from nacl.encoding import HexEncoder @@ -8,6 +9,9 @@ from consensus import mine_block +logger = logging.getLogger(__name__) + + def create_wallet(): """Generate a new keypair.""" sk = SigningKey.generate() @@ -16,43 +20,50 @@ def create_wallet(): async def node_loop(): - print("--- Starting MiniChain Node with Smart Contracts ---") + logger.info("Starting MiniChain Node with Smart Contracts") - # Initialize core components state = State() chain = Blockchain() mempool = Mempool() - # Track pending nonces locally pending_nonce_map = {} + def sync_nonce(address): + account = state.get_account(address) + if account: + pending_nonce_map[address] = account["nonce"] + else: + pending_nonce_map.pop(address, None) + def get_next_nonce(address): - if address not in pending_nonce_map: - pending_nonce_map[address] = state.get_account(address)['nonce'] - return pending_nonce_map[address] + account_nonce = state.get_account(address)["nonce"] + local_nonce = pending_nonce_map.get(address, account_nonce) + next_nonce = max(account_nonce, local_nonce) + pending_nonce_map[address] = next_nonce + return next_nonce def increment_nonce(address): - pending_nonce_map[address] += 1 + pending_nonce_map[address] = pending_nonce_map.get( + address, state.get_account(address)["nonce"] + ) + 1 async def handle_network_data(data): - print(f"[Network] Received: {data}") + logger.info("Received network data: %s", data) network = P2PNetwork(handle_network_data) await network.start() - # Create wallets alice_sk, alice_pk = create_wallet() bob_sk, bob_pk = create_wallet() - print(f"Alice Address: {alice_pk[:10]}...") - print(f"Bob Address: {bob_pk[:10]}...") + logger.info("Alice Address: %s...", alice_pk[:10]) + logger.info("Bob Address: %s...", bob_pk[:10]) - # Credit Alice with initial balance - print("\n[1] Genesis: Crediting Alice with 100 coins") + logger.info("[1] Genesis: Crediting Alice with 100 coins") state.credit_mining_reward(alice_pk, reward=100) + sync_nonce(alice_pk) - # Alice sends 10 coins to Bob - print("\n[2] Transaction: Alice sends 10 coins to Bob") + logger.info("[2] Transaction: Alice sends 10 coins to Bob") nonce = get_next_nonce(alice_pk) @@ -60,22 +71,23 @@ async def handle_network_data(data): sender=alice_pk, receiver=bob_pk, amount=10, - nonce=nonce + nonce=nonce, ) tx_payment.sign(alice_sk) increment_nonce(alice_pk) if mempool.add_transaction(tx_payment): await network.broadcast_transaction(tx_payment) + else: + logger.warning("Transaction rejected by mempool: %s", getattr(tx_payment, "hash", None)) + sync_nonce(alice_pk) - # Alice deploys a storage contract - print("\n[3] Smart Contract: Alice deploys a 'Storage' contract") + logger.info("[3] Smart Contract: Alice deploys a 'Storage' contract") contract_code = """ -# Storage Contract +# Storage Contract (UNSAFE EXAMPLE) if msg['data']: storage['value'] = msg['data'] - print(f"Contract: Stored value '{msg['data']}'") """ nonce = get_next_nonce(alice_pk) @@ -85,44 +97,58 @@ async def handle_network_data(data): receiver=None, amount=0, nonce=nonce, - data=contract_code + data=contract_code, ) tx_deploy.sign(alice_sk) increment_nonce(alice_pk) if mempool.add_transaction(tx_deploy): await network.broadcast_transaction(tx_deploy) + else: + logger.warning("Contract deploy rejected: %s", getattr(tx_deploy, "hash", None)) + sync_nonce(alice_pk) - # Mine block 1 - print("\n[4] Consensus: Mining Block 1...") + logger.info("[4] Consensus: Mining Block 1") pending_txs = mempool.get_transactions_for_block() block_1 = Block( index=chain.last_block.index + 1, previous_hash=chain.last_block.hash, - transactions=pending_txs + transactions=pending_txs, ) mined_block_1 = mine_block(block_1) - contract_address = None if chain.add_block(mined_block_1): - print(f" Block #{mined_block_1.index} added!") + logger.info("Block #%s added", mined_block_1.index) for tx in mined_block_1.transactions: result = state.apply_transaction(tx) if isinstance(result, str): contract_address = result - print(f" -> New Contract Deployed at: {contract_address[:10]}...") + logger.info("New Contract Deployed at: %s...", contract_address[:10]) + sync_nonce(tx.sender) + + elif result is False or result is None: + logger.error( + "Transaction failed in block %s: %s", + mined_block_1.index, + getattr(tx, "hash", None), + ) + sync_nonce(tx.sender) + + else: + sync_nonce(tx.sender) + else: + logger.error("Block 1 rejected by chain") - # Bob interacts with deployed contract - print("\n[5] Interaction: Bob sends data 'Hello Blockchain' to Contract") + logger.info("[5] Interaction: Bob sends data to Contract") if contract_address is None: - print("ERROR: Contract not deployed. Skipping interaction.") + logger.error("Contract not deployed. Skipping interaction.") return nonce = get_next_nonce(bob_pk) @@ -132,44 +158,58 @@ async def handle_network_data(data): receiver=contract_address, amount=0, nonce=nonce, - data="Hello Blockchain" + data="Hello Blockchain", ) tx_call.sign(bob_sk) increment_nonce(bob_pk) - mempool.add_transaction(tx_call) + if mempool.add_transaction(tx_call): + await network.broadcast_transaction(tx_call) + else: + logger.warning("Contract call rejected: %s", getattr(tx_call, "hash", None)) + sync_nonce(bob_pk) - # Mine block 2 - print("\n[6] Consensus: Mining Block 2...") + logger.info("[6] Consensus: Mining Block 2") pending_txs_2 = mempool.get_transactions_for_block() block_2 = Block( index=chain.last_block.index + 1, previous_hash=chain.last_block.hash, - transactions=pending_txs_2 + transactions=pending_txs_2, ) mined_block_2 = mine_block(block_2) if chain.add_block(mined_block_2): - print(f" Block #{mined_block_2.index} added!") + logger.info("Block #%s added", mined_block_2.index) + for tx in mined_block_2.transactions: - state.apply_transaction(tx) + result = state.apply_transaction(tx) + + if result is False or result is None: + logger.error( + "Transaction failed in block %s: %s", + mined_block_2.index, + getattr(tx, "hash", None), + ) + sync_nonce(tx.sender) + else: + sync_nonce(tx.sender) else: - print("ERROR: Block 2 rejected by chain!") + logger.error("Block 2 rejected by chain") - # Final balances and contract state - print("\n[7] Final State Check:") - print(f" Alice Balance: {state.get_account(alice_pk)['balance']}") - print(f" Bob Balance: {state.get_account(bob_pk)['balance']}") + logger.info("[7] Final State Check") + logger.info("Alice Balance: %s", state.get_account(alice_pk)["balance"]) + logger.info("Bob Balance: %s", state.get_account(bob_pk)["balance"]) - if contract_address is not None: + if contract_address: contract_acc = state.get_account(contract_address) - print(f" Contract Storage: {contract_acc['storage']}") + logger.info("Contract Storage: %s", contract_acc["storage"]) else: - print(" No contract deployed.") + logger.info("No contract deployed.") if __name__ == "__main__": + logging.basicConfig(level=logging.INFO) asyncio.run(node_loop()) From c51717ada3cf738738124db37eb81bb9900b69b7 Mon Sep 17 00:00:00 2001 From: Aniket Date: Sat, 14 Feb 2026 19:36:58 +0530 Subject: [PATCH 05/29] Code rabbit follow-up --- consensus/pow.py | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/consensus/pow.py b/consensus/pow.py index ccb1059..dd72652 100644 --- a/consensus/pow.py +++ b/consensus/pow.py @@ -6,7 +6,6 @@ class MiningExceededError(Exception): """Raised when max_nonce, timeout, or cancellation is exceeded during mining.""" - pass def calculate_hash(block_dict): @@ -18,24 +17,28 @@ def calculate_hash(block_dict): def mine_block( block, difficulty=4, - max_nonce=10_000_000, # Default upper bound to prevent infinite mining - timeout_seconds=None, # Optional timeout limit in seconds + max_nonce=10_000_000, + timeout_seconds=None, logger=None, progress_callback=None ): - """Mines a block using Proof-of-Work with nonce, timeout, and cancellation limits.""" + """Mines a block using Proof-of-Work without mutating input block until success.""" target = "0" * difficulty - block.nonce = 0 - start_time = time.time() # Record mining start time + local_nonce = 0 + start_time = time.time() if logger: - logger.info(f"Mining block {block.index} (Difficulty: {difficulty})") + logger.info( + "Mining block %s (Difficulty: %s)", + block.index, + difficulty, + ) while True: - # Enforce max_nonce limit - if block.nonce > max_nonce: + # 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") @@ -46,11 +49,13 @@ def mine_block( logger.warning("Mining timeout exceeded.") raise MiningExceededError("Mining failed: timeout exceeded") - block_hash = calculate_hash(block.to_header_dict()) # Compute current hash + # Temporarily set nonce for hashing only + block.nonce = local_nonce + block_hash = calculate_hash(block.to_header_dict()) - # Allow cancellation via progress callback + # Allow cancellation via progress callback (pass nonce explicitly) if progress_callback: - should_continue = progress_callback(block, block_hash) + should_continue = progress_callback(local_nonce, block_hash) if should_continue is False: if logger: logger.info("Mining cancelled via progress_callback.") @@ -58,9 +63,11 @@ def mine_block( # Check difficulty target if block_hash.startswith(target): + block.nonce = local_nonce block.hash = block_hash if logger: - logger.info(f"Success! Hash: {block_hash}") + logger.info("Success! Hash: %s", block_hash) return block - block.nonce += 1 # Increment nonce for next attempt + # Increment nonce after attempt + local_nonce += 1 From d06f2b9585cdbd9b63db450cf4e1ba695ea0436f Mon Sep 17 00:00:00 2001 From: Aniket Date: Sat, 14 Feb 2026 19:38:47 +0530 Subject: [PATCH 06/29] Code rabbit follow-up --- core/__init__.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/core/__init__.py b/core/__init__.py index 2122211..ce204c7 100644 --- a/core/__init__.py +++ b/core/__init__.py @@ -4,4 +4,10 @@ from .state import State from .contract import ContractMachine -__all__ = ["Block", "Blockchain", "Transaction", "State", "ContractMachine"] \ No newline at end of file +__all__ = [ + "Block", + "Blockchain", + "Transaction", + "State", + "ContractMachine", +] From b1639cbe17cd62afcc942d0a3ff5246b3e8ae7b1 Mon Sep 17 00:00:00 2001 From: Aniket Date: Sat, 14 Feb 2026 19:42:24 +0530 Subject: [PATCH 07/29] Implement Blockchain class for managing state and blocks Refactor blockchain management by introducing Blockchain class, handling block creation and state transitions. --- core/chain.py | 167 ++++++++++++++------------------------------------ 1 file changed, 47 insertions(+), 120 deletions(-) diff --git a/core/chain.py b/core/chain.py index 36068ba..222bd7e 100644 --- a/core/chain.py +++ b/core/chain.py @@ -1,130 +1,57 @@ -import copy -from nacl.hash import sha256 -from nacl.encoding import HexEncoder -from core.contract import ContractMachine +from core.block import Block +from core.state import State -class State: - def __init__(self): - # Stores account state including balance, nonce, code, and storage - self.accounts = {} - self.contract_machine = ContractMachine(self) - - def copy(self): - # Return deep copy of state for safe block validation - new_state = State() - new_state.accounts = copy.deepcopy(self.accounts) - new_state.contract_machine = ContractMachine(new_state) - return new_state - - def validate_and_apply(self, tx): - # Entry point used by Blockchain to validate and execute tx - return self.apply_transaction(tx) - - def get_account(self, address): - # Lazily initialize account if not present - if address not in self.accounts: - self.accounts[address] = { - 'balance': 0, - 'nonce': 0, - 'code': None, - 'storage': {} - } - return self.accounts[address] - - def verify_transaction_logic(self, tx): - # Validate signature, balance, and nonce - if not tx.verify(): - print(f"Error: Invalid signature for tx from {tx.sender[:8]}...") - return False - - sender_acc = self.get_account(tx.sender) +class Blockchain: + """ + Manages the blockchain, validates blocks, and commits state transitions. + """ - if sender_acc['balance'] < tx.amount: - print(f"Error: Insufficient balance for {tx.sender[:8]}...") - return False - - if sender_acc['nonce'] != tx.nonce: - print(f"Error: Invalid nonce. Expected {sender_acc['nonce']}, got {tx.nonce}") - return False - - return True - - def apply_transaction(self, tx): - # Apply transaction and mutate state - if not self.verify_transaction_logic(tx): + def __init__(self): + self.chain = [] + self.state = State() + 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. + """ + 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. + """ + + # Check previous hash linkage + if block.previous_hash != self.last_block.hash: return False - sender = self.accounts[tx.sender] + # Validate transactions on a temporary state copy + temp_state = self.state.copy() - # Deduct balance and increment nonce - sender['balance'] -= tx.amount - sender['nonce'] += 1 + for tx in block.transactions: + result = temp_state.validate_and_apply(tx) - # Handle contract deployment - if tx.receiver is None or tx.receiver == "": - contract_address = self.derive_contract_address(tx.sender, tx.nonce) - existing = self.accounts.get(contract_address) - - if existing and existing.get("code"): - sender['balance'] += tx.amount - sender['nonce'] -= 1 - return False - - return self.create_contract(contract_address, tx.data) - - # Handle contract call - if tx.data is not None: - receiver = self.accounts.get(tx.receiver) - - if not receiver or not receiver.get("code"): - sender['balance'] += tx.amount - sender['nonce'] -= 1 - return False - - receiver['balance'] += tx.amount - - success = self.contract_machine.execute( - contract_address=tx.receiver, - sender_address=tx.sender, - payload=tx.data, - amount=tx.amount - ) - - if not success: - receiver['balance'] -= tx.amount - sender['balance'] += tx.amount - sender['nonce'] -= 1 + # Reject block if any transaction fails + if result is False or result is None: return False - return True - - # Handle regular transfer - receiver = self.get_account(tx.receiver) - receiver['balance'] += tx.amount + # All transactions valid → commit state and append block + self.state = temp_state + self.chain.append(block) return True - - def derive_contract_address(self, sender, nonce): - # Deterministically derive contract address - raw = f"{sender}{nonce}".encode() - return sha256(raw, encoder=HexEncoder).decode()[:40] - - def create_contract(self, contract_address, code): - # Create new contract account - self.accounts[contract_address] = { - 'balance': 0, - 'nonce': 0, - 'code': code, - 'storage': {} - } - return contract_address - - def update_contract_storage(self, address, new_storage): - # Update storage for contract - if address in self.accounts: - self.accounts[address]['storage'] = new_storage - - def credit_mining_reward(self, miner_address, reward=50): - # Credit mining reward to account - account = self.get_account(miner_address) - account['balance'] += reward From 2d43e2aa7c69a2ec0e0d01e91b80118d8192ff15 Mon Sep 17 00:00:00 2001 From: Aniket Date: Sat, 14 Feb 2026 19:43:34 +0530 Subject: [PATCH 08/29] Apply suggestion from @coderabbitai[bot] Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- core/contract.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/contract.py b/core/contract.py index 5623c44..8c80e4b 100644 --- a/core/contract.py +++ b/core/contract.py @@ -37,7 +37,7 @@ def execute(self, contract_address, sender_address, payload, amount): try: # SECURITY WARNING: exec() is unsafe for production blockchains. # This is for educational/research purposes only. - exec(code, {}, context) + exec(code, {"__builtins__": {}}, context) # Update storage if execution succeeded self.state.update_contract_storage(contract_address, context['storage']) From 1349d3a8955541f24d15bf7570b95b71f7ac37ad Mon Sep 17 00:00:00 2001 From: Aniket Date: Sat, 14 Feb 2026 19:45:30 +0530 Subject: [PATCH 09/29] Enhance contract execution with improved safety measures Refactor contract execution to improve safety and clarity. --- core/contract.py | 64 +++++++++++++++++++++++++++++++----------------- 1 file changed, 42 insertions(+), 22 deletions(-) diff --git a/core/contract.py b/core/contract.py index 8c80e4b..956ed83 100644 --- a/core/contract.py +++ b/core/contract.py @@ -1,47 +1,67 @@ class ContractMachine: """ A minimal execution environment for Python-based smart contracts. + WARNING: Still not production-safe. For educational use only. """ + def __init__(self, state): self.state = state def execute(self, contract_address, sender_address, payload, amount): """ Executes the contract code associated with the contract_address. - - :param contract_address: Address of the contract to run - :param sender_address: Address calling the contract - :param payload: Input data (msg['data']) - :param amount: Value sent (msg['value']) """ - # Retrieve contract + account = self.state.get_account(contract_address) - code = account.get('code') - storage = account.get('storage', {}) + code = account.get("code") + + # Defensive copy of storage to prevent direct mutation + storage = dict(account.get("storage", {})) if not code: return False - # Sandbox Context - # We allow the contract to read/write its own storage and see msg details + # Restricted builtins (explicit allowlist) + safe_builtins = { + "True": True, + "False": False, + "None": None, + "range": range, + "len": len, + "min": min, + "max": max, + "abs": abs, + } + + globals_for_exec = { + "__builtins__": safe_builtins + } + + # Execution context (locals) context = { - 'storage': storage, - 'msg': { - 'sender': sender_address, - 'value': amount, - 'data': payload + "storage": storage, + "msg": { + "sender": sender_address, + "value": amount, + "data": payload, }, - 'print': print # Allow debug prints + "print": print, # Explicitly allowed for debugging } try: - # SECURITY WARNING: exec() is unsafe for production blockchains. - # This is for educational/research purposes only. - exec(code, {"__builtins__": {}}, context) - - # Update storage if execution succeeded - self.state.update_contract_storage(contract_address, context['storage']) + # SECURITY WARNING: + # This is a restricted but still educational sandbox. + # Production systems should use WASM or a proper VM. + exec(code, globals_for_exec, context) + + # Commit updated storage only after successful execution + self.state.update_contract_storage( + contract_address, + context["storage"] + ) + return True + except Exception as e: print(f"Contract Execution Failed: {e}") return False From a07c68ccb02b0d8c7eaf49b960255fa8233cbc90 Mon Sep 17 00:00:00 2001 From: Aniket Date: Sat, 14 Feb 2026 19:51:09 +0530 Subject: [PATCH 10/29] Enhance Transaction class with error handling and data preservation Updated the Transaction class to preserve None for data and handle additional exceptions in the verify method. --- core/transaction.py | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/core/transaction.py b/core/transaction.py index 092ef40..efd7483 100644 --- a/core/transaction.py +++ b/core/transaction.py @@ -2,7 +2,8 @@ import time from nacl.signing import SigningKey, VerifyKey from nacl.encoding import HexEncoder -from nacl.exceptions import BadSignatureError +from nacl.exceptions import BadSignatureError, CryptoError + class Transaction: def __init__(self, sender, receiver, amount, nonce, data=None, signature=None): @@ -10,7 +11,7 @@ def __init__(self, sender, receiver, amount, nonce, data=None, signature=None): self.receiver = receiver # Public key (Hex str) or None for Deploy self.amount = amount self.nonce = nonce - self.data = data or "" # Smart Contract Code or Input Data + self.data = data # Preserve None (do NOT normalize to "") self.timestamp = time.time() self.signature = signature # Hex str @@ -22,7 +23,7 @@ def to_dict(self): "nonce": self.nonce, "data": self.data, "timestamp": self.timestamp, - "signature": self.signature + "signature": self.signature, } @property @@ -33,9 +34,9 @@ def hash_payload(self): "receiver": self.receiver, "amount": self.amount, "nonce": self.nonce, - "data": self.data + "data": self.data, } - return json.dumps(payload, sort_keys=True).encode('utf-8') + return json.dumps(payload, sort_keys=True).encode("utf-8") def sign(self, signing_key: SigningKey): signed = signing_key.sign(self.hash_payload) @@ -44,9 +45,15 @@ def sign(self, signing_key: SigningKey): def verify(self): if not self.signature: return False + try: verify_key = VerifyKey(self.sender, encoder=HexEncoder) verify_key.verify(self.hash_payload, bytes.fromhex(self.signature)) return True - except BadSignatureError: - return False \ No newline at end of file + + except (BadSignatureError, CryptoError, ValueError): + # Covers: + # - Invalid signature + # - Malformed public key hex + # - Invalid hex in signature + return False From ff89df6666b357b60f1c25d2e9180a59f32c4116 Mon Sep 17 00:00:00 2001 From: Aniket Date: Sat, 14 Feb 2026 19:52:26 +0530 Subject: [PATCH 11/29] Replace print statements with logging calls --- network/p2p.py | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/network/p2p.py b/network/p2p.py index cc3384e..7e18412 100644 --- a/network/p2p.py +++ b/network/p2p.py @@ -1,4 +1,7 @@ import json +import logging + +logger = logging.getLogger(__name__) class P2PNetwork: @@ -22,38 +25,33 @@ def __init__(self, handler_callback): self.pubsub = None # Will be set in real implementation async def start(self): - print("Network: Listening on /ip4/0.0.0.0/tcp/0") + logger.info("Network: Listening on /ip4/0.0.0.0/tcp/0") # In real libp2p, we would await host.start() here async def broadcast_transaction(self, tx): msg = json.dumps({"type": "tx", "data": tx.to_dict()}) - print(f"Network: Broadcasting Tx from {tx.sender[:5]}...") + logger.info("Network: Broadcasting Tx from %s...", tx.sender[:5]) - # Publish if pubsub exists (real environment) if self.pubsub: await self.pubsub.publish("minichain-global", msg.encode()) else: - print("Network: pubsub not initialized (mock mode)") + logger.debug("Network: pubsub not initialized (mock mode)") async def broadcast_block(self, block): msg = json.dumps({"type": "block", "data": block.to_dict()}) - print(f"Network: Broadcasting Block #{block.index}") + logger.info("Network: Broadcasting Block #%d", block.index) if self.pubsub: await self.pubsub.publish("minichain-global", msg.encode()) else: - print("Network: pubsub not initialized (mock mode)") + logger.debug("Network: pubsub not initialized (mock mode)") async def handle_message(self, msg): """ Callback when a p2p message is received. - - Expected: - msg.data -> bytes (JSON encoded) """ try: - # --- Guard message shape --- if not hasattr(msg, "data"): raise TypeError("Incoming message missing 'data' attribute") @@ -63,11 +61,11 @@ async def handle_message(self, msg): decoded = msg.data.decode() data = json.loads(decoded) - # Validate JSON structure if not isinstance(data, dict) or "type" not in data or "data" not in data: raise ValueError("Invalid message format") - await self.handler_callback(data) + except (TypeError, ValueError, json.JSONDecodeError) as e: + logger.warning("Network Error: %s", e) + return - except Exception as e: - print(f"Network Error: {e}") + await self.handler_callback(data) From d2135fd6db2647aadc6c1208aa55cb6353bfdc45 Mon Sep 17 00:00:00 2001 From: Aniket Date: Sat, 14 Feb 2026 19:54:37 +0530 Subject: [PATCH 12/29] Update test_contract.py --- tests/test_contract.py | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/tests/test_contract.py b/tests/test_contract.py index 204af06..d464a1e 100644 --- a/tests/test_contract.py +++ b/tests/test_contract.py @@ -25,14 +25,12 @@ def test_deploy_and_execute(self): storage['counter'] = storage.get('counter', 0) + 1 """ - # Deploy contract tx_deploy = Transaction(self.pk, None, 0, 0, data=code) tx_deploy.sign(self.sk) contract_addr = self.state.apply_transaction(tx_deploy) self.assertTrue(isinstance(contract_addr, str)) - # Call contract tx_call = Transaction(self.pk, contract_addr, 0, 1, data="increment") tx_call.sign(self.sk) @@ -57,9 +55,13 @@ def test_deploy_insufficient_balance(self): self.assertFalse(result) def test_call_non_existent_contract(self): - """Calling unknown contract should fail.""" + """Calling unknown contract should fail with valid hex receiver.""" - tx = Transaction(self.pk, "nonexistent_address", 0, 0, data="increment") + # Generate a syntactically valid public key hex (but not deployed) + fake_sk = SigningKey.generate() + fake_receiver = fake_sk.verify_key.encode(encoder=HexEncoder).decode() + + tx = Transaction(self.pk, fake_receiver, 0, 0, data="increment") tx.sign(self.sk) result = self.state.apply_transaction(tx) @@ -72,14 +74,12 @@ def test_contract_runtime_exception(self): raise Exception("boom") """ - # Deploy contract tx_deploy = Transaction(self.pk, None, 0, 0, data=code) tx_deploy.sign(self.sk) contract_addr = self.state.apply_transaction(tx_deploy) self.assertTrue(isinstance(contract_addr, str)) - # Call contract that raises tx_call = Transaction(self.pk, contract_addr, 0, 1, data="anything") tx_call.sign(self.sk) @@ -90,7 +90,7 @@ def test_contract_runtime_exception(self): self.assertEqual(contract_acc["storage"], {}) def test_redeploy_same_address(self): - """Redeploying same contract address should fail.""" + """Force address collision and ensure redeploy fails.""" code = "storage['x'] = 1" @@ -98,13 +98,23 @@ def test_redeploy_same_address(self): tx1 = Transaction(self.pk, None, 0, 0, data=code) tx1.sign(self.sk) - addr = self.state.apply_transaction(tx1) - self.assertTrue(isinstance(addr, str)) + addr1 = self.state.apply_transaction(tx1) + self.assertTrue(isinstance(addr1, str)) - # Use correct next nonce to bypass nonce validation sender_after = self.state.get_account(self.pk) next_nonce = sender_after["nonce"] + # Compute the address that second deploy WOULD use + collision_addr = self.state.derive_contract_address(self.pk, next_nonce) + + # Manually pre-populate state to simulate collision + self.state.accounts[collision_addr] = { + "balance": 0, + "nonce": 0, + "code": "existing_code", + "storage": {}, + } + tx2 = Transaction(self.pk, None, 0, next_nonce, data=code) tx2.sign(self.sk) @@ -120,7 +130,6 @@ def test_balance_and_nonce_updates(self): code = "storage['x'] = 1" - # Deploy contract tx_deploy = Transaction(self.pk, None, 10, initial_nonce, data=code) tx_deploy.sign(self.sk) @@ -131,7 +140,6 @@ def test_balance_and_nonce_updates(self): self.assertEqual(sender_after_deploy["nonce"], initial_nonce + 1) self.assertEqual(sender_after_deploy["balance"], initial_balance - 10) - # Call contract tx_call = Transaction( self.pk, contract_addr, From a9c965a9cbed85ab703a52665d4f8feefc915e51 Mon Sep 17 00:00:00 2001 From: Aniket Date: Sat, 14 Feb 2026 19:56:16 +0530 Subject: [PATCH 13/29] Update test_contract.py --- tests/test_contract.py | 50 ++++++++---------------------------------- 1 file changed, 9 insertions(+), 41 deletions(-) diff --git a/tests/test_contract.py b/tests/test_contract.py index d464a1e..578f308 100644 --- a/tests/test_contract.py +++ b/tests/test_contract.py @@ -57,7 +57,6 @@ def test_deploy_insufficient_balance(self): def test_call_non_existent_contract(self): """Calling unknown contract should fail with valid hex receiver.""" - # Generate a syntactically valid public key hex (but not deployed) fake_sk = SigningKey.generate() fake_receiver = fake_sk.verify_key.encode(encoder=HexEncoder).decode() @@ -90,7 +89,7 @@ def test_contract_runtime_exception(self): self.assertEqual(contract_acc["storage"], {}) def test_redeploy_same_address(self): - """Force address collision and ensure redeploy fails.""" + """Deploying to an already-occupied contract address should fail.""" code = "storage['x'] = 1" @@ -98,23 +97,17 @@ def test_redeploy_same_address(self): tx1 = Transaction(self.pk, None, 0, 0, data=code) tx1.sign(self.sk) - addr1 = self.state.apply_transaction(tx1) - self.assertTrue(isinstance(addr1, str)) + addr = self.state.apply_transaction(tx1) + self.assertTrue(isinstance(addr, str)) - sender_after = self.state.get_account(self.pk) - next_nonce = sender_after["nonce"] - - # Compute the address that second deploy WOULD use + # Compute the address that a second deploy would use + next_nonce = self.state.get_account(self.pk)["nonce"] collision_addr = self.state.derive_contract_address(self.pk, next_nonce) - # Manually pre-populate state to simulate collision - self.state.accounts[collision_addr] = { - "balance": 0, - "nonce": 0, - "code": "existing_code", - "storage": {}, - } + # Pre-place contract to simulate collision + self.state.create_contract(collision_addr, "storage['y'] = 2") + # Attempt redeploy tx2 = Transaction(self.pk, None, 0, next_nonce, data=code) tx2.sign(self.sk) @@ -133,29 +126,4 @@ def test_balance_and_nonce_updates(self): tx_deploy = Transaction(self.pk, None, 10, initial_nonce, data=code) tx_deploy.sign(self.sk) - contract_addr = self.state.apply_transaction(tx_deploy) - self.assertTrue(isinstance(contract_addr, str)) - - sender_after_deploy = self.state.get_account(self.pk) - self.assertEqual(sender_after_deploy["nonce"], initial_nonce + 1) - self.assertEqual(sender_after_deploy["balance"], initial_balance - 10) - - tx_call = Transaction( - self.pk, - contract_addr, - 5, - sender_after_deploy["nonce"], - data="anything" - ) - tx_call.sign(self.sk) - - result = self.state.apply_transaction(tx_call) - self.assertTrue(result) - - sender_after_call = self.state.get_account(self.pk) - self.assertEqual(sender_after_call["nonce"], initial_nonce + 2) - self.assertEqual(sender_after_call["balance"], initial_balance - 15) - - -if __name__ == "__main__": - unittest.main() + contract_add_ From d0e44a68bf4121f529d1bc06b592d8249a8ad9bb Mon Sep 17 00:00:00 2001 From: Aniket Date: Sat, 14 Feb 2026 19:59:20 +0530 Subject: [PATCH 14/29] Update setup.py --- setup.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index eeab7fa..33e42e1 100644 --- a/setup.py +++ b/setup.py @@ -3,15 +3,15 @@ setup( name="minichain", version="0.1.0", - packages=find_packages(), # Will detect core, consensus, network, etc. + packages=find_packages(), py_modules=["main"], install_requires=[ - "pynacl", - "py-libp2p", + "pynacl==1.6.2", + "libp2p==0.5.0", # Fixed: was "py-libp2p" ], entry_points={ "console_scripts": [ - "minichain=main:main", # Points to main.py -> main() + "minichain=main:main", # Requires main() function in main.py ], }, python_requires=">=3.8", From 1c46fca6e19dad70730676ce044a6b5cd3cf3779 Mon Sep 17 00:00:00 2001 From: Aniket Date: Sat, 14 Feb 2026 20:04:00 +0530 Subject: [PATCH 15/29] Update main.py --- main.py | 71 ++++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 48 insertions(+), 23 deletions(-) diff --git a/main.py b/main.py index 802b379..06cd29e 100644 --- a/main.py +++ b/main.py @@ -13,7 +13,6 @@ def create_wallet(): - """Generate a new keypair.""" sk = SigningKey.generate() pk = sk.verify_key.encode(encoder=HexEncoder).decode() return sk, pk @@ -28,12 +27,10 @@ async def node_loop(): pending_nonce_map = {} + # Simplified: state.get_account always returns dict def sync_nonce(address): account = state.get_account(address) - if account: - pending_nonce_map[address] = account["nonce"] - else: - pending_nonce_map.pop(address, None) + pending_nonce_map[address] = account["nonce"] def get_next_nonce(address): account_nonce = state.get_account(address)["nonce"] @@ -42,11 +39,6 @@ def get_next_nonce(address): pending_nonce_map[address] = next_nonce return next_nonce - def increment_nonce(address): - pending_nonce_map[address] = pending_nonce_map.get( - address, state.get_account(address)["nonce"] - ) + 1 - async def handle_network_data(data): logger.info("Received network data: %s", data) @@ -63,6 +55,10 @@ async def handle_network_data(data): state.credit_mining_reward(alice_pk, reward=100) sync_nonce(alice_pk) + # ------------------------------- + # Alice Payment + # ------------------------------- + logger.info("[2] Transaction: Alice sends 10 coins to Bob") nonce = get_next_nonce(alice_pk) @@ -74,16 +70,26 @@ async def handle_network_data(data): nonce=nonce, ) tx_payment.sign(alice_sk) - increment_nonce(alice_pk) if mempool.add_transaction(tx_payment): + pending_nonce_map[alice_pk] = nonce + 1 await network.broadcast_transaction(tx_payment) else: - logger.warning("Transaction rejected by mempool: %s", getattr(tx_payment, "hash", None)) - sync_nonce(alice_pk) + logger.warning("Transaction rejected by mempool") + + # ------------------------------- + # Contract Deployment (UNSAFE) + # ------------------------------- logger.info("[3] Smart Contract: Alice deploys a 'Storage' contract") + # WARNING: + # This contract uses raw Python executed via exec inside ContractMachine. + # This is UNSAFE and should NEVER be used in production. + # TODO: Replace ContractMachine exec-based runtime with: + # - RestrictedPython + # - WASM-based VM + # - Custom DSL interpreter contract_code = """ # Storage Contract (UNSAFE EXAMPLE) if msg['data']: @@ -100,13 +106,16 @@ async def handle_network_data(data): data=contract_code, ) tx_deploy.sign(alice_sk) - increment_nonce(alice_pk) if mempool.add_transaction(tx_deploy): + pending_nonce_map[alice_pk] = nonce + 1 await network.broadcast_transaction(tx_deploy) else: - logger.warning("Contract deploy rejected: %s", getattr(tx_deploy, "hash", None)) - sync_nonce(alice_pk) + logger.warning("Contract deploy rejected") + + # ------------------------------- + # Mine Block 1 + # ------------------------------- logger.info("[4] Consensus: Mining Block 1") @@ -124,6 +133,10 @@ async def handle_network_data(data): if chain.add_block(mined_block_1): logger.info("Block #%s added", mined_block_1.index) + # Credit miner reward + miner_address = getattr(mined_block_1, "miner", alice_pk) + state.credit_mining_reward(miner_address) + for tx in mined_block_1.transactions: result = state.apply_transaction(tx) @@ -134,9 +147,8 @@ async def handle_network_data(data): elif result is False or result is None: logger.error( - "Transaction failed in block %s: %s", + "Transaction failed in block %s", mined_block_1.index, - getattr(tx, "hash", None), ) sync_nonce(tx.sender) @@ -145,6 +157,10 @@ async def handle_network_data(data): else: logger.error("Block 1 rejected by chain") + # ------------------------------- + # Bob Interaction + # ------------------------------- + logger.info("[5] Interaction: Bob sends data to Contract") if contract_address is None: @@ -161,13 +177,16 @@ async def handle_network_data(data): data="Hello Blockchain", ) tx_call.sign(bob_sk) - increment_nonce(bob_pk) if mempool.add_transaction(tx_call): + pending_nonce_map[bob_pk] = nonce + 1 await network.broadcast_transaction(tx_call) else: - logger.warning("Contract call rejected: %s", getattr(tx_call, "hash", None)) - sync_nonce(bob_pk) + logger.warning("Contract call rejected") + + # ------------------------------- + # Mine Block 2 + # ------------------------------- logger.info("[6] Consensus: Mining Block 2") @@ -184,14 +203,16 @@ async def handle_network_data(data): if chain.add_block(mined_block_2): logger.info("Block #%s added", mined_block_2.index) + miner_address = getattr(mined_block_2, "miner", alice_pk) + state.credit_mining_reward(miner_address) + for tx in mined_block_2.transactions: result = state.apply_transaction(tx) if result is False or result is None: logger.error( - "Transaction failed in block %s: %s", + "Transaction failed in block %s", mined_block_2.index, - getattr(tx, "hash", None), ) sync_nonce(tx.sender) else: @@ -199,6 +220,10 @@ async def handle_network_data(data): else: logger.error("Block 2 rejected by chain") + # ------------------------------- + # Final State + # ------------------------------- + logger.info("[7] Final State Check") logger.info("Alice Balance: %s", state.get_account(alice_pk)["balance"]) logger.info("Bob Balance: %s", state.get_account(bob_pk)["balance"]) From 6fbb613ffe73e1b325cdf099ede5fec9a14c7c89 Mon Sep 17 00:00:00 2001 From: aniket866 Date: Sat, 14 Feb 2026 22:40:48 +0530 Subject: [PATCH 16/29] fixing-all-code-rabbit-suggestions --- conftest.py | 4 + consensus/__init__.py | 2 +- core/chain.py | 16 ++ core/contract.py | 78 +++++++++- core/state.py | 50 +++++-- main.py | 137 ++++++++---------- node/mempool.py | 40 +++-- setup.py | 2 +- tests/__pycache__/__init__.cpython-311.pyc | Bin 0 -> 148 bytes ...test_contract.cpython-311-pytest-9.0.2.pyc | Bin 0 -> 8709 bytes .../test_core.cpython-311-pytest-9.0.2.pyc | Bin 0 -> 5006 bytes tests/test_contract.py | 2 - tests/test_core.py | 5 +- 13 files changed, 224 insertions(+), 112 deletions(-) create mode 100644 conftest.py create mode 100644 tests/__pycache__/__init__.cpython-311.pyc create mode 100644 tests/__pycache__/test_contract.cpython-311-pytest-9.0.2.pyc create mode 100644 tests/__pycache__/test_core.cpython-311-pytest-9.0.2.pyc diff --git a/conftest.py b/conftest.py new file mode 100644 index 0000000..0d6b08e --- /dev/null +++ b/conftest.py @@ -0,0 +1,4 @@ +import sys +import os + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) \ No newline at end of file diff --git a/consensus/__init__.py b/consensus/__init__.py index 5354380..119baf4 100644 --- a/consensus/__init__.py +++ b/consensus/__init__.py @@ -1,3 +1,3 @@ from .pow import mine_block, calculate_hash, MiningExceededError -__all__ = ["MiningExceededError", "calculate_hash", "mine_block"] \ No newline at end of file +__all__ = ["mine_block", "calculate_hash", "MiningExceededError"] \ No newline at end of file diff --git a/core/chain.py b/core/chain.py index 222bd7e..b03791f 100644 --- a/core/chain.py +++ b/core/chain.py @@ -1,5 +1,9 @@ from core.block import Block from core.state import State +from consensus import calculate_hash +import logging + +logger = logging.getLogger(__name__) class Blockchain: @@ -39,6 +43,17 @@ def add_block(self, block): # 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 + + # Verify block hash + if block.hash != calculate_hash(block.to_dict()): + logger.warning("Block %s rejected: Invalid hash %s", block.index, block.hash) return False # Validate transactions on a temporary state copy @@ -49,6 +64,7 @@ def add_block(self, block): # Reject block if any transaction fails if result is False or result is None: + logger.warning("Block %s rejected: Transaction failed validation", block.index) return False # All transactions valid → commit state and append block diff --git a/core/contract.py b/core/contract.py index 956ed83..5d5ba21 100644 --- a/core/contract.py +++ b/core/contract.py @@ -1,3 +1,29 @@ +import logging +import multiprocessing +import ast + +logger = logging.getLogger(__name__) + +def _safe_exec_worker(code, globals_dict, context_dict, result_queue): + """ + Worker function to execute contract code in a separate process. + """ + try: + # Attempt to set resource limits (Unix only) + try: + import resource + # Limit CPU time (seconds) and memory (bytes) - example values + resource.setrlimit(resource.RLIMIT_CPU, (1, 1)) + # resource.setrlimit(resource.RLIMIT_AS, (100 * 1024 * 1024, 100 * 1024 * 1024)) + except ImportError as e: + raise RuntimeError(f"Resource limits not enforced: {e}") + + exec(code, globals_dict, context_dict) + # Return the updated storage + result_queue.put({"status": "success", "storage": context_dict.get("storage")}) + except Exception as e: + result_queue.put({"status": "error", "error": str(e)}) + class ContractMachine: """ A minimal execution environment for Python-based smart contracts. @@ -13,6 +39,9 @@ def execute(self, contract_address, sender_address, payload, amount): """ account = self.state.get_account(contract_address) + if not account: + return False + code = account.get("code") # Defensive copy of storage to prevent direct mutation @@ -21,6 +50,10 @@ def execute(self, contract_address, sender_address, payload, amount): if not code: return False + # AST Validation to prevent introspection + if not self._validate_code_ast(code): + return False + # Restricted builtins (explicit allowlist) safe_builtins = { "True": True, @@ -49,19 +82,52 @@ def execute(self, contract_address, sender_address, payload, amount): } try: - # SECURITY WARNING: - # This is a restricted but still educational sandbox. - # Production systems should use WASM or a proper VM. - exec(code, globals_for_exec, context) + # Execute in a subprocess with timeout + queue = multiprocessing.Queue() + p = multiprocessing.Process( + target=_safe_exec_worker, + args=(code, globals_for_exec, context, queue) + ) + p.start() + p.join(timeout=2) # 2 second timeout + + if p.is_alive(): + p.kill() + logger.error("Contract execution timed out") + return False + + if queue.empty(): + logger.error("Contract execution crashed without result") + return False + + result = queue.get() + if result["status"] != "success": + logger.error(f"Contract Execution Failed: {result.get('error')}") + return False # Commit updated storage only after successful execution self.state.update_contract_storage( contract_address, - context["storage"] + result["storage"] ) return True except Exception as e: - print(f"Contract Execution Failed: {e}") + logger.error("Contract Execution Failed", exc_info=True) + return False + + def _validate_code_ast(self, code): + """Reject code that uses double underscores or introspection.""" + try: + tree = ast.parse(code) + for node in ast.walk(tree): + if isinstance(node, ast.Attribute) and node.attr.startswith("__"): + logger.warning("Rejected contract code with double-underscore attribute access.") + return False + if isinstance(node, ast.Name) and node.id.startswith("__"): + logger.warning("Rejected contract code with double-underscore name.") + return False + return True + except SyntaxError: return False diff --git a/core/state.py b/core/state.py index 8a7d463..17df4c3 100644 --- a/core/state.py +++ b/core/state.py @@ -1,6 +1,10 @@ from nacl.hash import sha256 from nacl.encoding import HexEncoder from core.contract import ContractMachine +import copy +import logging + +logger = logging.getLogger(__name__) class State: @@ -21,21 +25,37 @@ def get_account(self, address): def verify_transaction_logic(self, tx): if not tx.verify(): - print(f"Error: Invalid signature for tx from {tx.sender[:8]}...") + logger.error(f"Error: Invalid signature for tx from {tx.sender[:8]}...") return False sender_acc = self.get_account(tx.sender) if sender_acc['balance'] < tx.amount: - print(f"Error: Insufficient balance for {tx.sender[:8]}...") + logger.error(f"Error: Insufficient balance for {tx.sender[:8]}...") return False if sender_acc['nonce'] != tx.nonce: - print(f"Error: Invalid nonce. Expected {sender_acc['nonce']}, got {tx.nonce}") + logger.error(f"Error: Invalid nonce. Expected {sender_acc['nonce']}, got {tx.nonce}") return False return True + def copy(self): + """ + Return an independent copy of state for transactional validation. + """ + return copy.deepcopy(self) + + def validate_and_apply(self, tx): + """ + Validate and apply a transaction. + Returns the same success/failure shape as apply_transaction(). + NOTE: Delegates to apply_transaction. Callers should use this for + semantic validation entry points. + TODO: Implement specific semantic validation logic here. + """ + return self.apply_transaction(tx) + def apply_transaction(self, tx): """ Applies transaction and mutates state. @@ -60,20 +80,23 @@ def apply_transaction(self, tx): # Prevent redeploy collision existing = self.accounts.get(contract_address) if existing and existing.get("code"): + # Restore sender state on failure + sender['balance'] += tx.amount + sender['nonce'] -= 1 return False - return self.create_contract(contract_address, tx.data) + return self.create_contract(contract_address, tx.data, initial_balance=tx.amount) # LOGIC BRANCH 2: Contract Call - # If data is provided, treat as contract call - if tx.data is not None: + # If data is provided (non-empty), treat as contract call + if tx.data: receiver = self.accounts.get(tx.receiver) # Fail if contract does not exist or has no code if not receiver or not receiver.get("code"): - # Rollback sender changes + # Rollback sender balance, but nonce remains consumed sender['balance'] += tx.amount - sender['nonce'] -= 1 + # Do NOT rollback nonce return False # Credit contract balance @@ -87,10 +110,9 @@ def apply_transaction(self, tx): ) if not success: - # Rollback transfer if execution fails + # Rollback transfer if execution fails, nonce remains consumed receiver['balance'] -= tx.amount sender['balance'] += tx.amount - sender['nonce'] -= 1 return False return True @@ -101,12 +123,12 @@ def apply_transaction(self, tx): return True def derive_contract_address(self, sender, nonce): - raw = f"{sender}{nonce}".encode() + raw = f"{sender}:{nonce}".encode() return sha256(raw, encoder=HexEncoder).decode()[:40] - def create_contract(self, contract_address, code): + def create_contract(self, contract_address, code, initial_balance=0): self.accounts[contract_address] = { - 'balance': 0, + 'balance': initial_balance, 'nonce': 0, 'code': code, 'storage': {} @@ -116,6 +138,8 @@ def create_contract(self, contract_address, code): def update_contract_storage(self, address, new_storage): if address in self.accounts: self.accounts[address]['storage'] = new_storage + else: + raise KeyError(f"Contract address not found: {address}") def credit_mining_reward(self, miner_address, reward=50): account = self.get_account(miner_address) diff --git a/main.py b/main.py index 06cd29e..5926079 100644 --- a/main.py +++ b/main.py @@ -1,5 +1,6 @@ import asyncio import logging +import re from nacl.signing import SigningKey from nacl.encoding import HexEncoder @@ -11,6 +12,7 @@ logger = logging.getLogger(__name__) +BURN_ADDRESS = "0" * 40 def create_wallet(): sk = SigningKey.generate() @@ -18,6 +20,58 @@ def create_wallet(): return sk, pk +def mine_and_process_block(chain, mempool, state, pending_nonce_map): + """Helper to mine a block and apply transactions.""" + pending_txs = mempool.get_transactions_for_block() + + block = Block( + index=chain.last_block.index + 1, + previous_hash=chain.last_block.hash, + transactions=pending_txs, + ) + + mined_block = mine_block(block) + contract_address = None + + if chain.add_block(mined_block): + logger.info("Block #%s added", mined_block.index) + + # Credit miner reward + miner_attr = getattr(mined_block, "miner", None) + if miner_attr: + miner_address = miner_attr + else: + logger.warning("Block has no miner. Crediting treasury.") + miner_address = BURN_ADDRESS + + state.credit_mining_reward(miner_address) + + for tx in mined_block.transactions: + result = state.apply_transaction(tx) + + # Check for valid contract address (40 hex chars) + if isinstance(result, str) and re.match(r'^[0-9a-fA-F]{40}$', result): + contract_address = result + logger.info("New Contract Deployed at: %s", contract_address) + sync_nonce(state, pending_nonce_map, tx.sender) + elif result is True: + sync_nonce(state, pending_nonce_map, tx.sender) + elif result is False or result is None: + logger.error("Transaction failed in block %s", mined_block.index) + sync_nonce(state, pending_nonce_map, tx.sender) + + return mined_block, contract_address + else: + logger.error("Block rejected by chain") + return None, None + +def sync_nonce(state, pending_nonce_map, address): + account = state.get_account(address) + if account and "nonce" in account: + pending_nonce_map[address] = account["nonce"] + else: + pending_nonce_map[address] = 0 + async def node_loop(): logger.info("Starting MiniChain Node with Smart Contracts") @@ -27,22 +81,23 @@ async def node_loop(): pending_nonce_map = {} - # Simplified: state.get_account always returns dict - def sync_nonce(address): - account = state.get_account(address) - pending_nonce_map[address] = account["nonce"] - def get_next_nonce(address): account_nonce = state.get_account(address)["nonce"] local_nonce = pending_nonce_map.get(address, account_nonce) next_nonce = max(account_nonce, local_nonce) - pending_nonce_map[address] = next_nonce return next_nonce async def handle_network_data(data): logger.info("Received network data: %s", data) network = P2PNetwork(handle_network_data) + + try: + await _run_node(network, state, chain, mempool, pending_nonce_map, get_next_nonce) + finally: + await network.stop() + +async def _run_node(network, state, chain, mempool, pending_nonce_map, get_next_nonce): await network.start() alice_sk, alice_pk = create_wallet() @@ -53,7 +108,7 @@ async def handle_network_data(data): logger.info("[1] Genesis: Crediting Alice with 100 coins") state.credit_mining_reward(alice_pk, reward=100) - sync_nonce(alice_pk) + sync_nonce(state, pending_nonce_map, alice_pk) # ------------------------------- # Alice Payment @@ -119,43 +174,7 @@ async def handle_network_data(data): logger.info("[4] Consensus: Mining Block 1") - pending_txs = mempool.get_transactions_for_block() - - block_1 = Block( - index=chain.last_block.index + 1, - previous_hash=chain.last_block.hash, - transactions=pending_txs, - ) - - mined_block_1 = mine_block(block_1) - contract_address = None - - if chain.add_block(mined_block_1): - logger.info("Block #%s added", mined_block_1.index) - - # Credit miner reward - miner_address = getattr(mined_block_1, "miner", alice_pk) - state.credit_mining_reward(miner_address) - - for tx in mined_block_1.transactions: - result = state.apply_transaction(tx) - - if isinstance(result, str): - contract_address = result - logger.info("New Contract Deployed at: %s...", contract_address[:10]) - sync_nonce(tx.sender) - - elif result is False or result is None: - logger.error( - "Transaction failed in block %s", - mined_block_1.index, - ) - sync_nonce(tx.sender) - - else: - sync_nonce(tx.sender) - else: - logger.error("Block 1 rejected by chain") + _, contract_address = mine_and_process_block(chain, mempool, state, pending_nonce_map) # ------------------------------- # Bob Interaction @@ -190,35 +209,7 @@ async def handle_network_data(data): logger.info("[6] Consensus: Mining Block 2") - pending_txs_2 = mempool.get_transactions_for_block() - - block_2 = Block( - index=chain.last_block.index + 1, - previous_hash=chain.last_block.hash, - transactions=pending_txs_2, - ) - - mined_block_2 = mine_block(block_2) - - if chain.add_block(mined_block_2): - logger.info("Block #%s added", mined_block_2.index) - - miner_address = getattr(mined_block_2, "miner", alice_pk) - state.credit_mining_reward(miner_address) - - for tx in mined_block_2.transactions: - result = state.apply_transaction(tx) - - if result is False or result is None: - logger.error( - "Transaction failed in block %s", - mined_block_2.index, - ) - sync_nonce(tx.sender) - else: - sync_nonce(tx.sender) - else: - logger.error("Block 2 rejected by chain") + mine_and_process_block(chain, mempool, state, pending_nonce_map) # ------------------------------- # Final State diff --git a/node/mempool.py b/node/mempool.py index 07773c2..5e1c818 100644 --- a/node/mempool.py +++ b/node/mempool.py @@ -1,10 +1,15 @@ from consensus.pow import calculate_hash +import logging +import threading +logger = logging.getLogger(__name__) class Mempool: - def __init__(self): + def __init__(self, max_size=1000): self.pending_txs = [] self.seen_tx_ids = set() # Dedup tracking + self._lock = threading.Lock() + self.max_size = max_size def _get_tx_id(self, tx): """ @@ -21,29 +26,36 @@ def add_transaction(self, tx): """ if not tx.verify(): - print("Mempool: Invalid signature rejected") + logger.warning("Mempool: Invalid signature rejected") return False - tx_id = self._get_tx_id(tx) + with self._lock: + tx_id = self._get_tx_id(tx) - if tx_id in self.seen_tx_ids: - print("Mempool: Duplicate transaction rejected") - return False + if tx_id in self.seen_tx_ids: + logger.warning(f"Mempool: Duplicate transaction rejected {tx_id}") + return False + + if len(self.pending_txs) >= self.max_size: + # Simple eviction: drop oldest or reject. Here we reject. + logger.warning("Mempool: Full, rejecting transaction") + return False - self.pending_txs.append(tx) - self.seen_tx_ids.add(tx_id) + self.pending_txs.append(tx) + self.seen_tx_ids.add(tx_id) - return True + return True def get_transactions_for_block(self): """ Returns pending transactions and clears the pool. """ - txs = self.pending_txs[:] + with self._lock: + txs = self.pending_txs[:] - # Clear both list and dedup set to stay in sync - self.pending_txs = [] - self.seen_tx_ids.clear() + # Clear both list and dedup set to stay in sync + self.pending_txs = [] + self.seen_tx_ids.clear() - return txs + return txs diff --git a/setup.py b/setup.py index 33e42e1..142768d 100644 --- a/setup.py +++ b/setup.py @@ -14,5 +14,5 @@ "minichain=main:main", # Requires main() function in main.py ], }, - python_requires=">=3.8", + python_requires=">=3.9", ) diff --git a/tests/__pycache__/__init__.cpython-311.pyc b/tests/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..94a9cde9505cfb1700796aa85f28a72a1970dd8f GIT binary patch literal 148 zcmZ3^%ge<81Q&BAWP<3&AOZ#$p^VRLK*n^26oz01O-8?!3`I;p{%4TnFLh_Dn9$ylGNgo;+Xh&pg>7{yk0@&FAkgB{FKt1RJ$Tppgxev#r#0x R12ZEd;|B&9QN#=s0{{ZxAs_$% literal 0 HcmV?d00001 diff --git a/tests/__pycache__/test_contract.cpython-311-pytest-9.0.2.pyc b/tests/__pycache__/test_contract.cpython-311-pytest-9.0.2.pyc new file mode 100644 index 0000000000000000000000000000000000000000..94215a9e5538b45b5ceb7166dc02bf547e2efa48 GIT binary patch literal 8709 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 NZ_~!G-OxZW_b)KHA1nX> literal 0 HcmV?d00001 diff --git a/tests/__pycache__/test_core.cpython-311-pytest-9.0.2.pyc b/tests/__pycache__/test_core.cpython-311-pytest-9.0.2.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b19cc98b56de6269b1e87b876bd8d07d7ebbc4e5 GIT binary patch literal 5006 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 literal 0 HcmV?d00001 diff --git a/tests/test_contract.py b/tests/test_contract.py index 578f308..de9521f 100644 --- a/tests/test_contract.py +++ b/tests/test_contract.py @@ -2,8 +2,6 @@ import sys import os -sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) - from core import State, Transaction from nacl.signing import SigningKey from nacl.encoding import HexEncoder diff --git a/tests/test_core.py b/tests/test_core.py index adc1e17..16e5ef6 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1,12 +1,10 @@ import unittest -import time from nacl.signing import SigningKey from nacl.encoding import HexEncoder # Adjust import path to look at root directory import sys import os -sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from core import Transaction, Blockchain, Block, State from consensus import mine_block @@ -65,6 +63,9 @@ def test_insufficient_funds(self): result = self.state.apply_transaction(tx) self.assertFalse(result) + + self.assertEqual(self.state.get_account(self.alice_pk)['balance'], 10) + self.assertEqual(self.state.get_account(self.bob_pk)['balance'], 0) if __name__ == '__main__': unittest.main() \ No newline at end of file From d49a985312c02074db6c0e8afc155d78fa9e0f0a Mon Sep 17 00:00:00 2001 From: aniket866 Date: Sat, 14 Feb 2026 22:56:10 +0530 Subject: [PATCH 17/29] fixing-code-rabbit-suggestions --- core/contract.py | 3 ++- main.py | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/core/contract.py b/core/contract.py index 5d5ba21..ae148df 100644 --- a/core/contract.py +++ b/core/contract.py @@ -16,7 +16,8 @@ def _safe_exec_worker(code, globals_dict, context_dict, result_queue): resource.setrlimit(resource.RLIMIT_CPU, (1, 1)) # resource.setrlimit(resource.RLIMIT_AS, (100 * 1024 * 1024, 100 * 1024 * 1024)) except ImportError as e: - raise RuntimeError(f"Resource limits not enforced: {e}") + import sys + print(f"Warning: Resource limits not enforced: {e}", file=sys.stderr) exec(code, globals_dict, context_dict) # Return the updated storage diff --git a/main.py b/main.py index 5926079..f923e28 100644 --- a/main.py +++ b/main.py @@ -12,7 +12,7 @@ logger = logging.getLogger(__name__) -BURN_ADDRESS = "0" * 40 +TREASURY_ADDRESS = "0" * 40 def create_wallet(): sk = SigningKey.generate() @@ -38,11 +38,11 @@ def mine_and_process_block(chain, mempool, state, pending_nonce_map): # Credit miner reward miner_attr = getattr(mined_block, "miner", None) - if miner_attr: + if miner_attr is not None: miner_address = miner_attr else: logger.warning("Block has no miner. Crediting treasury.") - miner_address = BURN_ADDRESS + miner_address = TREASURY_ADDRESS state.credit_mining_reward(miner_address) From b32d077f4a15daa3c39adcd1c1a2d89044faf3f9 Mon Sep 17 00:00:00 2001 From: aniket866 Date: Sat, 14 Feb 2026 23:08:43 +0530 Subject: [PATCH 18/29] fixing-code-rabbit-suggestions --- core/contract.py | 5 +++-- main.py | 8 ++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/core/contract.py b/core/contract.py index ae148df..de12131 100644 --- a/core/contract.py +++ b/core/contract.py @@ -1,6 +1,7 @@ import logging import multiprocessing import ast +import sys logger = logging.getLogger(__name__) @@ -16,8 +17,8 @@ def _safe_exec_worker(code, globals_dict, context_dict, result_queue): resource.setrlimit(resource.RLIMIT_CPU, (1, 1)) # resource.setrlimit(resource.RLIMIT_AS, (100 * 1024 * 1024, 100 * 1024 * 1024)) except ImportError as e: - import sys - print(f"Warning: Resource limits not enforced: {e}", file=sys.stderr) + logger.error(f"Resource limits not enforced: {e}") + raise RuntimeError(f"Resource limits not enforced: {e}") exec(code, globals_dict, context_dict) # Return the updated storage diff --git a/main.py b/main.py index f923e28..a2f0b13 100644 --- a/main.py +++ b/main.py @@ -12,7 +12,7 @@ logger = logging.getLogger(__name__) -TREASURY_ADDRESS = "0" * 40 +BURN_ADDRESS = "0" * 40 def create_wallet(): sk = SigningKey.generate() @@ -38,11 +38,11 @@ def mine_and_process_block(chain, mempool, state, pending_nonce_map): # Credit miner reward miner_attr = getattr(mined_block, "miner", None) - if miner_attr is not None: + if isinstance(miner_attr, str) and re.match(r'^[0-9a-fA-F]{40}$', miner_attr): miner_address = miner_attr else: - logger.warning("Block has no miner. Crediting treasury.") - miner_address = TREASURY_ADDRESS + logger.warning("Block has no miner or invalid address. Crediting burn address.") + miner_address = BURN_ADDRESS state.credit_mining_reward(miner_address) From 0f40e6aa61e91d3b246f813139ea8039228b96a2 Mon Sep 17 00:00:00 2001 From: aniket866 Date: Sun, 15 Feb 2026 00:00:38 +0530 Subject: [PATCH 19/29] core-contract-base-setup --- .../__pycache__/__init__.cpython-311.pyc | Bin 271 -> 297 bytes consensus/__pycache__/pow.cpython-311.pyc | Bin 1654 -> 3060 bytes consensus/pow.py | 34 +++---- core/__pycache__/__init__.cpython-311.pyc | Bin 483 -> 489 bytes core/__pycache__/block.cpython-311.pyc | Bin 1871 -> 2400 bytes core/__pycache__/chain.cpython-311.pyc | Bin 1936 -> 4314 bytes core/__pycache__/contract.cpython-311.pyc | Bin 2054 -> 8515 bytes core/__pycache__/state.cpython-311.pyc | Bin 4196 -> 7501 bytes core/__pycache__/transaction.cpython-311.pyc | Bin 2843 -> 3378 bytes core/block.py | 40 ++++---- core/chain.py | 54 ++++++----- core/contract.py | 49 ++++++++-- core/state.py | 35 +++++-- core/transaction.py | 10 +- main.py | 87 +++++++++++++----- network/p2p.py | 48 +++++++--- node/mempool.py | 23 ++--- setup.py | 6 +- .../__pycache__/test_contract.cpython-311.pyc | Bin 3074 -> 8025 bytes tests/__pycache__/test_core.cpython-311.pyc | Bin 4976 -> 5734 bytes tests/test_contract.py | 11 ++- tests/test_core.py | 16 ++-- 22 files changed, 275 insertions(+), 138 deletions(-) diff --git a/consensus/__pycache__/__init__.cpython-311.pyc b/consensus/__pycache__/__init__.cpython-311.pyc index 0c30acc6092fe3f3bc31d7c9408cb478abaf7363..39cd960bb136e5c3f3e15a89de574e97abff6585 100644 GIT binary patch delta 186 zcmeBYTFJz_oR^o20SMM^o{-r(kylb*3CNkwkiw9{n8T3E7{!>&6vdRw9L3DYkPZ}E z#2Cer!W7J)$^4QLs8Ex6VxWxXEn(lxyv)3G*NWuS)Rfc|*P^2QB0o*mTg(Odw+lCr1=3n<1SCE%Gcq#XU~ssA8$IB1yZ}ZG>>yag3DgDvT81wF delta 184 zcmZ3<)X&7boR^o20SFwtCS*oVqlg)(q=*Gb_-V4- zVlK!phl)&`Emn79Q$fq9jw2E!%18&^4@BscWa3Zm@BkAdQp8mK$%;q`QD%fM7VZO)Q(jX9mfPnyuJ!DTR(!CT|VBb(bmYfE= zL-NfG-_N{x-|#(tok+wGv=6`EmCPtY{~(ig3$_L?K7zp>!U$6(RBZNCk@DugqR)q} zBka3}uwV2)^ds~Lo@OfsaNr&)2BWBb;Rp`jqlzI+-9-85b0W4Rw4}X`YE53GVX%ip zMAj+#Fje$pAL#hq%neDF}R@TyeebxDy~Nn^#91r}FSFvylYyHiW~fE(dBnJ6lTqZuwt?`n)EGV)j%34KR-c;^PZQfM_ zAncJ4474neTIX^Cx6Jmf5@0+*p&b->frvT@@QXT-J*0Q>);nVZp!(ML&<9|^(`@z5 zsQrfY*#8i;9(6RgDLs6|Uni@1&7gYa>g~hP7V!FqKp%D909=7;evU5*6^Od1u{Y=6 z``z!)vunJz#wsf;&)zO6!Unr0@>o=-ZsjRAswt|@ZHRZZYOtcOy!D5C$PKJ(itGk) zrM#)RAyF0-EV^N0q$J&TL)sb-M&0O|xP_%vQPcB2H|81Uuq5biygAl%0ZR0(W{bG$_3b zr(OirkU?Q;!^2AL)4LGBzFu@bqp;tgsvxlqpW1fVKy|X-ORX2L?T8L3pxxFx_K!(L zjevU62-LfG)K)?`V4(HR34&7t>mDTp3KD@4HhdeNZ8Zm$2EbCJquw>Zq24`FNY5Ox zak!WJ8D;qYa{2%9--uQLWW&GVo$deOL$r65V?M-sx!^+ zorwH4zRBLb9E(U_FPB6FGga_-JAD5?`Be1=Bt}RC?JvKgk;MCRN~~591X0tbRPw1*b62I66$x^TeixK95EWs8O+}M* z=~EHZb#WEai%XrYvd!AjGB&QU6Gyr;?6{V{RUN&-Z?RtWcvXP_5wB9PE4)+^@yt=Z z>1ZrfhgveN0@UG5RZkt$J}%(~z16F!o6TL0vh$FfhS zE3%?1vLv_xNnTOhpd@2)%MIV*RZ@wY6^$fMHwKnfT_X^xuaOr4l34R`H|W9V#&8EJ zFxxKEZeJ?~oF+oK#E|7n$|_*yre4~b8-f*9Mct*LbnvBSopmy)M%~nESzYdU@+!HlAqImrJeYc6Hnb=bmG~c!+~h*7X+l?k@y?I z;YJ9>2lw*_^q5JHIei1*N8&2wFxkE2gQR)tip9*?%$&*09j3?4+=7)Z*y(~9FA%-g zOgi`7)o+)q$ys}H_DR*6T=d-HxFP7BgcrGbS_R8MdX$kd)C4ZTlArtJc0_$i8V5MA@Gu0gWqU5yY$eN4eFev|I!fA%=Pbn_#A>YWDa5C$@$7lReQ$yh}k?UO_yM{|nW(^vwVO literal 1654 zcmZ`(&1)M+6ra)V>SNb(YcEdXe$XuzjTc%`>bSL)Qc`3E+f93j(~A&@_3qeOTiSJZ zR*fyzpo0&l_~1h!fi&Pt-4>_NV~+h3v_@dD5C{|>d=vPVQ{Jq;BDZ}Sy?Hw`@B26N zTQVskpu5Y5<^_k)KkTDNk*;ug3xr3=Kn5!A@_~FjlS$b z49_CR5ZOsDI&{>RK(S8-cMMu5ea&6$#`>Gc!)|==^^Ct_Q5ftEq^lMoKcS{(wX$9< zYc8S6%Hr3MqEgamNwL=yO<675`leDM8swUO0J$fq?YPBFvO{Y^)m{5!4%T9%Qw%j4 z@Eg>&0>P+OwrCLTuCtmrtT;i`AgpX^BoIT(MZ?tHV5B2c*D63c77kV$musY}pUfTRXw1-V49T!gk&Qb(TEM0jr~P`TEb9#~Xhw zG~{_-o^Q(YXY!p>`Oe?heR-)VFV*k8kj4%_{!?s7Grly_lxEJP%&C-l{)I1Pn^LyH zXD`7Jx?u6^Z}y4xBJB1x=n!BFd$2P+(YNsg@aAFQ!c$?m z(-}Pde{<5LYqUGc*)&%;TP*r&lyE^3V$W zxA8_#2^sNPl2vGs`N681sQn}=KuebHkP5L}s#vzA6QydoW?6Adq=Pl;Y7Xo@G~JSN z(=gZ8OhBZ&b5pUZl~v*}EY^r)({yO4m=Aj8fy(bUOW@CbN?(Rb3~PeyA)0zB0-MyO3%u5JE?R zumz}f1HrTmvK^u?7t=&!))s4b$=vBQj?e7}CTH7?h4d*7V%iH5lJnEbK{6O8BY zbY|c_zue@P8~k!Rk`N}(C#H^aXQ{c<)SREnG*cOW;&yZ5_P+R1nmCh`Q%O0x=1bE} zX}XQL#AIu7YCm_d{9zc%kD{#48Oy71G*DF8evVtbpJr1iLe&0;$K#5qgp0k wIa8p>dO}x$wK&8ZlUj timeout_seconds: + 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") - # Temporarily set nonce for hashing only - block.nonce = local_nonce - block_hash = calculate_hash(block.to_header_dict()) + 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: @@ -61,13 +71,5 @@ def mine_block( logger.info("Mining cancelled via progress_callback.") raise MiningExceededError("Mining cancelled") - # Check difficulty target - if block_hash.startswith(target): - block.nonce = local_nonce - block.hash = block_hash - if logger: - logger.info("Success! Hash: %s", block_hash) - return block - # Increment nonce after attempt local_nonce += 1 diff --git a/core/__pycache__/__init__.cpython-311.pyc b/core/__pycache__/__init__.cpython-311.pyc index 8b3aab2c7d7c97c4b1bf2edc047748f1e77a1cbe..ee49e5e41f67b36511bf58546c99a59376a749fa 100644 GIT binary patch delta 56 zcmaFN{F0e(IWI340}!m;JRy^paU)+GBV)0syAd B4MqR} delta 50 xcmaFK{Fs?m_!pO(L~x$MN^`ZDO1^0%-C>bLlv{njQ7YfkfoRMVv_bPD_Vla zmx-373Bdg{2{^@lO@+!S+bvwrg^J}+^JbVXGxH0}t$2FT@QPu|f9a?fCVZ=8dcILA zhe>KJE?R|(?JpG|tr1{KC3x3EKr5ss)QBD})k4h?0@7sHx{iUXB4Lm9Km(xA!2LfJFz#Vw|M zH{al)Gb)D{lczt(&wD2G@@LEWPb|keT{J8wUvQb3U%>ih%S&Ng*I~ui_392@(`;A9 zgS;}JItliSH00aKVC+ysZpBA}ljoWzFFmY1tTnIbs2P5Ce5QH){7>euGxtCK<=p*q zPo~zUj;@U#YmFakjvxDT_EPh*(WJ$-%f+=>t2JxEX*-^c?Bj<{z6m4|WZnZZ(12$h zzDWvENkG7q^7beK1MNYs0qp?r(M~EX<66`dWoVL_Ek%cQJc$GFQELF-Q`FTM7OOgO zp=dCZs?_3l&R|RGLdCMFPIh zxA$Hkj|klJKHt^xlNm7-v+stYf78R5RF&-p@*+8H!+Fyc=D^CSfC(Y4Z=l5{o?(EOnkJ=CU)qJF6xbso$wavwL!a5trO#;J6JDCf_>R_2_YEf7q=OPk?&zl6V^MjFoB{@0Yo1XTm2~?N{lNyd= zvHJFzifya>o~YFD4Rz6FD%>W^u&t_rGx#tcb7hzLS@s4PW<;g}>GGJMP)5_<^05(6 zSJRyYZ4`PGRNe%jI(as*>)Tu3+*%t@TLWrC4)*T5v(%6q7r#yg1G^gi+XKhW;DH|k z=Klr`0SeWL8H}ppL4#vxB^U$H*9Qx(>Nt)*XX0!SNEq{%%21w80x))lfrNuN%(Fdzx1HqhXg6ozMSQxG-4pB`u*7Qe l>E4zEK?q2Hoj(CN*nItO`)XU>FF*v_(Z8sB{)z=2+JCU-7GnSa literal 1871 zcmb_c!Ef7S6n}Q?G)bG%L7Ocl1$S_05XwM898jq?R zFi}fnprWN@Rt~K+D-5*mtKn3-2XLFBNO^?#^hgK5chWQCS<%X}Guq3ijtuae>ISC- z^{H5QedgSW%U$k#>jphxZdzh9o(Z|-3(F4Oz!$L=x*aDj5{l63bYtBQeA|gN@~!K! zCLHf3rwp75CdB#W3yoD|%JRe-DyhpEd}VAP4B=U=nf#vI_~FZ+TR*jaZ11(RhkOp5_VcIB37^I- zK11+2IVrr1i=h4|HSq;h`{jvZ$X#4Q7{VwImBk0GXleOi=|Uu{_2em9*J%KH!eMd> zIyn9QEn~&Tug>vBPt;%f0B{;0K2K0VI8HpT;)DK5*5pmcVh)#+avJe9im^x_iPis` z*f+_E+>bFD&*DrS%g~=cE$>-+X%*praXgyN6TJx>(OXwG>UsKu@vV+ynsLcAI|1u? zq|cbi)^cY^ZWg|2?k7V{9l7kyG%)ncpc}-?6 zqfF>I?q(>5eOoI^A7bRPDVho(ZIY--iyCxo+a|(Y0 zb44Zx`J1Aq%D|4{jv9l!Sde+Cn9r-G!5f%u-qgi{p_*#3U<{GNBwrW60OmRX@Ad-# zt7sOLkpP(6OgvL>tsKKDPhQxu-n7nJk9NsOAv4^Vn`b?I&$&TwMn<3wY629(5^;6x z8xBjPauR~1E7%aTN8FX_he9PWU?&Fff@OHNVKZtm2386&h6czg zGMfw~Q!+h^z6fl<{9|9;cPW`EQ-Z&)jOKl_>~EAZ)p{M^`mmtpVCgLJ4eV^=M6s^3 z)wS!o7k^7N=XjCNskarG$2Y)rD7;C>bRFtsCg8AYK~`=%u?1bZsTLOva#x%v@k3D} zj_Ca8O{cT`h7)#TAyh)G7a5P%&FmSeRD>K#~)*<1=NzY=-EB{#X`+M5;b zgw36>yr13yXSEm{`#P3l`#OMCxJmQyb=&t4h5No~Wh&`SA&HjB(;B}hxG(+a6f&4w z^zFbjx5OmUNyS{$3$BUtMZ@HEMN$f8-OOr%1RD1u_wlT2_d-3AMu900`BMQ`)V|{_on1RqEY9J)}HwGqf+8_qO&b5z0 zsD?qB@7g@xL)wP~(|>6C2?(KeKiGMz@K>hF)Vv?;0QXJ3|H&H={vN~YHVTEotoYgO z1ajaqYB{+nCN${DObHIEbNluIxMf?;Z07tG=l_p9`&u-yXVqJoT5r;&H@Z8$v_`eM zT;Dx_T)|y(`8>2n+W(?&H18+3byD0k_;j(>G1{QUKymqrC<61 z=O=z~gNqtbmJMF4M<}z{h)}$m<3&w#>zsOi;f+N}QWUw7pCH@g?NV_;h1^97Iv!3@ z3dW+Y@S<7FtCCkvL8;W(SUN=l8UHNJt;$<(nEI}E7nj^2&Y!(OQ=fH4F}wAvjv2ONdj z4C`iG_6DxCoQSF97X-IXqs`k+P*;pa%``9tC>82S*a?G4>&c>%h~_37e>4Nj4HKd> z)51qW!k8vCb5G7={A8j)Sg)RXifgKX!oA}Qmph2 zZ^nnM_^?m>!r$Nh$NSdwb$eR2a`Sd>(awEj{o>CNcrao0U$v%Y?5P`-PRZ_+tWK$Vl&_sWAQwY$5R?b0^zu6^ z7pl?rl?zXzJ)6;18nq{Hb04S-^xdQ73}`9SxBi z?pEIJ0J-MwU$d|T|1ohyHR{0x#~B=7a9(r5vx<3<1Ri}5`g$J*M6y#D!?iJy_8V^0 z)!bQq7{+iUHGTsCszQpYqSvj~-+m)$3$^^JsBMWnRn%^Izh~jpa+e)Wt$n`|PTS%1 W5_s`AMS&vrhg)QP=Owd*|Nj6@d%H*g literal 1936 zcmZt`O-~y~^v!zLEXIzL8dRf_boq$HN-2dyQ5B^{ZPYfcRI63`CD+n+@hq@lYjGy9Jiz+ZF-LGBUf?;~*l95^e%Cd)HMv{aHdWBC{Z2|fcJ+XXHkNdU+A42XT#fq>B zzLmM^mZus9a$LixXD$+$3ojKk1MdjDfWTH zN!L&ODG~?Z_9e<0msa5jxyK~o!xQF4G{83FvETmJ*eB99tcT5lMxS(O1NEdM^m82f z>XlB(2~zaf{2A>u4BTE>v5U2myQRy5Yzdk>p^{kKtX8cu*JEOYj;VnvdnuYD9+uuA zT}{(OlGKM0)!*)`ojRl06Xw9o!PG+PVGN!syBm}qCO#?Dn$SDKM`R} z>>?(NFY47rZ#PI9#0__d_5e7$)LH2v*@`?hsDYP&#|HJBL96m07w3hn!a7Z_4~=+8 z_3zu4qwyb|Tz%pbAu8go_Ide{S+cqIm9QS!m71eHFrBqeJ$WAC3ZY76MZ@)dMQnVd z4|%C-Pt90EHIYT%o=l{tRE1r3y@cbM!gU_o?pn~l42tMdFtExpJ#r3JrZ<@0u*rF^ zo1=Z*jBZKY5s0vvBiGv_w;D6QyxX2yJQ-PRjx4@N-e|rt-%2jDlMBt{LMM}b_Fgv* z*T?qjr|FrK^i1QsR{BmmeWwf3@c32IFZ-tWc1zXTs@7DsPCB#a`U8{GReFoc=B8yB zUcxXoE4)@BI%ya`*UVDD5h)DO|H?eIDk@b=xLba8gocdg;M)p5Me3U%L}TF;5bZ?2 zt|Tcl7;A`BS<$-?p(MnwbtR_U>H?xyF+A@65M0EQ%4ioZqrfq_%jt@jphtGj?}Gof zc&TEg>{w-|=FC+qkAtVi5AZf>Tm=1Ey%*&Dp_oMmeHzY>h`KSxSO+GWgR28G&B4`` S-(ncwW%P>f{<(tTJO2-)9j5aD diff --git a/core/__pycache__/contract.cpython-311.pyc b/core/__pycache__/contract.cpython-311.pyc index 731dfd015e72a02aa75be4616dcfe3be416c0e8e..9bf3358c120713c60940060cc8f09e4ae42fe855 100644 GIT binary patch literal 8515 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 N{^EOo{|6)X`44@21g8K1 diff --git a/core/__pycache__/state.cpython-311.pyc b/core/__pycache__/state.cpython-311.pyc index 4aa9288407531a1a5f6b4b96a8734cf7c573d50b..cb73b03f6e98c12ebe9e6e4ebe3b87661e102a1d 100644 GIT binary patch literal 7501 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 delta 1999 zcmZ`)S!f$a7@j>PYgcxx!oI$2^lSk+dyp@)aO?m!W z_k5utv=>IMAPXfp6D4?)n?VV|R?LL5`8x5yWKo4W=igU1Sa$_F@={LZ&=3W*2RpE6Rz5MD_5PJ4U0MlqK7=9dV zUk$b|ynQ2G2<|8bcg%X$l;B*tsOSs3ZtPiAqIo6yM6J*J1{Y5(4KL&S+%jG|4c*F9 zP>&SVBYE}6nm;txLsicfm6nCgH@a4pt$AfDQypGBx;$R&29@EwPaj^v4=0#7Tu>85 zHIY{n7416ztb;3+`H5EMfz*{l3ljO-A0S^Up%%ziwInwos5>mn5DB_0@DE;%bCE^0aY_V>@K)MoDOat@^pDmY%W3^@CZcP%`D{ z2TT*gq@F&bo7U)s?4^rV#)(y|Yp}rpu7~YeG1n=%;w6Or@Fo1F*pvpsNkhS{bO{Xu zM<~$Yk{eqoEB!9;qArCk@NFcuu_rTD62a{xBM_#?vgEQRtkjre88V{`ZmybZqf!T3 zvk{6dLr>THX#%iHXX=xqqZclXKuj4M!b)c|lZ`_xBmbG8&e|;y%QPyxiCRC|x_h;C z_hSFjYs>hni51_&#zO1yV(am|+O+0x%d2f<*x$3MEF(2AH&9fxGIG^uYvh4%XRnF^ z+HCy03QNFjs#+}QNbpiR{JI2i1?9M_a2&6q>pZ#dZ*LZzDpe55iOzGHcR9ibc2Du9 z@B#l1RW3`WXo@)@4F~(02(+ncg3fZ~Lt4iT2V$=jVI#T3$}LyjbZ*6 zg3})*_rhE2=y@$i=2;ahl_kiq(>Q|HeLakM&!LnZ!Oi2!_pqA;UV8d+NZoA zR!Gmz%r@HXq|i-){?Xh*Y+pueOfMG(luY*f0Mpf9nHQwqGOB{dEp?Pp6#{N4K)V|d z(5ZBSA%#PnD(PiZg(hCwUq)4M^HMwQz7Q%7Qs3f+i*z*Y>0*BNQD@O-dF$t diff --git a/core/__pycache__/transaction.cpython-311.pyc b/core/__pycache__/transaction.cpython-311.pyc index ad39fc3597b2eec75a3656e29f3c2b1be9de0189..e1f037c0a7d52727402ac574d2d5b5969c499074 100644 GIT binary patch delta 1604 zcmZuxU1%It6ux(MXJ%(6zq{Kcn`D#O65@`Mm`dVb(&{!O+KMSP8?i;wu9-=)X)?3i znH4vzZPg%!7D>1d3RZ|ktHC}M`lwJJ#0Q1lu#zyK2qO5nL51Lx=gwx=#N;yf%)RHH zdw#z2&3xQ)zrEp$`g$3`nEqxpJtwU-wBgs+9vN1bq>BAyBtDa!K{pn z^4itGPR?yUozf@Mb7soP5@U=Ii@3@t$z>dC?V-~WsG7su57P(p&rSLger_UJo$ztA=?2cD0mrG>J5B zBK-#>rAhQR92a*zB50&vUN`65Gzw4X)?O8~$6;_Tb6F z;F(X3eU$rhu-N^0F)~n!3=|>*KW2ZLx=@;$E>2x8OG#ByXQODyY&EF^Pj~6?dQ5$lNxp<+ak`TD@M*<=CjUh-_w8xob|i@1=})RUe7LN zY*#SM8A~@b&db#mGpXF7mC|j-36*6%$v-AUVPj9y6EJINrqC8%J3ze~sA+2!OsnG(Vd$>XYIxO@LF4PXLFaQu~+|LJ{JII!I zaUQP=^i#1Fi?mzlP-=}|mslKnL1?~K(|Kg@O>AIStW-&sV~o~mb4~?ZRNXRc)wCRS zDdo(}tF~e42GL|!e$yc7*&M@;bhDg*wa688gCRv2rCs^e)M9q;)9~Bilf;-I>-{y? zH*dV9`8+_$aqlX#Fm#($s4`TUtp;MbDM?bMotkmdmf6p&9tEv!g1C-;Ra#0)Y)gr6 zD)9&H1tnfoMoP-amNL4jj24x#k}~#v_=k>7C0S6CKL9j; z;+X)BJ)Q+HjfsU`hcR|Ch;1_I0aa3~BOw|IbiByQb@?}Pj$ymv zEU}iLX2RSP#!?bz$5JwvsbI86FVYL@8nu{RA2kE5(GyJl9*FB`yD@a@Y8hcEvK?w$ z9V+`}dM7YC{(DpG^Wg{b*Jlci<7LE4k$pl&;mGO`*eamk-QBfWQ8yEzgo+-j(bL%X zEy5r*-vsTJv{S4!(hkB6WQdh9h?A={j{)O#h4jH{0}%#arC03tK$r;7_ob0(nIFYv z#N>_-2@NZ+l{rt<#`6Q~@L|B~|DvIRhnx?KN)YB+QwaOcNHbOC@jeelFYFLg)>8Q= tFtTLn*+t_Nc?J&HPsF}SKaj^?ezt&)c*??47y=&_J52Z25eHtZe*woQU-tk2 delta 1125 zcmZ`%T}u>E7(Qpdc4k)Bk6kx6R~Tur9n?j`x==G#LBR`&6w-)fbH;Sj-Q~>sv9X4P zk&wl12l^l(>B0){x~OaafpjGUcF`|bu|R^L_l(SjS%;Zt-jC;;_nqgwAJ4uH`rcJm zstDHnP+{hmRP@#3vEupeFh(32M25JE3~7r)XdB++bwepW=&ZtaO<%$onYoinWao)l z{E`iNTn2m@n}_%CDZPoWjAyWc^JqeF8CkDuA(Rwddkc(h*FnISTqiGqS3ciBBS`0l zZKTVzn~TyU7sej?h|`o>*kCap{ls09Wah=GkB`(8hFEOPd^L3z!r49UG99D z%X3MNewAV!6XpAK)WpNOU<(#pC(+;#M--c%HkWkXX^J$_b~(@-V7YSu_o_!pB4s5e zvoq;bjO~j;)Vct$g6Njq5&pks-w-{NccT$R1dPU!fk)6HTH?piBG!fB0Ug_(V?oHM zu8>;lRU5iAIM^0%=dz|{d!|Tw4q7AZ7F^NP1(VE7#WdUSMTu{n7h_s2D~qp9&2zte^R zb&j@p_1-#2k`RC`XGl6@l5Fl+NH!&oVuT6w0THGPadc$kNf>zOGjGo=MeN5V#PEPe zl5cga#5tiLC$?|Gb;IXxkYzDcLZ{)l2s>I=2^$R diff --git a/core/block.py b/core/block.py index 495119d..c650330 100644 --- a/core/block.py +++ b/core/block.py @@ -1,34 +1,36 @@ import time +from typing import List, Optional, Union +from core.transaction import Transaction # Assuming Transaction is defined in core.transaction class Block: - def __init__(self, index, previous_hash, transactions, timestamp=None, difficulty=None): + def __init__(self, index: int, previous_hash: str, transactions: List[Transaction], timestamp: Optional[float] = None, difficulty: Optional[int] = None): self.index = index self.previous_hash = previous_hash - self.transactions = transactions - self.timestamp = time.time() if timestamp is None else timestamp - self.nonce = 0 - self.hash = None - self.difficulty = difficulty + self.transactions: List[Transaction] = transactions if transactions is not None else [] # Ensure transactions is a list + # Use integer milliseconds for timestamp for determinism + self.timestamp: int = round(time.time() * 1000) if timestamp is None else round(timestamp * 1000) + # Ensure difficulty is an integer + self.nonce: int = 0 + self.hash: Optional[str] = None + self.difficulty: Optional[int] = difficulty - def to_dict(self): - """Full block data for serialization/transport.""" + def _base_dict(self): + """Shared dictionary building logic.""" return { "index": self.index, "previous_hash": self.previous_hash, - "transactions": [tx.to_dict() for tx in self.transactions], + "transactions": [tx.to_dict() for tx in (self.transactions or [])], "timestamp": self.timestamp, "difficulty": self.difficulty, - "nonce": self.nonce, - "hash": self.hash + "nonce": self.nonce } + def to_dict(self): + """Full block data for serialization/transport.""" + data = self._base_dict() + data["hash"] = self.hash + return data + def to_header_dict(self): """Data used for mining (consensus).""" - return { - "index": self.index, - "previous_hash": self.previous_hash, - "transactions": [tx.to_dict() for tx in self.transactions], - "timestamp": self.timestamp, - "difficulty": self.difficulty, - "nonce": self.nonce - } + return self._base_dict() diff --git a/core/chain.py b/core/chain.py index b03791f..1ad401f 100644 --- a/core/chain.py +++ b/core/chain.py @@ -2,6 +2,7 @@ from core.state import State from consensus import calculate_hash import logging +import threading logger = logging.getLogger(__name__) @@ -15,6 +16,7 @@ def __init__(self): self.chain = [] self.state = State() self._create_genesis_block() + self._lock = threading.RLock() def _create_genesis_block(self): """ @@ -33,7 +35,8 @@ def last_block(self): """ Returns the most recent block in the chain. """ - return self.chain[-1] + with self._lock: # Acquire lock for thread-safe access + return self.chain[-1] def add_block(self, block): """ @@ -41,33 +44,34 @@ def add_block(self, block): Uses a copied State to ensure atomic validation. """ - # 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 + 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 + # 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 - # Verify block hash - if block.hash != calculate_hash(block.to_dict()): - logger.warning("Block %s rejected: Invalid hash %s", block.index, block.hash) - return False + # Verify block hash + if block.hash != calculate_hash(block.to_dict()): + logger.warning("Block %s rejected: Invalid hash %s", block.index, block.hash) + return False - # Validate transactions on a temporary state copy - temp_state = self.state.copy() + # Validate transactions on a temporary state copy + temp_state = self.state.copy() - for tx in block.transactions: - result = temp_state.validate_and_apply(tx) + for tx in block.transactions: + result = temp_state.validate_and_apply(tx) - # Reject block if any transaction fails - if result is False or result is None: - logger.warning("Block %s rejected: Transaction failed validation", block.index) - return False + # 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 + # All transactions valid → commit state and append block + self.state = temp_state + self.chain.append(block) + return True diff --git a/core/contract.py b/core/contract.py index de12131..18ceba1 100644 --- a/core/contract.py +++ b/core/contract.py @@ -1,8 +1,8 @@ import logging import multiprocessing import ast -import sys +import json # Moved to module-level import logger = logging.getLogger(__name__) def _safe_exec_worker(code, globals_dict, context_dict, result_queue): @@ -14,11 +14,13 @@ def _safe_exec_worker(code, globals_dict, context_dict, result_queue): try: import resource # Limit CPU time (seconds) and memory (bytes) - example values - resource.setrlimit(resource.RLIMIT_CPU, (1, 1)) - # resource.setrlimit(resource.RLIMIT_AS, (100 * 1024 * 1024, 100 * 1024 * 1024)) - except ImportError as e: - logger.error(f"Resource limits not enforced: {e}") - raise RuntimeError(f"Resource limits not enforced: {e}") + resource.setrlimit(resource.RLIMIT_CPU, (2, 2)) # Align with p.join timeout (2 seconds) + resource.setrlimit(resource.RLIMIT_AS, (100 * 1024 * 1024, 100 * 1024 * 1024)) + except ImportError: + logger.warning("Resource module not available. Contract will run without OS-level resource limits.") + except (OSError, ValueError) as e: + logger.error(f"Failed to set resource limits: {e}") + raise RuntimeError(f"Failed to set resource limits: {e}") exec(code, globals_dict, context_dict) # Return the updated storage @@ -66,6 +68,14 @@ def execute(self, contract_address, sender_address, payload, amount): "min": min, "max": max, "abs": abs, + "str": str, # Keeping str for basic functionality, relying on AST checks for safety + "bool": bool, + "float": float, + "list": list, + "dict": dict, + "tuple": tuple, + "sum": sum, + "Exception": Exception, # Added to allow contracts to raise exceptions } globals_for_exec = { @@ -80,7 +90,7 @@ def execute(self, contract_address, sender_address, payload, amount): "value": amount, "data": payload, }, - "print": print, # Explicitly allowed for debugging + # "print": print, # Removed for security } try: @@ -95,6 +105,7 @@ def execute(self, contract_address, sender_address, payload, amount): if p.is_alive(): p.kill() + p.join() logger.error("Contract execution timed out") return False @@ -107,6 +118,13 @@ def execute(self, contract_address, sender_address, payload, amount): logger.error(f"Contract Execution Failed: {result.get('error')}") return False + # Validate storage is JSON serializable + try: + json.dumps(result["storage"]) + except (TypeError, ValueError): + logger.error("Contract storage not JSON serializable") + return False + # Commit updated storage only after successful execution self.state.update_contract_storage( contract_address, @@ -130,6 +148,23 @@ def _validate_code_ast(self, code): if isinstance(node, ast.Name) and node.id.startswith("__"): logger.warning("Rejected contract code with double-underscore name.") return False + if isinstance(node, (ast.Import, ast.ImportFrom)): + logger.warning("Rejected contract code with import statement.") + return False + if isinstance(node, ast.Call): + if isinstance(node.func, ast.Name) and node.func.id == 'type': + logger.warning("Rejected type() call.") + return False + if isinstance(node, ast.Call) and isinstance(node.func, ast.Name) and node.func.id in {"getattr", "setattr", "delattr"}: + logger.warning(f"Rejected direct call to {node.func.id}.") + return False + if isinstance(node, ast.Constant) and isinstance(node.value, str): + if "__" in node.value: + logger.warning("Rejected string literal with double-underscore.") + return False + if isinstance(node, ast.JoinedStr): # f-strings + logger.warning("Rejected f-string usage.") + return False return True except SyntaxError: return False diff --git a/core/state.py b/core/state.py index 17df4c3..17bc68c 100644 --- a/core/state.py +++ b/core/state.py @@ -13,6 +13,8 @@ def __init__(self): self.accounts = {} self.contract_machine = ContractMachine(self) + DEFAULT_MINING_REWARD = 50 + def get_account(self, address): if address not in self.accounts: self.accounts[address] = { @@ -44,7 +46,9 @@ def copy(self): """ Return an independent copy of state for transactional validation. """ - return copy.deepcopy(self) + new_state = copy.deepcopy(self) + new_state.contract_machine = ContractMachine(new_state) # Reinitialize contract_machine + return new_state def validate_and_apply(self, tx): """ @@ -52,8 +56,11 @@ def validate_and_apply(self, tx): Returns the same success/failure shape as apply_transaction(). NOTE: Delegates to apply_transaction. Callers should use this for semantic validation entry points. - TODO: Implement specific semantic validation logic here. """ + # Semantic validation: amount must be an integer and non-negative + if not isinstance(tx.amount, int) or tx.amount < 0: + return False + # Further checks can be added here return self.apply_transaction(tx) def apply_transaction(self, tx): @@ -94,25 +101,26 @@ def apply_transaction(self, tx): # Fail if contract does not exist or has no code if not receiver or not receiver.get("code"): - # Rollback sender balance, but nonce remains consumed - sender['balance'] += tx.amount - # Do NOT rollback nonce + # Rollback sender balance and nonce on failure + sender['balance'] += tx.amount # Refund amount + sender['nonce'] -= 1 return False # Credit contract balance receiver['balance'] += tx.amount success = self.contract_machine.execute( - contract_address=tx.receiver, + contract_address=tx.receiver, # Pass receiver as contract_address sender_address=tx.sender, payload=tx.data, amount=tx.amount ) if not success: - # Rollback transfer if execution fails, nonce remains consumed + # Rollback transfer and nonce if execution fails receiver['balance'] -= tx.amount - sender['balance'] += tx.amount + sender['balance'] += tx.amount # Refund amount + sender['nonce'] -= 1 return False return True @@ -141,6 +149,15 @@ def update_contract_storage(self, address, new_storage): else: raise KeyError(f"Contract address not found: {address}") - def credit_mining_reward(self, miner_address, reward=50): + def update_contract_storage_partial(self, address, updates): + if address not in self.accounts: + raise KeyError(f"Contract address not found: {address}") + if isinstance(updates, dict): + self.accounts[address]['storage'].update(updates) + else: + raise ValueError("Updates must be a dictionary") + + def credit_mining_reward(self, miner_address, reward=None): + reward = reward if reward is not None else self.DEFAULT_MINING_REWARD account = self.get_account(miner_address) account['balance'] += reward diff --git a/core/transaction.py b/core/transaction.py index efd7483..cdf4d99 100644 --- a/core/transaction.py +++ b/core/transaction.py @@ -6,13 +6,13 @@ class Transaction: - def __init__(self, sender, receiver, amount, nonce, data=None, signature=None): + def __init__(self, sender, receiver, amount, nonce, data=None, signature=None, timestamp=None): self.sender = sender # Public key (Hex str) self.receiver = receiver # Public key (Hex str) or None for Deploy self.amount = amount self.nonce = nonce self.data = data # Preserve None (do NOT normalize to "") - self.timestamp = time.time() + self.timestamp = round(timestamp * 1000) if timestamp is not None else round(time.time() * 1000) # Integer milliseconds for determinism self.signature = signature # Hex str def to_dict(self): @@ -35,10 +35,14 @@ def hash_payload(self): "amount": self.amount, "nonce": self.nonce, "data": self.data, + "timestamp": self.timestamp, # Already integer milliseconds } return json.dumps(payload, sort_keys=True).encode("utf-8") def sign(self, signing_key: SigningKey): + # Validate that the signing key matches the sender + if signing_key.verify_key.encode(encoder=HexEncoder).decode() != self.sender: + raise ValueError("Signing key does not match sender") signed = signing_key.sign(self.hash_payload) self.signature = signed.signature.hex() @@ -51,7 +55,7 @@ def verify(self): verify_key.verify(self.hash_payload, bytes.fromhex(self.signature)) return True - except (BadSignatureError, CryptoError, ValueError): + except (BadSignatureError, CryptoError, ValueError, TypeError): # Covers: # - Invalid signature # - Malformed public key hex diff --git a/main.py b/main.py index a2f0b13..d555e53 100644 --- a/main.py +++ b/main.py @@ -31,7 +31,7 @@ def mine_and_process_block(chain, mempool, state, pending_nonce_map): ) mined_block = mine_block(block) - contract_address = None + deployed_contracts: list[str] = [] # Type hint for clarity if chain.add_block(mined_block): logger.info("Block #%s added", mined_block.index) @@ -51,19 +51,19 @@ def mine_and_process_block(chain, mempool, state, pending_nonce_map): # Check for valid contract address (40 hex chars) if isinstance(result, str) and re.match(r'^[0-9a-fA-F]{40}$', result): - contract_address = result - logger.info("New Contract Deployed at: %s", contract_address) + deployed_contracts.append(result) + logger.info("New Contract Deployed at: %s", result) # Added logging for deployed contract sync_nonce(state, pending_nonce_map, tx.sender) elif result is True: sync_nonce(state, pending_nonce_map, tx.sender) elif result is False or result is None: logger.error("Transaction failed in block %s", mined_block.index) - sync_nonce(state, pending_nonce_map, tx.sender) + # Nonce is not synced for failed transactions - return mined_block, contract_address + return mined_block, deployed_contracts else: logger.error("Block rejected by chain") - return None, None + return None, [] def sync_nonce(state, pending_nonce_map, address): account = state.get_account(address) @@ -81,10 +81,12 @@ async def node_loop(): pending_nonce_map = {} - def get_next_nonce(address): - account_nonce = state.get_account(address)["nonce"] + def claim_nonce(address): + account = state.get_account(address) + account_nonce = account.get("nonce", 0) if account else 0 local_nonce = pending_nonce_map.get(address, account_nonce) next_nonce = max(account_nonce, local_nonce) + pending_nonce_map[address] = next_nonce + 1 # Atomically increment for next call return next_nonce async def handle_network_data(data): @@ -92,8 +94,43 @@ async def handle_network_data(data): network = P2PNetwork(handle_network_data) + async def _handle_network_data(data): + logger.info("Received network data: %s", data) + try: + if data["type"] == "tx": + # Transaction constructor accepts all fields from to_dict + tx = Transaction(**data["data"]) + if network_mempool.add_transaction(tx): + logger.info("Received transaction added to mempool: %s", tx.sender[:5]) + await network.broadcast_transaction(tx) # Re-broadcast + elif data["type"] == "block": + block_data = data["data"] + # Deserialize transactions within the block + transactions_raw = block_data.get("transactions", []) + transactions = [Transaction(**tx_data) for tx_data in transactions_raw] + + block = Block( + index=block_data.get("index"), + previous_hash=block_data.get("previous_hash"), + transactions=transactions, + timestamp=block_data.get("timestamp"), + difficulty=block_data.get("difficulty") + ) + block.nonce = block_data.get("nonce", 0) + block.hash = block_data.get("hash") + if network_chain.add_block(block): + logger.info("Received block added to chain: #%s", block.index) + # TODO: Reorg mempool, notify peers + except Exception: + logger.exception("Error processing network data: %s", data) + try: - await _run_node(network, state, chain, mempool, pending_nonce_map, get_next_nonce) + network.handler_callback = _handle_network_data # Update handler + # Mempool, chain, state for network handler + network_mempool = mempool + network_chain = chain + network_state = state + await _run_node(network, state, chain, network_mempool, pending_nonce_map, claim_nonce) finally: await network.stop() @@ -116,7 +153,7 @@ async def _run_node(network, state, chain, mempool, pending_nonce_map, get_next_ logger.info("[2] Transaction: Alice sends 10 coins to Bob") - nonce = get_next_nonce(alice_pk) + nonce = claim_nonce(alice_pk) # Use claim_nonce tx_payment = Transaction( sender=alice_pk, @@ -127,7 +164,6 @@ async def _run_node(network, state, chain, mempool, pending_nonce_map, get_next_ tx_payment.sign(alice_sk) if mempool.add_transaction(tx_payment): - pending_nonce_map[alice_pk] = nonce + 1 await network.broadcast_transaction(tx_payment) else: logger.warning("Transaction rejected by mempool") @@ -138,9 +174,6 @@ async def _run_node(network, state, chain, mempool, pending_nonce_map, get_next_ logger.info("[3] Smart Contract: Alice deploys a 'Storage' contract") - # WARNING: - # This contract uses raw Python executed via exec inside ContractMachine. - # This is UNSAFE and should NEVER be used in production. # TODO: Replace ContractMachine exec-based runtime with: # - RestrictedPython # - WASM-based VM @@ -151,7 +184,7 @@ async def _run_node(network, state, chain, mempool, pending_nonce_map, get_next_ storage['value'] = msg['data'] """ - nonce = get_next_nonce(alice_pk) + nonce = claim_nonce(alice_pk) # Use claim_nonce tx_deploy = Transaction( sender=alice_pk, @@ -163,7 +196,6 @@ async def _run_node(network, state, chain, mempool, pending_nonce_map, get_next_ tx_deploy.sign(alice_sk) if mempool.add_transaction(tx_deploy): - pending_nonce_map[alice_pk] = nonce + 1 await network.broadcast_transaction(tx_deploy) else: logger.warning("Contract deploy rejected") @@ -174,7 +206,8 @@ async def _run_node(network, state, chain, mempool, pending_nonce_map, get_next_ logger.info("[4] Consensus: Mining Block 1") - _, contract_address = mine_and_process_block(chain, mempool, state, pending_nonce_map) + _, deployed_contracts = mine_and_process_block(chain, mempool, state, pending_nonce_map) # deployed_contracts is a list + contract_address = deployed_contracts[0] if deployed_contracts else None # ------------------------------- # Bob Interaction @@ -186,7 +219,7 @@ async def _run_node(network, state, chain, mempool, pending_nonce_map, get_next_ logger.error("Contract not deployed. Skipping interaction.") return - nonce = get_next_nonce(bob_pk) + nonce = claim_nonce(bob_pk) # Use claim_nonce tx_call = Transaction( sender=bob_pk, @@ -198,7 +231,6 @@ async def _run_node(network, state, chain, mempool, pending_nonce_map, get_next_ tx_call.sign(bob_sk) if mempool.add_transaction(tx_call): - pending_nonce_map[bob_pk] = nonce + 1 await network.broadcast_transaction(tx_call) else: logger.warning("Contract call rejected") @@ -216,16 +248,25 @@ async def _run_node(network, state, chain, mempool, pending_nonce_map, get_next_ # ------------------------------- logger.info("[7] Final State Check") - logger.info("Alice Balance: %s", state.get_account(alice_pk)["balance"]) - logger.info("Bob Balance: %s", state.get_account(bob_pk)["balance"]) + alice_acc = state.get_account(alice_pk) + logger.info("Alice Balance: %s", alice_acc.get("balance", 0) if alice_acc else 0) + + bob_acc = state.get_account(bob_pk) + logger.info("Bob Balance: %s", bob_acc.get("balance", 0) if bob_acc else 0) if contract_address: contract_acc = state.get_account(contract_address) - logger.info("Contract Storage: %s", contract_acc["storage"]) + if contract_acc and "storage" in contract_acc: + logger.info("Contract Storage: %s", contract_acc["storage"]) + else: + logger.info("Contract storage not found for %s", contract_address) else: logger.info("No contract deployed.") -if __name__ == "__main__": +def main(): logging.basicConfig(level=logging.INFO) asyncio.run(node_loop()) + +if __name__ == "__main__": + main() diff --git a/network/p2p.py b/network/p2p.py index 7e18412..ef0a9dd 100644 --- a/network/p2p.py +++ b/network/p2p.py @@ -20,7 +20,8 @@ class P2PNetwork: """ def __init__(self, handler_callback): - self.peers = [] + if not callable(handler_callback): + raise ValueError("handler_callback must be callable") self.handler_callback = handler_callback self.pubsub = None # Will be set in real implementation @@ -28,23 +29,29 @@ async def start(self): logger.info("Network: Listening on /ip4/0.0.0.0/tcp/0") # In real libp2p, we would await host.start() here - async def broadcast_transaction(self, tx): - msg = json.dumps({"type": "tx", "data": tx.to_dict()}) - logger.info("Network: Broadcasting Tx from %s...", tx.sender[:5]) - + async def _broadcast_message(self, topic, msg_type, payload): + msg = json.dumps({"type": msg_type, "data": payload}) if self.pubsub: - await self.pubsub.publish("minichain-global", msg.encode()) + try: + await self.pubsub.publish(topic, msg.encode()) + except Exception as e: + logger.error("Network: Publish failed: %s", e) else: logger.debug("Network: pubsub not initialized (mock mode)") + async def broadcast_transaction(self, tx): + sender = getattr(tx, "sender", "") + logger.info("Network: Broadcasting Tx from %s...", sender[:5]) + try: + payload = tx.to_dict() + except (TypeError, ValueError) as e: + logger.error("Network: Failed to serialize tx: %s", e) + return + await self._broadcast_message("minichain-global", "tx", payload) + async def broadcast_block(self, block): - msg = json.dumps({"type": "block", "data": block.to_dict()}) logger.info("Network: Broadcasting Block #%d", block.index) - - if self.pubsub: - await self.pubsub.publish("minichain-global", msg.encode()) - else: - logger.debug("Network: pubsub not initialized (mock mode)") + await self._broadcast_message("minichain-global", "block", block.to_dict()) async def handle_message(self, msg): """ @@ -58,14 +65,25 @@ async def handle_message(self, msg): if not isinstance(msg.data, (bytes, bytearray)): raise TypeError("msg.data must be bytes") - decoded = msg.data.decode() + if len(msg.data) > 1024 * 1024: # 1MB limit + logger.warning("Network: Message too large") + return + + try: + decoded = msg.data.decode('utf-8') + except UnicodeDecodeError as e: + logger.warning("Network Error: UnicodeDecodeError during message decode: %s", e) + return data = json.loads(decoded) if not isinstance(data, dict) or "type" not in data or "data" not in data: raise ValueError("Invalid message format") except (TypeError, ValueError, json.JSONDecodeError) as e: - logger.warning("Network Error: %s", e) + logger.warning("Network Error parsing message: %s", e) return - await self.handler_callback(data) + try: + await self.handler_callback(data) + except Exception: + logger.exception("Error in network handler callback for data: %s", data) diff --git a/node/mempool.py b/node/mempool.py index 5e1c818..8bb941a 100644 --- a/node/mempool.py +++ b/node/mempool.py @@ -6,8 +6,8 @@ class Mempool: def __init__(self, max_size=1000): - self.pending_txs = [] - self.seen_tx_ids = set() # Dedup tracking + self._pending_txs = [] + self._seen_tx_ids = set() # Dedup tracking self._lock = threading.Lock() self.max_size = max_size @@ -25,24 +25,24 @@ def add_transaction(self, tx): - Transaction is not a duplicate """ + tx_id = self._get_tx_id(tx) + if not tx.verify(): logger.warning("Mempool: Invalid signature rejected") return False with self._lock: - tx_id = self._get_tx_id(tx) - - if tx_id in self.seen_tx_ids: + if tx_id in self._seen_tx_ids: logger.warning(f"Mempool: Duplicate transaction rejected {tx_id}") return False - if len(self.pending_txs) >= self.max_size: + if len(self._pending_txs) >= self.max_size: # Simple eviction: drop oldest or reject. Here we reject. logger.warning("Mempool: Full, rejecting transaction") return False - self.pending_txs.append(tx) - self.seen_tx_ids.add(tx_id) + self._pending_txs.append(tx) + self._seen_tx_ids.add(tx_id) return True @@ -52,10 +52,11 @@ def get_transactions_for_block(self): """ with self._lock: - txs = self.pending_txs[:] + txs = self._pending_txs[:] # Clear both list and dedup set to stay in sync - self.pending_txs = [] - self.seen_tx_ids.clear() + self._pending_txs = [] + confirmed_ids = {self._get_tx_id(tx) for tx in txs} + self._seen_tx_ids.difference_update(confirmed_ids) return txs diff --git a/setup.py b/setup.py index 142768d..1edff7b 100644 --- a/setup.py +++ b/setup.py @@ -6,12 +6,12 @@ packages=find_packages(), py_modules=["main"], install_requires=[ - "pynacl==1.6.2", - "libp2p==0.5.0", # Fixed: was "py-libp2p" + "PyNaCl>=1.5.0", + "libp2p>=0.5.0", # Correct PyPI package name ], entry_points={ "console_scripts": [ - "minichain=main:main", # Requires main() function in main.py + "minichain=main:main", ], }, python_requires=">=3.9", diff --git a/tests/__pycache__/test_contract.cpython-311.pyc b/tests/__pycache__/test_contract.cpython-311.pyc index 362872cf612fde9956aff72e4cd19317f83a299b..b8ef83c490f6f8c07f6df4809e16ab9ac098710a 100644 GIT binary patch literal 8025 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 delta 1089 zcmZuvUx*t;7@wJ)-JRSe*(BYhNzA3Iws7$ucM4UZUJEClZLe33>dOkoZDzghnQYSC zEz&cgX9;?JyLN=T2Okb8*J^z@RIo3F_DK-Lq~xJp5JcZ4qA2*HGuyjUu-`C0zWILN z_xrw?-TPA)^W)bNi4nkU?8K@=6aan}haHP;JHNdNFOQevHe8f;DAp?Eu#FZa*2|W6 zolz0lPGLQ5FR|542*3?^rI2H!kQDng1QbwICykgHYmAs9(OIgGHsWSH@^L-UNSa9q z5SRf}?gFaVU{Tv?y~#V=*y#eyl%1yPJ4moz8Udb}VSmG%kb;$u3y)w?OO^lC%56ES{WGSaj>5jz z^6ZyvihY~a*abPqzKf+;typ4rg5t z;kbb<4uX2u3C12gdg77hD(pRFGM(XWh<#_l^^)B6;_Q|(mm!`W#cNG!hqBvr zYb>W8N)jG4w$013syZ{7<{_VdBCrgGL`vZmJR~5US&@Ix_if;->UI74*{uUNX-}W- z>(c{$ntf23HDbxt^raD=lo?y6A1`clwyc}k+i&zT^Zm^HAT!S<$xHH30r?U;OI}p> z@+NZ?nH?NEa3RNi?%+o>=3qfy!yZ_Gq7dg~u(sOlt$~(*n{*=J~gMC(h{O~;>b;*a?MQs&* zq^@aKv<-Asy{3JlZK1E!ueC3<+oV5nbTDzWZyXyK$NH(`gVgapm>q!G9+>@+^~*Z@ zsXYD`eiVueKOEP|AYb`4B7gsH$}14y_{!2 z$VDZq1hi5J6~ozZQO&AFEvsQ9p?>6qt{^8&&_#XS@Pdrc4e_Df0Vf+FQAZtEjBqjN$IEppCfZMRlf53Y}L%!`3{tzc|7f(tDBUeyXbJQV}))xDvI~c3{Y2_QN zeRfZgu*qxTP9rHyGAzKNySSkq!Hv969mg&FTx9p+k1E6Qo#3=dKuuxph(}}mb^TDh z85oj3#ULmP4$tYQ#IdUW9&TAY7wMMDokFs*PXGm-nmTM3Y%fn%PKaG59i;|v8}pPn zuAeKqp6iX|D4DdWLj#{s-M}$@7N{7g$L;a)!sVP#ZI9V`-z|AG@Q?ywbcFahJD)F2 zc>Z;{l|qd407$}=u!(}tprlq1ZOVKRxAQm6UxR38F~*Dkou^Au_ulr=NG zcSxKNvk>SwCBiJPW>uRfP4uUk=|jcFgt9_Fp~q@xYQ{RSm0R zSgW!S1@ZvygXRN(4c=DN+S=My>}AcEGka#uO0ws>?pyTRnWf`{)#HOp+S#ghwzB>* zXz|y3`cm@kl+yQ%dbuHV7NBpk6>Y+na3~`YJ7~e&)4}yn}CEDLv lMu#ffzpKhC7(*u8|2~|m#;p5wg$MuFJ0tP_ogE5<^&gzBh{yl{ delta 1228 zcmah|-)kI29G}_WmrHK$%>8ICK{vK(d)nGq5~bLPKR{^{3)(#G%d)*4(@pnBnAy`- z9}=XXzDUmz>SIx{6kntfL<_|~;Q|Mq3syl8eDZ>Q@5H{?SJV?5El^o35*g zk?!dE9EXNur?Tj0SX4+| zOpy{i3wdrWoQO(JDIyMu%1$||IF;P5hYv)P&Ll<(dJ*}?W#pR_tyjBoW3oo;zI8!? z60D*i>27pCoW+Ii*ZZ;7y))f>E=<}NGW~4orL={!u_%bCdk&o4KkE31sNtj9S=2JO zSMawOTjGHF6(%3wQ58(Y5BkCJeKIqbdXdeVqGBxIY4L)wfoH^Sd1||7a9pWD?S_O* z*@x*tZXAU3mHH}ZnmbzA!~s}l!l zN_m=TX%JB{Upu_1@=H8Z5}s)rUV1(=~|k%?XnLh8Di)=n5@ z`E?nf$pnBuAuF_#nA4bdID@}1vsPuX=RHnG;+MdufgJXXoPx?llyB~L>tvMposd4w zUX~1ele{5usA7xu160_``v9qzt+&Zrd&9ru}}By(>vwmetCHZt@P1K53T%&p6LDG7$Ccs ij}xPp#G$r` Date: Sun, 15 Feb 2026 00:20:59 +0530 Subject: [PATCH 20/29] Update core/chain.py Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- core/chain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/chain.py b/core/chain.py index 1ad401f..57e07ba 100644 --- a/core/chain.py +++ b/core/chain.py @@ -56,7 +56,7 @@ def add_block(self, block): return False # Verify block hash - if block.hash != calculate_hash(block.to_dict()): + if block.hash != calculate_hash(block.to_header_dict()): logger.warning("Block %s rejected: Invalid hash %s", block.index, block.hash) return False From d5cf36bbafc99093c85423c62d9909ef0548175d Mon Sep 17 00:00:00 2001 From: Aniket Date: Sun, 15 Feb 2026 00:22:08 +0530 Subject: [PATCH 21/29] Update core/chain.py Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- core/chain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/chain.py b/core/chain.py index 57e07ba..9545864 100644 --- a/core/chain.py +++ b/core/chain.py @@ -15,8 +15,8 @@ class Blockchain: def __init__(self): self.chain = [] self.state = State() - self._create_genesis_block() self._lock = threading.RLock() + self._create_genesis_block() def _create_genesis_block(self): """ From 60cd231ed52a546102a3a298b35215d2c3aaa924 Mon Sep 17 00:00:00 2001 From: Aniket Date: Sun, 15 Feb 2026 00:22:41 +0530 Subject: [PATCH 22/29] Update core/contract.py Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- core/contract.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/contract.py b/core/contract.py index 18ceba1..c12fddd 100644 --- a/core/contract.py +++ b/core/contract.py @@ -109,11 +109,11 @@ def execute(self, contract_address, sender_address, payload, amount): logger.error("Contract execution timed out") return False - if queue.empty(): + try: + result = queue.get(timeout=1) + except Exception: logger.error("Contract execution crashed without result") return False - - result = queue.get() if result["status"] != "success": logger.error(f"Contract Execution Failed: {result.get('error')}") return False From c7cf0b1ab0b8c62af486f2a0ad77cc5618d9f664 Mon Sep 17 00:00:00 2001 From: Aniket Date: Sun, 15 Feb 2026 00:28:13 +0530 Subject: [PATCH 23/29] Code rabbit follow-up --- main.py | 72 ++++++++++++++++++++++++++------------------------------- 1 file changed, 33 insertions(+), 39 deletions(-) diff --git a/main.py b/main.py index d555e53..39f76ea 100644 --- a/main.py +++ b/main.py @@ -14,6 +14,7 @@ BURN_ADDRESS = "0" * 40 + def create_wallet(): sk = SigningKey.generate() pk = sk.verify_key.encode(encoder=HexEncoder).decode() @@ -31,12 +32,16 @@ def mine_and_process_block(chain, mempool, state, pending_nonce_map): ) mined_block = mine_block(block) - deployed_contracts: list[str] = [] # Type hint for clarity + + # Ensure miner field exists (minimal fix without touching Block class) + if not hasattr(mined_block, "miner"): + mined_block.miner = BURN_ADDRESS + + deployed_contracts: list[str] = [] if chain.add_block(mined_block): logger.info("Block #%s added", mined_block.index) - # Credit miner reward miner_attr = getattr(mined_block, "miner", None) if isinstance(miner_attr, str) and re.match(r'^[0-9a-fA-F]{40}$', miner_attr): miner_address = miner_attr @@ -49,22 +54,21 @@ def mine_and_process_block(chain, mempool, state, pending_nonce_map): for tx in mined_block.transactions: result = state.apply_transaction(tx) - # Check for valid contract address (40 hex chars) if isinstance(result, str) and re.match(r'^[0-9a-fA-F]{40}$', result): deployed_contracts.append(result) - logger.info("New Contract Deployed at: %s", result) # Added logging for deployed contract + logger.info("New Contract Deployed at: %s", result) sync_nonce(state, pending_nonce_map, tx.sender) elif result is True: sync_nonce(state, pending_nonce_map, tx.sender) elif result is False or result is None: logger.error("Transaction failed in block %s", mined_block.index) - # Nonce is not synced for failed transactions return mined_block, deployed_contracts else: logger.error("Block rejected by chain") return None, [] + def sync_nonce(state, pending_nonce_map, address): account = state.get_account(address) if account and "nonce" in account: @@ -72,6 +76,7 @@ def sync_nonce(state, pending_nonce_map, address): else: pending_nonce_map[address] = 0 + async def node_loop(): logger.info("Starting MiniChain Node with Smart Contracts") @@ -86,26 +91,25 @@ def claim_nonce(address): account_nonce = account.get("nonce", 0) if account else 0 local_nonce = pending_nonce_map.get(address, account_nonce) next_nonce = max(account_nonce, local_nonce) - pending_nonce_map[address] = next_nonce + 1 # Atomically increment for next call + pending_nonce_map[address] = next_nonce + 1 return next_nonce - async def handle_network_data(data): - logger.info("Received network data: %s", data) + network = P2PNetwork(None) + + network_mempool = mempool + network_chain = chain - network = P2PNetwork(handle_network_data) - async def _handle_network_data(data): logger.info("Received network data: %s", data) try: if data["type"] == "tx": - # Transaction constructor accepts all fields from to_dict tx = Transaction(**data["data"]) if network_mempool.add_transaction(tx): logger.info("Received transaction added to mempool: %s", tx.sender[:5]) - await network.broadcast_transaction(tx) # Re-broadcast + await network.broadcast_transaction(tx) + elif data["type"] == "block": block_data = data["data"] - # Deserialize transactions within the block transactions_raw = block_data.get("transactions", []) transactions = [Transaction(**tx_data) for tx_data in transactions_raw] @@ -116,24 +120,24 @@ async def _handle_network_data(data): timestamp=block_data.get("timestamp"), difficulty=block_data.get("difficulty") ) + block.nonce = block_data.get("nonce", 0) block.hash = block_data.get("hash") + if network_chain.add_block(block): logger.info("Received block added to chain: #%s", block.index) - # TODO: Reorg mempool, notify peers + except Exception: logger.exception("Error processing network data: %s", data) - + + network.handler_callback = _handle_network_data + try: - network.handler_callback = _handle_network_data # Update handler - # Mempool, chain, state for network handler - network_mempool = mempool - network_chain = chain - network_state = state - await _run_node(network, state, chain, network_mempool, pending_nonce_map, claim_nonce) + await _run_node(network, state, chain, mempool, pending_nonce_map, claim_nonce) finally: await network.stop() + async def _run_node(network, state, chain, mempool, pending_nonce_map, get_next_nonce): await network.start() @@ -153,7 +157,7 @@ async def _run_node(network, state, chain, mempool, pending_nonce_map, get_next_ logger.info("[2] Transaction: Alice sends 10 coins to Bob") - nonce = claim_nonce(alice_pk) # Use claim_nonce + nonce = get_next_nonce(alice_pk) tx_payment = Transaction( sender=alice_pk, @@ -165,26 +169,20 @@ async def _run_node(network, state, chain, mempool, pending_nonce_map, get_next_ if mempool.add_transaction(tx_payment): await network.broadcast_transaction(tx_payment) - else: - logger.warning("Transaction rejected by mempool") # ------------------------------- - # Contract Deployment (UNSAFE) + # Contract Deployment # ------------------------------- logger.info("[3] Smart Contract: Alice deploys a 'Storage' contract") - # TODO: Replace ContractMachine exec-based runtime with: - # - RestrictedPython - # - WASM-based VM - # - Custom DSL interpreter contract_code = """ # Storage Contract (UNSAFE EXAMPLE) if msg['data']: storage['value'] = msg['data'] """ - nonce = claim_nonce(alice_pk) # Use claim_nonce + nonce = get_next_nonce(alice_pk) tx_deploy = Transaction( sender=alice_pk, @@ -197,8 +195,6 @@ async def _run_node(network, state, chain, mempool, pending_nonce_map, get_next_ if mempool.add_transaction(tx_deploy): await network.broadcast_transaction(tx_deploy) - else: - logger.warning("Contract deploy rejected") # ------------------------------- # Mine Block 1 @@ -206,7 +202,7 @@ async def _run_node(network, state, chain, mempool, pending_nonce_map, get_next_ logger.info("[4] Consensus: Mining Block 1") - _, deployed_contracts = mine_and_process_block(chain, mempool, state, pending_nonce_map) # deployed_contracts is a list + _, deployed_contracts = mine_and_process_block(chain, mempool, state, pending_nonce_map) contract_address = deployed_contracts[0] if deployed_contracts else None # ------------------------------- @@ -219,7 +215,7 @@ async def _run_node(network, state, chain, mempool, pending_nonce_map, get_next_ logger.error("Contract not deployed. Skipping interaction.") return - nonce = claim_nonce(bob_pk) # Use claim_nonce + nonce = get_next_nonce(bob_pk) tx_call = Transaction( sender=bob_pk, @@ -232,8 +228,6 @@ async def _run_node(network, state, chain, mempool, pending_nonce_map, get_next_ if mempool.add_transaction(tx_call): await network.broadcast_transaction(tx_call) - else: - logger.warning("Contract call rejected") # ------------------------------- # Mine Block 2 @@ -248,9 +242,10 @@ async def _run_node(network, state, chain, mempool, pending_nonce_map, get_next_ # ------------------------------- logger.info("[7] Final State Check") + alice_acc = state.get_account(alice_pk) logger.info("Alice Balance: %s", alice_acc.get("balance", 0) if alice_acc else 0) - + bob_acc = state.get_account(bob_pk) logger.info("Bob Balance: %s", bob_acc.get("balance", 0) if bob_acc else 0) @@ -260,13 +255,12 @@ async def _run_node(network, state, chain, mempool, pending_nonce_map, get_next_ logger.info("Contract Storage: %s", contract_acc["storage"]) else: logger.info("Contract storage not found for %s", contract_address) - else: - logger.info("No contract deployed.") def main(): logging.basicConfig(level=logging.INFO) asyncio.run(node_loop()) + if __name__ == "__main__": main() From fc938cdbb7b59bb046a165283c27ee386afbc918 Mon Sep 17 00:00:00 2001 From: aniket866 Date: Tue, 17 Feb 2026 20:58:14 +0530 Subject: [PATCH 24/29] updating --- consensus/__pycache__/__init__.cpython-311.pyc | Bin 297 -> 0 bytes consensus/__pycache__/pow.cpython-311.pyc | Bin 3060 -> 0 bytes core/__pycache__/__init__.cpython-311.pyc | Bin 489 -> 0 bytes core/__pycache__/block.cpython-311.pyc | Bin 2400 -> 0 bytes core/__pycache__/chain.cpython-311.pyc | Bin 4314 -> 0 bytes core/__pycache__/contract.cpython-311.pyc | Bin 8515 -> 0 bytes core/__pycache__/state.cpython-311.pyc | Bin 7501 -> 0 bytes core/__pycache__/transaction.cpython-311.pyc | Bin 3378 -> 0 bytes network/__pycache__/__init__.cpython-311.pyc | Bin 227 -> 0 bytes network/__pycache__/p2p.cpython-311.pyc | Bin 2622 -> 0 bytes node/__pycache__/__init__.cpython-311.pyc | Bin 225 -> 0 bytes node/__pycache__/mempool.cpython-311.pyc | Bin 1325 -> 0 bytes tests/__pycache__/__init__.cpython-311.pyc | Bin 148 -> 0 bytes .../test_contract.cpython-311-pytest-9.0.2.pyc | Bin 8709 -> 0 bytes tests/__pycache__/test_contract.cpython-311.pyc | Bin 8025 -> 0 bytes .../test_core.cpython-311-pytest-9.0.2.pyc | Bin 5006 -> 0 bytes tests/__pycache__/test_core.cpython-311.pyc | Bin 5734 -> 0 bytes 17 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 consensus/__pycache__/__init__.cpython-311.pyc delete mode 100644 consensus/__pycache__/pow.cpython-311.pyc delete mode 100644 core/__pycache__/__init__.cpython-311.pyc delete mode 100644 core/__pycache__/block.cpython-311.pyc delete mode 100644 core/__pycache__/chain.cpython-311.pyc delete mode 100644 core/__pycache__/contract.cpython-311.pyc delete mode 100644 core/__pycache__/state.cpython-311.pyc delete mode 100644 core/__pycache__/transaction.cpython-311.pyc delete mode 100644 network/__pycache__/__init__.cpython-311.pyc delete mode 100644 network/__pycache__/p2p.cpython-311.pyc delete mode 100644 node/__pycache__/__init__.cpython-311.pyc delete mode 100644 node/__pycache__/mempool.cpython-311.pyc delete mode 100644 tests/__pycache__/__init__.cpython-311.pyc delete mode 100644 tests/__pycache__/test_contract.cpython-311-pytest-9.0.2.pyc delete mode 100644 tests/__pycache__/test_contract.cpython-311.pyc delete mode 100644 tests/__pycache__/test_core.cpython-311-pytest-9.0.2.pyc delete mode 100644 tests/__pycache__/test_core.cpython-311.pyc diff --git a/consensus/__pycache__/__init__.cpython-311.pyc b/consensus/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 39cd960bb136e5c3f3e15a89de574e97abff6585..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 297 zcmZ3^%ge<81Zy`>$ZQ4Dk3k$5V1hC}D*+kP8B!Qh7;_kM8KW3;nWC6-nWLB)8Pb7b zix{I=Qka4nG?`yA0u^d9-{Q*6%u9_=%E?d8zQvcEn3G(ZlUR}(pOILcaZA`YGcPkQ z-L)b)H8mwQ#kHs?zsOIM^%iqMet8iyP=65%h+qX0x7g$36LWIn<5x0#267pGX**lR zgche36~}lK#DL6k&PdG6i%HJUD^ATTE-j9Uj|U2t#K-FuRQ}?y$<0qG%}KQ@;s)vk ixvba-NPJ*sWMsU-;BWyqdcfs)0gM{hL9mDus0{!IZc^a@ diff --git a/consensus/__pycache__/pow.cpython-311.pyc b/consensus/__pycache__/pow.cpython-311.pyc deleted file mode 100644 index d8924a65e721fd683ea448cf258ee10487ecaea4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3060 zcma)8O>Emn79Q$fq9jw2E!%18&^4@BscWa3Zm@BkAdQp8mK$%;q`QD%fM7VZO)Q(jX9mfPnyuJ!DTR(!CT|VBb(bmYfE= zL-NfG-_N{x-|#(tok+wGv=6`EmCPtY{~(ig3$_L?K7zp>!U$6(RBZNCk@DugqR)q} zBka3}uwV2)^ds~Lo@OfsaNr&)2BWBb;Rp`jqlzI+-9-85b0W4Rw4}X`YE53GVX%ip zMAj+#Fje$pAL#hq%neDF}R@TyeebxDy~Nn^#91r}FSFvylYyHiW~fE(dBnJ6lTqZuwt?`n)EGV)j%34KR-c;^PZQfM_ zAncJ4474neTIX^Cx6Jmf5@0+*p&b->frvT@@QXT-J*0Q>);nVZp!(ML&<9|^(`@z5 zsQrfY*#8i;9(6RgDLs6|Uni@1&7gYa>g~hP7V!FqKp%D909=7;evU5*6^Od1u{Y=6 z``z!)vunJz#wsf;&)zO6!Unr0@>o=-ZsjRAswt|@ZHRZZYOtcOy!D5C$PKJ(itGk) zrM#)RAyF0-EV^N0q$J&TL)sb-M&0O|xP_%vQPcB2H|81Uuq5biygAl%0ZR0(W{bG$_3b zr(OirkU?Q;!^2AL)4LGBzFu@bqp;tgsvxlqpW1fVKy|X-ORX2L?T8L3pxxFx_K!(L zjevU62-LfG)K)?`V4(HR34&7t>mDTp3KD@4HhdeNZ8Zm$2EbCJquw>Zq24`FNY5Ox zak!WJ8D;qYa{2%9--uQLWW&GVo$deOL$r65V?M-sx!^+ zorwH4zRBLb9E(U_FPB6FGga_-JAD5?`Be1=Bt}RC?JvKgk;MCRN~~591X0tbRPw1*b62I66$x^TeixK95EWs8O+}M* z=~EHZb#WEai%XrYvd!AjGB&QU6Gyr;?6{V{RUN&-Z?RtWcvXP_5wB9PE4)+^@yt=Z z>1ZrfhgveN0@UG5RZkt$J}%(~z16F!o6TL0vh$FfhS zE3%?1vLv_xNnTOhpd@2)%MIV*RZ@wY6^$fMHwKnfT_X^xuaOr4l34R`H|W9V#&8EJ zFxxKEZeJ?~oF+oK#E|7n$|_*yre4~b8-f*9Mct*LbnvBSopmy)M%~nESzYdU@+!HlAqImrJeYc6Hnb=bmG~c!+~h*7X+l?k@y?I z;YJ9>2lw*_^q5JHIei1*N8&2wFxkE2gQR)tip9*?%$&*09j3?4+=7)Z*y(~9FA%-g zOgi`7)o+)q$ys}H_DR*6T=d-HxFP7BgcrGbS_R8MdX$kd)C4ZTlArtJc0_$i8V5MA@Gu0gWqU5yY$eN4eFev|I!fA%=Pbn_#A>YWDa5C$@$7lReQ$yh}k?UO_yM{|nW(^vwVO diff --git a/core/__pycache__/__init__.cpython-311.pyc b/core/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index ee49e5e41f67b36511bf58546c99a59376a749fa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 489 zcmZvXu};G<5Qfi5oTQDaij9R0kh1Uq2rj;~koJTLQ^esr8vGK(3* zx?${W^|JLZiYk2;*`++dG5z1VLFsKmNC$`QFgrMEhuOh#JIw#4PFfgb@3?{0!(LuJ JedH7u{{aY8f42Yt diff --git a/core/__pycache__/block.cpython-311.pyc b/core/__pycache__/block.cpython-311.pyc deleted file mode 100644 index 62966748082e9dace2b9e355e0944fb025fed712..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2400 zcma(SU2oG?^!h7J(u9@)MJ;V{X{8aig-IJAjS3{_7!xepN(;yevfNyk7#urXI}?pE zkx6~XCZuf=QrXmLOkxbg3%_96Baf++NtU0G_OiWYDo;G^oSVj^El4|#&)2=@`h4A! z-_q$M0hs^&J1ZR{m_!pO(L~x$MN^`ZDO1^0%-C>bLlv{njQ7YfkfoRMVv_bPD_Vla zmx-373Bdg{2{^@lO@+!S+bvwrg^J}+^JbVXGxH0}t$2FT@QPu|f9a?fCVZ=8dcILA zhe>KJE?R|(?JpG|tr1{KC3x3EKr5ss)QBD})k4h?0@7sHx{iUXB4Lm9Km(xA!2LfJFz#Vw|M zH{al)Gb)D{lczt(&wD2G@@LEWPb|keT{J8wUvQb3U%>ih%S&Ng*I~ui_392@(`;A9 zgS;}JItliSH00aKVC+ysZpBA}ljoWzFFmY1tTnIbs2P5Ce5QH){7>euGxtCK<=p*q zPo~zUj;@U#YmFakjvxDT_EPh*(WJ$-%f+=>t2JxEX*-^c?Bj<{z6m4|WZnZZ(12$h zzDWvENkG7q^7beK1MNYs0qp?r(M~EX<66`dWoVL_Ek%cQJc$GFQELF-Q`FTM7OOgO zp=dCZs?_3l&R|RGLdCMFPIh zxA$Hkj|klJKHt^xlNm7-v+stYf78R5RF&-p@*+8H!+Fyc=D^CSfC(Y4Z=l5{o?(EOnkJ=CU)qJF6xbso$wavwL!a5trO#;J6JDCf_>R_2_YEf7q=OPk?&zl6V^MjFoB{@0Yo1XTm2~?N{lNyd= zvHJFzifya>o~YFD4Rz6FD%>W^u&t_rGx#tcb7hzLS@s4PW<;g}>GGJMP)5_<^05(6 zSJRyYZ4`PGRNe%jI(as*>)Tu3+*%t@TLWrC4)*T5v(%6q7r#yg1G^gi+XKhW;DH|k z=Klr`0SeWL8H}ppL4#vxB^U$H*9Qx(>Nt)*XX0!SNEq{%%21w80x))lfrNuN%(Fdzx1HqhXg6ozMSQxG-4pB`u*7Qe l>E4zEK?q2Hoj(CN*nItO`)XU>FF*v_(Z8sB{)z=2+JCU-7GnSa diff --git a/core/__pycache__/chain.cpython-311.pyc b/core/__pycache__/chain.cpython-311.pyc deleted file mode 100644 index e28a9667e1acf539140877704e77ef3124ac366c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4314 zcmbssU2ogg^^zhbN|h`pwqi@E9mdwYsB?`3+3L-3vbk=PE=^p(N#i)^cY^ZWg|2?k7V{9l7kyG%)ncpc}-?6 zqfF>I?q(>5eOoI^A7bRPDVho(ZIY--iyCxo+a|(Y0 zb44Zx`J1Aq%D|4{jv9l!Sde+Cn9r-G!5f%u-qgi{p_*#3U<{GNBwrW60OmRX@Ad-# zt7sOLkpP(6OgvL>tsKKDPhQxu-n7nJk9NsOAv4^Vn`b?I&$&TwMn<3wY629(5^;6x z8xBjPauR~1E7%aTN8FX_he9PWU?&Fff@OHNVKZtm2386&h6czg zGMfw~Q!+h^z6fl<{9|9;cPW`EQ-Z&)jOKl_>~EAZ)p{M^`mmtpVCgLJ4eV^=M6s^3 z)wS!o7k^7N=XjCNskarG$2Y)rD7;C>bRFtsCg8AYK~`=%u?1bZsTLOva#x%v@k3D} zj_Ca8O{cT`h7)#TAyh)G7a5P%&FmSeRD>K#~)*<1=NzY=-EB{#X`+M5;b zgw36>yr13yXSEm{`#P3l`#OMCxJmQyb=&t4h5No~Wh&`SA&HjB(;B}hxG(+a6f&4w z^zFbjx5OmUNyS{$3$BUtMZ@HEMN$f8-OOr%1RD1u_wlT2_d-3AMu900`BMQ`)V|{_on1RqEY9J)}HwGqf+8_qO&b5z0 zsD?qB@7g@xL)wP~(|>6C2?(KeKiGMz@K>hF)Vv?;0QXJ3|H&H={vN~YHVTEotoYgO z1ajaqYB{+nCN${DObHIEbNluIxMf?;Z07tG=l_p9`&u-yXVqJoT5r;&H@Z8$v_`eM zT;Dx_T)|y(`8>2n+W(?&H18+3byD0k_;j(>G1{QUKymqrC<61 z=O=z~gNqtbmJMF4M<}z{h)}$m<3&w#>zsOi;f+N}QWUw7pCH@g?NV_;h1^97Iv!3@ z3dW+Y@S<7FtCCkvL8;W(SUN=l8UHNJt;$<(nEI}E7nj^2&Y!(OQ=fH4F}wAvjv2ONdj z4C`iG_6DxCoQSF97X-IXqs`k+P*;pa%``9tC>82S*a?G4>&c>%h~_37e>4Nj4HKd> z)51qW!k8vCb5G7={A8j)Sg)RXifgKX!oA}Qmph2 zZ^nnM_^?m>!r$Nh$NSdwb$eR2a`Sd>(awEj{o>CNcrao0U$v%Y?5P`-PRZ_+tWK$Vl&_sWAQwY$5R?b0^zu6^ z7pl?rl?zXzJ)6;18nq{Hb04S-^xdQ73}`9SxBi z?pEIJ0J-MwU$d|T|1ohyHR{0x#~B=7a9(r5vx<3<1Ri}5`g$J*M6y#D!?iJy_8V^0 z)!bQq7{+iUHGTsCszQpYqSvj~-+m)$3$^^JsBMWnRn%^Izh~jpa+e)Wt$n`|PTS%1 W5_s`AMS&vrhg)QP=Owd*|Nj6@d%H*g diff --git a/core/__pycache__/contract.cpython-311.pyc b/core/__pycache__/contract.cpython-311.pyc deleted file mode 100644 index 9bf3358c120713c60940060cc8f09e4ae42fe855..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8515 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 diff --git a/core/__pycache__/transaction.cpython-311.pyc b/core/__pycache__/transaction.cpython-311.pyc deleted file mode 100644 index e1f037c0a7d52727402ac574d2d5b5969c499074..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3378 zcmb_eO>7&-6`uVgxx198e`_5prW7Ob7O?zN8LbnysKh#rTa*GSDToOn%!a#?D3iPF z>?$z{I#F941jGj)4A=)3Ad1$FVV?>Z&7mofoN`D3QrJMm009C70ooe_w}6pT-^`M$ zrS1CES@P|BGjIOh`{tYd^T2?DpuG9_>&0~$p|7dYD`Ho9xC+WmWFnI*p6+I!r(T%V3^d?@V2mZ0b>UfHG``a z&tXQPJ3M>!CVTi(FmECY(WU2jGhy;OTuv~B9h4I-$rN|c9q_mp_RYmi$%?P>W^6~y zCCoVZ$!xhYQJ1k{yGFq)I(D{@2)iIegC@ZL;U0*asLs_P#Yf#j-_Y zDqYVgSBUKP@~0<==vt*UOm~QrRM55KK`K&Ij8ZWI!ljEzcguHr=H=BD*TU}VYn9bs z7VTnY!zkLT1qWNJ-a&$9D^-%n=b_!p=WAn;)Xny40s6>Y1F?dCOmUt65lY zt*o~eH~hs7=r!2ZX6S1do`*0KDujsxgYJvLCOB(#v`Jl90X_5t{Qxj%^8hYr^8hSp z6QB|H4O4G&x8p2h|+YbW({SrGo+?EBFv}>-&`(m0@C3jz!eWa3rw>9a)sFtk0!Fgkw6|fU->!NVXioZV zD^0;}nX`KA*`Z$srjuOhiNY5pmOXY$BOk<%&yU7Gp`BXQ?Cc zicu{&hUrq=q`OH>ZW!)HzS~lpjAk<0G6h}SB@o-FlR$@_dqzS#1MyMFC5YYle}>S| z*tOcNmp)u>rp~ldXZ+Ne{nWX=)VYsdYNZzZ)Iu|{z_LwL8W*5@y4^yEY?zUzouys@ z>`@v$I1i2RG=NW7yb%8g-RJsK(+JN==nhIh6#}@z5iT=-oQl$^$@h=>TdvME!m||} z7e)%<)ZgbSU*|VNFf}OYJW(EB)noK4Wbbk$9;6u8rs*9B;Y&mK(xwAwvmGy8HoU?{ zI$&pAAqr44T%$C^_$bVWDLasuX|cA;tSh>}z=UU5 z&j8CV4H7UhqXn#kQ5%W^iF$nxYF!%y@PRtvtJC}H@jdnUgTqbrcuPI+tLOLC%$}NQ zsTX|p!e?WjKewkYH`V1YM~*b7e$pD5_ebWN$@%v1k^SN6z2WJ`F@NU!t>HO;cy2e= z){gp`zOS9w(@wOslfHJcN3TyZ2~8A(QzC#vNC38JhmnD-V>9FE&%Jc90}%5uu8S-y_!MyL|y%AAslx9LKeh qy3L-5GTTH?`L)rpX8*5^UT!|^*NOdrgRMU)zNK~leo6yd?0*4+amC>P diff --git a/network/__pycache__/__init__.cpython-311.pyc b/network/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index dbc6bae3b324e9a1b1aab2f7e3ad6965e8e45ab6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 227 zcmZ3^%ge<81RY)zGR=YXV-N=hn4pZ$50)gBTDK9}g5NiI3MSsQkrYlbfGXnv-f*#0k_1vaMJN dNPJ*sWMsU-AbkN9J>VAa&}(1^!6J5`G5{LaH|_uc diff --git a/network/__pycache__/p2p.cpython-311.pyc b/network/__pycache__/p2p.cpython-311.pyc deleted file mode 100644 index 3462cd25416f0b8942861bda05daa2321239e12a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2622 zcmcgt%WE4)7@vJfYeiNPnLL~}X4<&eOR?;xEi_J?AlspDHS`5BhGo6Gw%1G;7geGeO*Fw7i1nWk*h~|mAplB-2pBUYz_<|ul#MvdDEop3-E8f3~&X_pfXaS6iS(cTEjEoU9gnPDZDQgVCU4|H>){@Z4y=2Y)1D@A7*E;M7(eSB8=m}(LjTEk3X>m#OU7n3%bASqo&@_cg(Y8BRZw}VTI$;N zWTw%`P69myu!1%Y99k7GCpPfVs#uecR)VwzDx^1e3KeG4MfkV+0p3R(*ygq94eln? zZZ})F&^kU>>W^3)#vfSJGaVoX18;*?;f=weOz;hQdSP%#;lK~Xay*42GAu&bWSDdS z`V#dt;+6Kbm=d(4Sictl{(x@agMrKyDE$9yUPCvY<38d}%=yxk4VMPl4;w2T-JP{j)&n0?Bys?H zk%J6aV1^t?7ErX*k&LO6nw}s(h*@pQ<#{e{k_9ED>_i(RNR4#}-6HGqTp$k=r5d zG1eX*%X}HFH6yBZX!$udF2Y;(rSLhrB77qPGw@y=LtgL_hNF5BIl3%(EF>Z^__DAY ztLI_#vhbC_<_Jp*+yYt@&Z7lR5lXL&MYlFym^B>?_b|?zlxj03wkRg1ZdzZMMy|9s zItchIAubulN<5(6muv`TO1fYde#|g+7lM=BA%4f%Mcpj0_bE6OiWmkgI|yNK&ZNv0 z5VKLxrox9rjn*%9w;YIe6+2S&DnNK1>Y~`8Y<3;`VfaV=`s~l{RkzwRSnC9sm) zg3L+7U~2Ee(OsZUsNC(IrQ>+;C$GG;MG8nS3g= s`80VI6zuJy?*f4PIBo;&SqZ)klv)YCy4b_PsypE?*7o->(wdC7*nLxT3-a@GikN|XKTYOa?72t+x7g$36LWIn<5x0#2ATUy&Dkm@v^ce> zIL4zO#y2xB(>WtCGcP7DKP5FLJ|4&~iI3MSsQkrYlbfGXnv-f*#0k^}va47LNPJ*s ZWMsU-Aael~J>VAU&~9J{!6J5`G63VuH<$nb diff --git a/node/__pycache__/mempool.cpython-311.pyc b/node/__pycache__/mempool.cpython-311.pyc deleted file mode 100644 index e3cb03852024a182f6ee8e519c41d8d6a82abf1a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1325 zcmZ`&%}*0S6rb64OR39;)bOE5Mu;K3a4;Iaj0s{yP3S@N(lptuooQKYcgxH|q^Su9 zOwgFf4G$)A!VvzMVw%|O)p%cnCr-ZE?XE@iZTGitX5QEQ-s_j1o)iN4^kmQctRVDV zE<%a7l~V%BK2p#UqG$(Ed=SMPy~)RdxYIfRc2AsD(TKEk-=_U zuyRqzIOOc+;#~f@U|i%MRrAZHWiGB7rj@sB%JP*U<5YFaP3k(FIl5l!Z`Uyu?#LQM zKfpF>UAw*;ZzhKu{_j+lQHWT8g)G%CcL$VxRL2MKy(ovUbsTN;(K=d-E`87$FS4oQ zL$ElGtT6Oo1vy=j0d(MkMGWYP*OpSGEen(iVi1QLmM{vAXPcmk?(6Gl}w$5pG` zw498>WzDVvjgl87geT>?&!ANAD6BRVcHyrB6|BVrfNkU@k=FZR_TB8;`SJ)YkYnh>(G#lQd^7&d&+=GOOW<#cJdBwuqY{%nZZTWfIn1c zxx8k`RSdW~N7mI6b2`ZqdeP?ki?UsKSsM>|`@fAGWibu_6sAyLy4gGSaa}&m-W$8I zaKu}E1G_1opll`~{~nf6VY=?7biHEJjk44=U0>fY%E1ZmhLBta=V^c=hb}@8T?Wo(aFAd~W|1xRlb$8-28G_^!?5RP Zo(9E>VT?Ux5JT9D@Kf&n`p1DE_&3^aEA;>X diff --git a/tests/__pycache__/__init__.cpython-311.pyc b/tests/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 94a9cde9505cfb1700796aa85f28a72a1970dd8f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 148 zcmZ3^%ge<81Q&BAWP<3&AOZ#$p^VRLK*n^26oz01O-8?!3`I;p{%4TnFLh_Dn9$ylGNgo;+Xh&pg>7{yk0@&FAkgB{FKt1RJ$Tppgxev#r#0x R12ZEd;|B&9QN#=s0{{ZxAs_$% diff --git a/tests/__pycache__/test_contract.cpython-311-pytest-9.0.2.pyc b/tests/__pycache__/test_contract.cpython-311-pytest-9.0.2.pyc deleted file mode 100644 index 94215a9e5538b45b5ceb7166dc02bf547e2efa48..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8709 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 NZ_~!G-OxZW_b)KHA1nX> diff --git a/tests/__pycache__/test_contract.cpython-311.pyc b/tests/__pycache__/test_contract.cpython-311.pyc deleted file mode 100644 index b8ef83c490f6f8c07f6df4809e16ab9ac098710a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8025 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 diff --git a/tests/__pycache__/test_core.cpython-311-pytest-9.0.2.pyc b/tests/__pycache__/test_core.cpython-311-pytest-9.0.2.pyc deleted file mode 100644 index b19cc98b56de6269b1e87b876bd8d07d7ebbc4e5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5006 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 diff --git a/tests/__pycache__/test_core.cpython-311.pyc b/tests/__pycache__/test_core.cpython-311.pyc deleted file mode 100644 index 74a89d0df323fd6a64cc93a53da59ebb6bf63b2c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5734 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 Date: Tue, 17 Feb 2026 21:14:51 +0530 Subject: [PATCH 25/29] fixed header --- core/block.py | 107 +++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 88 insertions(+), 19 deletions(-) diff --git a/core/block.py b/core/block.py index c650330..5817369 100644 --- a/core/block.py +++ b/core/block.py @@ -1,36 +1,105 @@ import time -from typing import List, Optional, Union -from core.transaction import Transaction # Assuming Transaction is defined in core.transaction +import hashlib +import json +from typing import List, Optional +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] + class Block: - def __init__(self, index: int, previous_hash: str, transactions: List[Transaction], timestamp: Optional[float] = None, difficulty: Optional[int] = None): + def __init__( + self, + index: int, + previous_hash: str, + transactions: List[Transaction], + timestamp: Optional[float] = None, + difficulty: Optional[int] = None, + ): self.index = index self.previous_hash = previous_hash - self.transactions: List[Transaction] = transactions if transactions is not None else [] # Ensure transactions is a list - # Use integer milliseconds for timestamp for determinism - self.timestamp: int = round(time.time() * 1000) if timestamp is None else round(timestamp * 1000) - # Ensure difficulty is an integer + self.transactions: List[Transaction] = transactions or [] + + # Deterministic timestamp (ms) + self.timestamp: int = ( + round(time.time() * 1000) + if timestamp is None + else round(timestamp * 1000) + ) + + self.difficulty: Optional[int] = difficulty self.nonce: int = 0 self.hash: Optional[str] = None - self.difficulty: Optional[int] = difficulty - def _base_dict(self): - """Shared dictionary building logic.""" + # 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, - "transactions": [tx.to_dict() for tx in (self.transactions or [])], + "merkle_root": self.merkle_root, "timestamp": self.timestamp, "difficulty": self.difficulty, - "nonce": self.nonce + "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): - """Full block data for serialization/transport.""" - data = self._base_dict() - data["hash"] = self.hash - return data + return { + **self.to_header_dict(), + **self.to_body_dict(), + "hash": self.hash, + } - def to_header_dict(self): - """Data used for mining (consensus).""" - return self._base_dict() + # ------------------------- + # HASH CALCULATION + # ------------------------- + def compute_hash(self) -> str: + header_string = json.dumps( + self.to_header_dict(), + sort_keys=True + ) + return _sha256(header_string) From cccf341fd94b7b87bd94c0ce0774d37e403402cc Mon Sep 17 00:00:00 2001 From: Aniket Date: Tue, 17 Feb 2026 21:19:39 +0530 Subject: [PATCH 26/29] Update main.py --- main.py | 122 ++++++++++++-------------------------------------------- 1 file changed, 26 insertions(+), 96 deletions(-) diff --git a/main.py b/main.py index 39f76ea..cee8922 100644 --- a/main.py +++ b/main.py @@ -21,8 +21,12 @@ def create_wallet(): return sk, pk -def mine_and_process_block(chain, mempool, state, pending_nonce_map): - """Helper to mine a block and apply transactions.""" +def mine_and_process_block(chain, mempool, pending_nonce_map): + """ + Mine block and let Blockchain handle validation + state updates. + DO NOT manually apply transactions again. + """ + pending_txs = mempool.get_transactions_for_block() block = Block( @@ -33,7 +37,6 @@ def mine_and_process_block(chain, mempool, state, pending_nonce_map): mined_block = mine_block(block) - # Ensure miner field exists (minimal fix without touching Block class) if not hasattr(mined_block, "miner"): mined_block.miner = BURN_ADDRESS @@ -46,22 +49,19 @@ def mine_and_process_block(chain, mempool, state, pending_nonce_map): if isinstance(miner_attr, str) and re.match(r'^[0-9a-fA-F]{40}$', miner_attr): miner_address = miner_attr else: - logger.warning("Block has no miner or invalid address. Crediting burn address.") + logger.warning("Invalid miner address. Crediting burn address.") miner_address = BURN_ADDRESS - state.credit_mining_reward(miner_address) + # Reward must go through chain.state + chain.state.credit_mining_reward(miner_address) for tx in mined_block.transactions: - result = state.apply_transaction(tx) + sync_nonce(chain.state, pending_nonce_map, tx.sender) - if isinstance(result, str) and re.match(r'^[0-9a-fA-F]{40}$', result): - deployed_contracts.append(result) - logger.info("New Contract Deployed at: %s", result) - sync_nonce(state, pending_nonce_map, tx.sender) - elif result is True: - sync_nonce(state, pending_nonce_map, tx.sender) - elif result is False or result is None: - logger.error("Transaction failed in block %s", mined_block.index) + # Track deployed contracts if your state.apply_transaction returns address + result = chain.state.get_account(tx.receiver) if tx.receiver else None + if isinstance(result, dict): + deployed_contracts.append(tx.receiver) return mined_block, deployed_contracts else: @@ -81,7 +81,7 @@ async def node_loop(): logger.info("Starting MiniChain Node with Smart Contracts") state = State() - chain = Blockchain() + chain = Blockchain(state) mempool = Mempool() pending_nonce_map = {} @@ -96,16 +96,13 @@ def claim_nonce(address): network = P2PNetwork(None) - network_mempool = mempool - network_chain = chain - async def _handle_network_data(data): logger.info("Received network data: %s", data) + try: if data["type"] == "tx": tx = Transaction(**data["data"]) - if network_mempool.add_transaction(tx): - logger.info("Received transaction added to mempool: %s", tx.sender[:5]) + if mempool.add_transaction(tx): await network.broadcast_transaction(tx) elif data["type"] == "block": @@ -124,7 +121,7 @@ async def _handle_network_data(data): block.nonce = block_data.get("nonce", 0) block.hash = block_data.get("hash") - if network_chain.add_block(block): + if chain.add_block(block): logger.info("Received block added to chain: #%s", block.index) except Exception: @@ -148,8 +145,8 @@ async def _run_node(network, state, chain, mempool, pending_nonce_map, get_next_ logger.info("Bob Address: %s...", bob_pk[:10]) logger.info("[1] Genesis: Crediting Alice with 100 coins") - state.credit_mining_reward(alice_pk, reward=100) - sync_nonce(state, pending_nonce_map, alice_pk) + chain.state.credit_mining_reward(alice_pk, reward=100) + sync_nonce(chain.state, pending_nonce_map, alice_pk) # ------------------------------- # Alice Payment @@ -170,92 +167,25 @@ async def _run_node(network, state, chain, mempool, pending_nonce_map, get_next_ if mempool.add_transaction(tx_payment): await network.broadcast_transaction(tx_payment) - # ------------------------------- - # Contract Deployment - # ------------------------------- - - logger.info("[3] Smart Contract: Alice deploys a 'Storage' contract") - - contract_code = """ -# Storage Contract (UNSAFE EXAMPLE) -if msg['data']: - storage['value'] = msg['data'] -""" - - nonce = get_next_nonce(alice_pk) - - tx_deploy = Transaction( - sender=alice_pk, - receiver=None, - amount=0, - nonce=nonce, - data=contract_code, - ) - tx_deploy.sign(alice_sk) - - if mempool.add_transaction(tx_deploy): - await network.broadcast_transaction(tx_deploy) - # ------------------------------- # Mine Block 1 # ------------------------------- - logger.info("[4] Consensus: Mining Block 1") - - _, deployed_contracts = mine_and_process_block(chain, mempool, state, pending_nonce_map) - contract_address = deployed_contracts[0] if deployed_contracts else None + logger.info("[3] Mining Block 1") + mine_and_process_block(chain, mempool, pending_nonce_map) # ------------------------------- - # Bob Interaction + # Final State Check # ------------------------------- - logger.info("[5] Interaction: Bob sends data to Contract") - - if contract_address is None: - logger.error("Contract not deployed. Skipping interaction.") - return + logger.info("[4] Final State Check") - nonce = get_next_nonce(bob_pk) + alice_acc = chain.state.get_account(alice_pk) + bob_acc = chain.state.get_account(bob_pk) - tx_call = Transaction( - sender=bob_pk, - receiver=contract_address, - amount=0, - nonce=nonce, - data="Hello Blockchain", - ) - tx_call.sign(bob_sk) - - if mempool.add_transaction(tx_call): - await network.broadcast_transaction(tx_call) - - # ------------------------------- - # Mine Block 2 - # ------------------------------- - - logger.info("[6] Consensus: Mining Block 2") - - mine_and_process_block(chain, mempool, state, pending_nonce_map) - - # ------------------------------- - # Final State - # ------------------------------- - - logger.info("[7] Final State Check") - - alice_acc = state.get_account(alice_pk) logger.info("Alice Balance: %s", alice_acc.get("balance", 0) if alice_acc else 0) - - bob_acc = state.get_account(bob_pk) logger.info("Bob Balance: %s", bob_acc.get("balance", 0) if bob_acc else 0) - if contract_address: - contract_acc = state.get_account(contract_address) - if contract_acc and "storage" in contract_acc: - logger.info("Contract Storage: %s", contract_acc["storage"]) - else: - logger.info("Contract storage not found for %s", contract_address) - def main(): logging.basicConfig(level=logging.INFO) From 2231368c4752fdba66d4b54baaa47b5eabb8d40e Mon Sep 17 00:00:00 2001 From: Aniket Date: Tue, 17 Feb 2026 21:31:22 +0530 Subject: [PATCH 27/29] Update core/block.py Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- core/block.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/block.py b/core/block.py index 5817369..cbd4ebe 100644 --- a/core/block.py +++ b/core/block.py @@ -39,7 +39,7 @@ def __init__( self, index: int, previous_hash: str, - transactions: List[Transaction], + transactions: Optional[List[Transaction]] = None, timestamp: Optional[float] = None, difficulty: Optional[int] = None, ): From dd209dcd479ef2d544fda21e0de1826e3613bf6f Mon Sep 17 00:00:00 2001 From: Aniket Date: Tue, 17 Feb 2026 21:37:01 +0530 Subject: [PATCH 28/29] Update main.py Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.py b/main.py index cee8922..d9670c0 100644 --- a/main.py +++ b/main.py @@ -135,7 +135,7 @@ async def _handle_network_data(data): await network.stop() -async def _run_node(network, state, chain, mempool, pending_nonce_map, get_next_nonce): +async def _run_node(network, chain, mempool, pending_nonce_map, get_next_nonce): await network.start() alice_sk, alice_pk = create_wallet() From 8252bc5f32427de879196cb14711613ff745ad56 Mon Sep 17 00:00:00 2001 From: Aniket Date: Tue, 17 Feb 2026 21:37:46 +0530 Subject: [PATCH 29/29] Update core/block.py Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- core/block.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/block.py b/core/block.py index cbd4ebe..23f7536 100644 --- a/core/block.py +++ b/core/block.py @@ -51,7 +51,7 @@ def __init__( self.timestamp: int = ( round(time.time() * 1000) if timestamp is None - else round(timestamp * 1000) + else int(timestamp) ) self.difficulty: Optional[int] = difficulty