From 5d58c2c93924cb67bbbc2fb51450c1f0cbada09d Mon Sep 17 00:00:00 2001 From: aniket866 Date: Sat, 14 Feb 2026 18:08:41 +0530 Subject: [PATCH 01/18] 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 zcmahMOKcm*b@oTDNJ_M*zbIL2ea%Q_Wy?wt#}N?!=FxTv48;sJqhT6fpf~KZ)V9AY1!^@ z`1Z}`y?OKY{q}Fo%>e}Mk6&)4{vAZc`f43!ks_zDh`47l2#yCEGQ9d*MF<^nK+S=uel2tVf zqwvLSCWmDucTH9nQyERE($hzc+naMRe2g@dU{sV~RptRoa4HLsS2=(JkpNB<0QyxQ zKv4~-;sYkpq&9&RK}WcelXIHF)N$tkC;vC+Df1L6prp5)BP-QbKs!LU>(Ro#nqZ(7 zV9(6OI7gHfCA*Tz=~w_d=a^ExS8~a{7~GZDwe-AB_(;DDT|9T^mabv_&ei2R*HW3( z#YH)lxuayUc1I_&#+TO}F`0ywTr!z&q4LLTV;}I)2LRkhue*DT{KMu~(yyNLrZjCy z(?#AA`wiOPz!s!<(!ziUM{oTVAdgXjDWLBFbK5-zhgAlCC(^r`PW8v!nf$CSkOtf&dgR zJ-oIxW`+hUA#|+$;l|eQtni2-j^GyH(4ZWmR;nByyd+Q#D#66jR$#vKjG@sqHrN7K z=b>6)r>Fe8lo1}x`42g&Y&|dVG43zyA@vjRtnazT6Ttg~8wgJE9qt1jHP1zNso}nL ziobC`@D=N6o}lmfcb!6gCoqRwLEyY}8RINICw-C8SLWwaN=nP*r0R>7=CfEj5Xm+! z9*1Is8T|fXaLd!r@aD4I-@gzrB z#wiF6gjtMpt%c34IRSSYu@9ZxR;pP|_dN}#=GT)DPck}$lvFm8OlKETO8&TOLhoDe z0519?0ANoYJ^LLa<&Kfkw@*`M$E4LUSq#|0j?I)6luBoweo_uj7{Q5Gp;jX@`~3Qg z+b{9In3wp)?_ub6Gj!DoT{S{i?Y8dCX+pYS1&@~opPnoSKQe+JQPSJbzkIoBoduHH z(D(3_Z-gc2@sxUHD`fSLaH|CnjxOVzAt}BduOKz=?Cx2Ds-! zJ0R7rC)oy9;K&V2#1xOT`qLXVv%wd5^+PwVzY(wc5A}l2f<8$&qpd_!Yi(3})}ITw z7ZB?20l0UIAH*GS=UvaGH-rMXyz7klg#eg4ZMT+HNE5o`W((=MmRrFYT^hTISF|ze zk@||FKxCbez&mq`nv~I2rCPd`K6k^el$w`RZ8@D?Ujm1Yd)%*3ucy9uK!V+zgK!%> zV|-m|tA=i;rRI??PshJ3ucx!JilabrGZw@oRne%AdkIFS9T97amI9Y_!V1>lD#=>P zq^CMOTu-Kf0TZ=4er-)tR&rX*M?(SbC!8p|A%cz4J=H7g#8_N4xe29JUS3W&M238q z8xxK!5Lxx(aOeA|B0|F04^NcC6Mw&Mh9|A?WCbxzJ=8g0w{|@0-@@DLR%@*2tN2h; zxVUbJJyz|j7{RC!?P`Gpr>yu|-p+l&0zWe$8}4SZvS`s}tLBQyk*?cMurL*=%i(uJqL zG214sw#lMsi^n!Wf+%!Lj1uWg@uWL8#V9$hee;SHj8>4pDQb82TAkzjom1t`Df`6G z)2KagVtd{0?%np=T|JN1D@~~D>`zGOYWbOf;w&W4_GoeL^2xKuU`z zkf$KGuq7BDZ96cv>rHR)4J8+!tK<9w(^y-UJXi6^0cqr?LSU-Tr5jKqs(gWi!bYHl zjmN(r&_D(*7W3s#&uSSBQ@@g>nmtNYJD22)3ho9anoj+Rlr?dv>-?Hq&dwd21UYCG zgpUCh6C7W4iAapek&aK*XzfdDpsFcTD<)}0Kjy8sI$+A3~${60DV6)UXF~LkyBRWRMGz`dZIM;k8?(J#*EHb(V6||<#P10 z8J)GFvwPfLWRJ6=Um0RIaOv)`J38(5ZmE#d3 z0G(2=0rI}72EGR5L*?XK*$A?j$~0!x0$ZO|Yg!?NS&X4EGRC_FQu7jgMB!vDGW)Q$ zDq|I@?CRGEk%GERoX3Gi70$fw~mMETM?VNx4VK;n`9X8`xTUxnp_k1NOn z0Z|yOAP=;QLOU5&LAyjS4_snGf>c2s=wpTR732Yt6-LPLoxqikh&C7VJ53o`Nyisu zeGwD;aGL3uh6;EMZk%W)pMvpP2%;aA?$u_1;_$k^i056k&EN~LKvK273ZTL<3}d4{ hqp{m)*l6rFiW!Z)!hg*$aEo`tpXmJ0?^&gS{SRQ>tTzAv 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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)