From 775f7b6b96a48418cd9a644227daf90bfbf1647e Mon Sep 17 00:00:00 2001 From: arman Date: Fri, 12 Mar 2021 11:50:01 +0530 Subject: [PATCH 01/26] Removing pyc files --- Event.pyc | Bin 2019 -> 0 bytes InputsConfig.pyc | Bin 1675 -> 0 bytes Models/Bitcoin/Node.pyc | Bin 2516 -> 0 bytes Models/Ethereum/Block.pyc | Bin 1649 -> 0 bytes Models/Ethereum/Node.pyc | Bin 2516 -> 0 bytes Models/Network.pyc | Bin 685 -> 0 bytes Models/Node.pyc | Bin 2516 -> 0 bytes 7 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 Event.pyc delete mode 100644 InputsConfig.pyc delete mode 100644 Models/Bitcoin/Node.pyc delete mode 100644 Models/Ethereum/Block.pyc delete mode 100644 Models/Ethereum/Node.pyc delete mode 100644 Models/Network.pyc delete mode 100644 Models/Node.pyc diff --git a/Event.pyc b/Event.pyc deleted file mode 100644 index 3e705a5385936f7b48636b9e34e205fd3da219fa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2019 zcmcIlOK;Oa5T3Quv?=8+3S2q)0;zfdiHA^2k;)+w8wrUVSh?{wIPpu~O+`>owEvhN z0P~Gg2N1_*ogL3??3r)AnMuN*H@ANjdtb)1Tp{1z@^;5868I%l-tI07%NRHm!7oSGLvb_4Ti5~bL*6c8ajxi`$gmQK!e%H6n;lU?cxqe> zq7J{}MJy^SN>L__;?vv;vXiVF&mu&U2b|4LHDYSerSY=Hmx$g72Q>Y!Wp4X%K8=(9 zJL_h?tojG=G)!~zAujg!_V@aq_Fw!*6)K)p3lHLqNsH7Q(`DV^fK*Se2;rBH6mf2i z@!Nc2OkO7Qj7`1C<_eW)*9A?ZK*-vGiLMf&KF;mjj@g#gmhdH;R!TUL`Z4{NtZ*Ha zDBL)KDBLcgDBWdWjW$tigc-vdHj)OtZkL58>P)5J6GhJhBTaHuS}7425n3$T%VeqZ zO%KAUoUI7DE^d5}eaA^+#Q$wJgd|dJ5^cxc0z4wlfw_3i#0(>RH%M8Ks2UmQ8!(Z+ z#_X&CGThcSFVAd)8e?~bnpIh#OsId#N`rbD0JslnDn-2DJVN4LxS&8S^Xky!ri252 zjA&*TGBO^`Su;bmvrV>W@Kv_WsFk&s4lgTP$DS>)5qn=xtoOE-u6rM6&aNoA);9Mj z5XUlw{pK5H?(>Pm(35(i#<*j(6}`y@XYLg-H_XRXHzIc-AhX3P%XPs9K9_#C7?OX> z*g{1ne?sL-&Vs)?5U4ertRFJ^A$3Q2@(0tKW%5d}riOZ7krCle8Bl^BgyL$?O!V6*NF+mY@r?oKswGQmHg z|Gqc_rCYdwAr6ygGgwBE(067362QmjD4{8v^5Xd};VUXw-Kn{Tz0XYm}6l4L!7|0P2r@%wZagZ1s zgH93r2@n&IQ4=6fQJMV%fBcX?LcWJpac2?4Br}_W0Er^GaVEEwIbm|8f5=H>If+!P zlrFNt$kd?-c8rk{MUJHlOt)dl=|ftc1~CnCh80DH{!(;`6-E6JKhbg)#0*HZXV{)) zJIi*C?YZ`5kY{09EFN_(7ym=+n~m}uUN4Qe3p75Ux6sAw zK;y@{_hqm2;r5r$>uKb2)#PyDp7z>C7tr&zd6cT&;@ZcrpQ^=m&$gaUE`7VPuIc*g z-#E${^bB3}dMK6QT2fO`f5#*ruvyRZQM`?yd&Y<(Y`jRv_Z1T^_-X&-Q7sL z2?^KqAXRTw^;Z?Irl-&h+>VM98M=v2|4vO$1uAlk4v`XR)J!!Z;hT5T2_0M7)rM)S zNOgTCTgEu_O?(%pm3HW>isL_XWaXBMcTBijsdYkkr=k7UT_;#sURka@TDf|Jd-$(% zY4_D8p_72|YczQ)pBv5QGyLhjX~upra|PeSxl!x*jMXHgzqjXEhye$m6p?TWoKeis d&U(@(U2i7VZ=1an?wzmZYI4=rSOcsJe*kR)KrjFR diff --git a/Models/Bitcoin/Node.pyc b/Models/Bitcoin/Node.pyc deleted file mode 100644 index 99fced99c0845bfe1246207f8bef0ee707999210..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2516 zcmcIlUvC>l5TEt=PaT>PP(UdH>PM>*b(y zvL8_R415m0055zcJ^=iFb55KJcyoQ;x!Kvhnc11&%tn80Zv0|)U#7DBMR#dm;m$jgyl}HlkxX2s zv~!+^VP}~5;VcQRGjWMOn_(?4ROozXiv8ZDWsyC|p;7wXTcCl?=UHgu#jl^0=WZ4@ zFnh%)ro$vJ!WK8qlENox$jib%E6cI&IV;k!tM)J=`iT!@b;XAkrO7xcQm2Q}tavv* zwzO+`zAi>ZJhRgqmJ`=YCZi;ySH%YPkaQ9*F``yR?!7|$qrauLq zD~@x7ERKl>PyeOXHaDMCWgzch9ANkX2*QuRSHN2Uco%!j-N?w{kB?-5+!z_vWE2Sw z0xa^cpahVRW=ugZZ^@+*^ZFxs0r8+)hu;1Th~CEhX)!$u{-7+5^ApX6BFb_}A>9A& z@T-C4mm1AvI(0>6F{NSRx^P`aFc8#Jl@&Ps`eXS4^q)9j4XBF2_}s^u=J&ARnQ1Xw zcv3pTY%`@A&OgDt5l+N};WM((QdnsC7InG!P3Cn1k&%uVyHkoWUY=2(RU+MBYo6h$&>-O$05R0g2&3k=ltV_2seay2PXrz-v&;@4ey zF^`u0ZU^H%GY)<>%%JiIob+0p6x(ZIftWLaDt^Fz%1#~_42+caxU zvjgs3bI)v>4LqCXPECP9XRm>=g9$UC=&cwe2T<9$J5W&ftI%qPAlmIVh_-i22iATp z%Y)p<@H8nEmgzF5ie-X5R9+hbl+OO!SGuV+`xbo-bVXCFZ8rwWPQvgr2(O$6tBzmC z`y~W#6e9cD*O~(MwLSJVv@KN6@{R&i-BDn)&2b5LRHS=wM^#Kq{;$QYieyzEb`x%B zbS!(amWCzP70#9j*W@eUEl;`jJZrVgoOfXm(9RygMs+o42U}eZ)y2pvi(}rfajaj) z@ubYo#+X{(8TNgWPe_O#`zgsikd@!ct?5>?1SG4$vu!j@`*yq0-flT+Pi aDPN6T8g&2LaYZlKy($<_=$1lnEBXhG;p!9s diff --git a/Models/Ethereum/Block.pyc b/Models/Ethereum/Block.pyc deleted file mode 100644 index 5d62487a78419f99ce81e3b4e8202aa9d53b22bf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1649 zcmcIkOK;Oa5MIY^+JwTPLL3l>JsPP>qDV+p#Y>@Fkm$jYkm#Y-*4`xB`VqWq9#wDj zck&yM_yO?EY(mq31P5@klbOlPH{Z_AI{bFK`yd#eq+;=e_ZvfaYS)$6JnHC>G{v}TjasAk06js;gBstpzl(d0z4EXH`u=^uDaql6!AD} zQ~H;d5b{t@OsTDOGc8|2)Ve7o*ONt+>ActNN<0U1RjWdp(#cHE-Ryu9OquDAvYN1Q zN+c1%26UB6cBL}~XRfoUu4Ockld6_ejhe>#fQRc9Oy=PmQ@(4oG?_#S5u7PIyHRdE zb6)HBrfTe8S#<{pVm`C+F0<`z4(GOe|8I$KXJ(9wF)A#nHYVJ9%E zW!81mR`YXXopiM-txBD#N^C_*b`s8tADvF~Cezun<;-doiVn+~3+(Oc? z<2S(5H_EZc_Xh(~$x|(BJ;8{S=^x5YmCa3I+-hm~un{P|g>S*MC2e z`+I}IfR=1Z<1DohfB!Nkjm1!@#bNvXk&B^tdj?}J%y&Gv1@wGff=#_bAuI_o2l5TEt=PaT>PP(UdH>PM>*b(y zvL8_R415m0055zcJ^=iFb55KJcyoQ;x!Kvhnc11&%tn80Zv0|)U#7DBMR#dm;m$jgyl}HlkxX2s zv~!+^VP}~5;VcQRGjWMOn_(?4ROozXiv8ZDWsyC|p;7wXTcCl?=UHgu#jl^0=WZ4@ zFnh%)ro$vJ!WK8qlENox$jib%E6cI&IV;k!tM)J=`iT!@b;XAkrO7xcQm2Q}tavv* zwzO+`zAi>ZJhRgqmJ`=YCZi;ySH%YPkaQ9*F``yR?!7|$qrauLq zD~@x7ERKl>PyeOXHaDMCWgzch9ANkX2*QuRSHN2Uco%!j-N?w{kB?-5+!z_vWE2Sw z0xa^cpahVRW=ugZZ^@+*^ZFxs0r8+)hu;1Th~CEhX)!$u{-7+5^ApX6BFb_}A>9A& z@T-C4mm1AvI(0>6F{NSRx^P`aFc8#Jl@&Ps`eXS4^q)9j4XBF2_}s^u=J&ARnQ1Xw zcv3pTY%`@A&OgDt5l+N};WM((QdnsC7InG!P3Cn1k&%uVyHkoWUY=2(RU+MBYo6h$&>-O$05R0g2&3k=ltV_2seay2PXrz-v&;@4ey zF^`u0ZU^H%GY)<>%%JiIob+0p6x(ZIftWLaDt^Fz%1#~_42+caxU zvjgs3bI)v>4LqCXPECP9XRm>=g9$UC=&cwe2T<9$J5W&ftI%qPAlmIVh_-i22iATp z%Y)p<@H8nEmgzF5ie-X5R9+hbl+OO!SGuV+`xbo-bVXCFZ8rwWPQvgr2(O$6tBzmC z`y~W#6e9cD*O~(MwLSJVv@KN6@{R&i-BDn)&2b5LRHS=wM^#Kq{;$QYieyzEb`x%B zbS!(amWCzP70#9j*W@eUEl;`jJZrVgoOfXm(9RygMs+o42U}eZ)y2pvi(}rfajaj) z@ubYo#+X{(8TNgWPe_O#`zgsikd@!ct?5>?1SG4$vu!j@`*yq0-flT+Pi aDPN6T8g&2LaYZlKy($<_=$1lnEBXhG;p!9s diff --git a/Models/Network.pyc b/Models/Network.pyc deleted file mode 100644 index 3e0572a118c71317ffc65107a0ac78055ae5aace..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 685 zcmcIhOHRWu5FN+i-%4G;9kL=ND;5Z$78YfZMT%HdLY1iv)X-I3|7O#{~1mjVUYMSC;%oc_Ml);GMIS4B!?F~;2`}5 z_(UBC8+a}Kz!3)wq3d86Rc7w&P*->9{ntYr4~97HKG6U&Mgq=*jKje*Nl_|sN-BX* zoJ|b1sH8649zwM+`cc%WaLN(MG=zavg;)l-hIOzPxC|e3sX|d^LWbwc=1!Y%Q0Q4c zPRr?~sG@$<53i%+A7HH9ldkhavvfUFx}{rFQ)@GAUZd0_7d`tkV-w61M~{;HmBdopJFMsJ@K5$E BikJWZ diff --git a/Models/Node.pyc b/Models/Node.pyc deleted file mode 100644 index 99fced99c0845bfe1246207f8bef0ee707999210..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2516 zcmcIlUvC>l5TEt=PaT>PP(UdH>PM>*b(y zvL8_R415m0055zcJ^=iFb55KJcyoQ;x!Kvhnc11&%tn80Zv0|)U#7DBMR#dm;m$jgyl}HlkxX2s zv~!+^VP}~5;VcQRGjWMOn_(?4ROozXiv8ZDWsyC|p;7wXTcCl?=UHgu#jl^0=WZ4@ zFnh%)ro$vJ!WK8qlENox$jib%E6cI&IV;k!tM)J=`iT!@b;XAkrO7xcQm2Q}tavv* zwzO+`zAi>ZJhRgqmJ`=YCZi;ySH%YPkaQ9*F``yR?!7|$qrauLq zD~@x7ERKl>PyeOXHaDMCWgzch9ANkX2*QuRSHN2Uco%!j-N?w{kB?-5+!z_vWE2Sw z0xa^cpahVRW=ugZZ^@+*^ZFxs0r8+)hu;1Th~CEhX)!$u{-7+5^ApX6BFb_}A>9A& z@T-C4mm1AvI(0>6F{NSRx^P`aFc8#Jl@&Ps`eXS4^q)9j4XBF2_}s^u=J&ARnQ1Xw zcv3pTY%`@A&OgDt5l+N};WM((QdnsC7InG!P3Cn1k&%uVyHkoWUY=2(RU+MBYo6h$&>-O$05R0g2&3k=ltV_2seay2PXrz-v&;@4ey zF^`u0ZU^H%GY)<>%%JiIob+0p6x(ZIftWLaDt^Fz%1#~_42+caxU zvjgs3bI)v>4LqCXPECP9XRm>=g9$UC=&cwe2T<9$J5W&ftI%qPAlmIVh_-i22iATp z%Y)p<@H8nEmgzF5ie-X5R9+hbl+OO!SGuV+`xbo-bVXCFZ8rwWPQvgr2(O$6tBzmC z`y~W#6e9cD*O~(MwLSJVv@KN6@{R&i-BDn)&2b5LRHS=wM^#Kq{;$QYieyzEb`x%B zbS!(amWCzP70#9j*W@eUEl;`jJZrVgoOfXm(9RygMs+o42U}eZ)y2pvi(}rfajaj) z@ubYo#+X{(8TNgWPe_O#`zgsikd@!ct?5>?1SG4$vu!j@`*yq0-flT+Pi aDPN6T8g&2LaYZlKy($<_=$1lnEBXhG;p!9s From 350c8a23953ec9a1554022d88f6873cd39e3c149 Mon Sep 17 00:00:00 2001 From: arman Date: Fri, 12 Mar 2021 11:59:10 +0530 Subject: [PATCH 02/26] Fixed excel creation --- Main.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/Main.py b/Main.py index 221e9bb..0951b9e 100644 --- a/Main.py +++ b/Main.py @@ -1,3 +1,4 @@ +from datetime import datetime from InputsConfig import InputsConfig as p from Event import Event, Queue from Scheduler import Scheduler @@ -67,7 +68,7 @@ def main(): # distribute the rewards between the particiapting nodes Incentives.distribute_rewards() # calculate the simulation results (e.g., block statstics and miners' rewards) - Statistics.calculate() + Statistics.calculate(i) if p.model == 3: Statistics.print_to_excel(i, True) @@ -76,16 +77,11 @@ def main(): ########## reset all global variable before the next run ############# Statistics.reset() # reset all variables used to calculate the results Node.resetState() # reset all the states (blockchains) for all nodes in the network - fname = "(Allverify)1day_{0}M_{1}K.xlsx".format( - p.Bsize/1000000, p.Tn/1000) - # print all the simulation results in an excel file - Statistics.print_to_excel(fname) - fname = "(Allverify)1day_{0}M_{1}K.xlsx".format( - p.Bsize/1000000, p.Tn/1000) - # print all the simulation results in an excel file - Statistics.print_to_excel(fname) - Statistics.reset2() # reset profit results + fname = f"(Allverify)1day_{p.Bsize/1000000}M_{p.Tn/1000}K-{i}-{datetime.now()}.xlsx" + # print all the simulation results in an excel file + Statistics.print_to_excel(fname) + # Statistics.reset2() # reset profit results ######################################################## Run Main method ##################################################################### if __name__ == '__main__': From 6283339080a97f5ffba9a90fd1ba653a3ff56869 Mon Sep 17 00:00:00 2001 From: arman Date: Fri, 12 Mar 2021 12:03:52 +0530 Subject: [PATCH 03/26] Added fee attribute to Block and Node classes. Fee calculated in Block assigned to the Node that mines it --- Models/Bitcoin/BlockCommit.py | 4 +++- Models/Bitcoin/Node.py | 10 +++++----- Models/Block.py | 3 ++- Models/Incentives.py | 18 ++++++------------ Models/Node.py | 4 +++- 5 files changed, 19 insertions(+), 20 deletions(-) diff --git a/Models/Bitcoin/BlockCommit.py b/Models/Bitcoin/BlockCommit.py index 6b4329f..bc76459 100644 --- a/Models/Bitcoin/BlockCommit.py +++ b/Models/Bitcoin/BlockCommit.py @@ -30,7 +30,9 @@ def generate_block (event): elif p.Ttechnique == "Full": blockTrans,blockSize = FT.execute_transactions(miner,eventTime) event.block.transactions = blockTrans - event.block.usedgas= blockSize + for tx in event.block.transactions: + event.block.fee += tx.fee + event.block.usedgas= blockSize # FIXME miner.blockchain.append(event.block) diff --git a/Models/Bitcoin/Node.py b/Models/Bitcoin/Node.py index 36a3d28..ed982a8 100644 --- a/Models/Bitcoin/Node.py +++ b/Models/Bitcoin/Node.py @@ -6,8 +6,8 @@ def __init__(self,id,hashPower): '''Initialize a new miner named name with hashrate measured in hashes per second.''' super().__init__(id)#,blockchain,transactionsPool,blocks,balance) self.hashPower = hashPower - self.blockchain= []# create an array for each miner to store chain state locally - self.transactionsPool= [] - self.blocks= 0# total number of blocks mined in the main chain - self.balance= 0# to count all reward that a miner made, including block rewards + uncle rewards + transactions fees - + # self.blockchain= []# create an array for each miner to store chain state locally + # self.transactionsPool= [] + # self.blocks= 0# total number of blocks mined in the main chain + # self.fee = 0 + # self.balance= 0# to count all reward that a miner made, including block rewards + uncle rewards + transactions fee diff --git a/Models/Block.py b/Models/Block.py index 84254eb..c62089a 100644 --- a/Models/Block.py +++ b/Models/Block.py @@ -1,5 +1,5 @@ class Block(object): - + """ Defines the base Block model. :param int depth: the index of the block in the local blockchain ledger (0 for genesis block) @@ -27,3 +27,4 @@ def __init__(self, self.miner = miner self.transactions = transactions or [] self.size = size + self.fee = 0 diff --git a/Models/Incentives.py b/Models/Incentives.py index 287eb09..eed586a 100644 --- a/Models/Incentives.py +++ b/Models/Incentives.py @@ -1,23 +1,17 @@ from InputsConfig import InputsConfig as p from Models.Consensus import Consensus as c + class Incentives: """ - Defines the rewarded elements (block + transactions), calculate and distribute the rewards among the participating nodes + Defines the rewarded elements (block + transactions), calculate and distribute the rewards among the participating nodes """ def distribute_rewards(): for bc in c.global_chain: for m in p.NODES: if bc.miner == m.id: - m.blocks +=1 - m.balance += p.Breward # increase the miner balance by the block reward - tx_fee= Incentives.transactions_fee(bc) - m.balance += tx_fee # add transaction fees to balance - - - def transactions_fee(bc): - fee=0 - for tx in bc.transactions: - fee += tx.fee - return fee + m.blocks += 1 + m.balance += p.Breward # increase the miner balance by the block reward + m.fee += bc.fee + m.balance += bc.fee # add transaction fees to balance diff --git a/Models/Node.py b/Models/Node.py index cba18af..cd349c7 100644 --- a/Models/Node.py +++ b/Models/Node.py @@ -15,6 +15,7 @@ def __init__(self,id): self.blockchain= [] self.transactionsPool= [] self.blocks= 0# + self.fee = 0 self.balance= 0 # Generate the Genesis block and append it to the local blockchain for all nodes @@ -31,11 +32,12 @@ def last_block(self): def blockchain_length(self): return len(self.blockchain)-1 - # reset the state of blockchains for all nodes in the network (before starting the next run) + # reset the state of blockchains for all nodes in the network (before starting the next run) def resetState(): from InputsConfig import InputsConfig as p for node in p.NODES: node.blockchain= [] # create an array for each miner to store chain state locally node.transactionsPool= [] node.blocks=0 # total number of blocks mined in the main chain + node.fee = 0 # total transaction fee recieved from mined block node.balance= 0 # to count all reward that a miner made From f1cf7c3bc227c6bef59f0f518ec4d19f5bb93a96 Mon Sep 17 00:00:00 2001 From: arman Date: Fri, 12 Mar 2021 12:08:58 +0530 Subject: [PATCH 04/26] Added Run ID, Node Fee, Block Fee, Profit in $ to results --- Statistics.py | 77 +++++++++++++++++++++++++++------------------------ 1 file changed, 41 insertions(+), 36 deletions(-) diff --git a/Statistics.py b/Statistics.py index 5a42fc0..f71abf3 100644 --- a/Statistics.py +++ b/Statistics.py @@ -16,17 +16,16 @@ class Statistics: staleRate=0 blockData=[] blocksResults=[] - profits= [[0 for x in range(7)] for y in range(p.Runs * len(p.NODES))] # rows number of miners * number of runs, columns =7 - index=0 + profits= [[] for y in range(p.Runs * len(p.NODES))] # rows number of miners * number of runs, columns =7 chain=[] - def calculate(): - Statistics.global_chain() # print the global chain - Statistics.blocks_results() # calcuate and print block statistics e.g., # of accepted blocks and stale rate etc - Statistics.profit_results() # calculate and distribute the revenue or reward for miners + def calculate(run_id): + Statistics.global_chain(run_id) # print the global chain + Statistics.blocks_results(run_id) # calcuate and print block statistics e.g., # of accepted blocks and stale rate etc + Statistics.profit_results(run_id) # calculate and distribute the revenue or reward for miners ########################################################### Calculate block statistics Results ########################################################################################### - def blocks_results(): + def blocks_results(run_id): trans = 0 Statistics.mainBlocks= len(c.global_chain)-1 @@ -38,36 +37,41 @@ def blocks_results(): Statistics.staleRate= round(Statistics.staleBlocks/Statistics.totalBlocks * 100, 2) if p.model==2: Statistics.uncleRate= round(Statistics.uncleBlocks/Statistics.totalBlocks * 100, 2) else: Statistics.uncleRate==0 - Statistics.blockData = [ Statistics.totalBlocks, Statistics.mainBlocks, Statistics.uncleBlocks, Statistics.uncleRate, Statistics.staleBlocks, Statistics.staleRate, trans] + Statistics.blockData = [run_id, Statistics.totalBlocks, Statistics.mainBlocks, Statistics.uncleBlocks, Statistics.uncleRate, Statistics.staleBlocks, Statistics.staleRate, trans] Statistics.blocksResults+=[Statistics.blockData] ########################################################### Calculate and distibute rewards among the miners ########################################################################################### - def profit_results(): + def profit_results(run_id): for m in p.NODES: - i = Statistics.index + m.id * p.Runs - Statistics.profits[i][0]= m.id - if p.model== 0: Statistics.profits[i][1]= "NA" - else: Statistics.profits[i][1]= m.hashPower - Statistics.profits[i][2]= m.blocks - Statistics.profits[i][3]= round(m.blocks/Statistics.mainBlocks * 100,2) + i = run_id * len(p.NODES) + m.id + Statistics.profits[i] += [run_id, m.id] + if p.model== 0: + Statistics.profits[i].append("NA") + else: + Statistics.profits[i].append(m.hashPower) + Statistics.profits[i].append(m.blocks) + Statistics.profits[i].append(round(m.blocks/Statistics.mainBlocks * 100, 2)) if p.model==2: - Statistics.profits[i][4]= m.uncles - Statistics.profits[i][5]= round((m.blocks + m.uncles)/(Statistics.mainBlocks + Statistics.totalUncles) * 100,2) - else: Statistics.profits[i][4]=0; Statistics.profits[i][5]=0 - Statistics.profits[i][6]= m.balance + Statistics.profits[i].append(m.uncles) + Statistics.profits[i].append(round((m.blocks + m.uncles)/(Statistics.mainBlocks + Statistics.totalUncles) * 100,2)) + else: + Statistics.profits[i].append(0) + Statistics.profits[i].append(0) + Statistics.profits[i].append(m.fee) + Statistics.profits[i].append(m.balance) + Statistics.profits[i].append(m.balance * p.Bprice) - Statistics.index+=1 ########################################################### prepare the global chain ########################################################################################### - def global_chain(): + def global_chain(run_id): if p.model==0 or p.model==1: for i in c.global_chain: - block= [i.depth, i.id, i.previous, i.timestamp, i.miner, len(i.transactions), i.size] + block= [run_id, i.depth, i.id, i.previous, i.timestamp, i.miner, len(i.transactions), i.fee, i.size] Statistics.chain +=[block] elif p.model==2: for i in c.global_chain: - block= [i.depth, i.id, i.previous, i.timestamp, i.miner, len(i.transactions), i.usedgas, len(i.uncles)] + block= [run_id, i.depth, i.id, i.previous, i.timestamp, i.miner, len(i.transactions), i.fee, i.usedgas, len(i.uncles)] Statistics.chain +=[block] ########################################################### Print simulation results to Excel ########################################################################################### @@ -77,21 +81,23 @@ def print_to_excel(fname): #data = {'Stale Rate': Results.staleRate,'Uncle Rate': Results.uncleRate ,'# Stale Blocks': Results.staleBlocks,'# Total Blocks': Results.totalBlocks, '# Included Blocks': Results.mainBlocks, '# Uncle Blocks': Results.uncleBlocks} df2= pd.DataFrame(Statistics.blocksResults) - df2.columns= ['Total Blocks', 'Main Blocks', 'Uncle blocks', 'Uncle Rate', 'Stale Blocks', 'Stale Rate', '# transactions'] + df2.columns= ['Run ID', 'Total Blocks', 'Main Blocks', 'Uncle blocks', 'Uncle Rate', 'Stale Blocks', 'Stale Rate', '# transactions'] df3 = pd.DataFrame(Statistics.profits) - df3.columns = ['Miner ID', '% Hash Power','# Mined Blocks', '% of main blocks','# Uncle Blocks','% of uncles', 'Profit (in ETH)'] + df3.columns = ['Run ID', 'Miner ID', '% Hash Power','# Mined Blocks', '% of main blocks', '# Uncle Blocks','% of uncles', 'Fee', 'Profit (in crypto)', 'Profit in $'] df4 = pd.DataFrame(Statistics.chain) #df4.columns= ['Block Depth', 'Block ID', 'Previous Block', 'Block Timestamp', 'Miner ID', '# transactions','Block Size'] - if p.model==2: df4.columns= ['Block Depth', 'Block ID', 'Previous Block', 'Block Timestamp', 'Miner ID', '# transactions','Block Limit', 'Uncle Blocks'] - else: df4.columns= ['Block Depth', 'Block ID', 'Previous Block', 'Block Timestamp', 'Miner ID', '# transactions', 'Block Size'] + if p.model==2: + df4.columns= ['Run ID', 'Block Depth', 'Block ID', 'Previous Block', 'Block Timestamp', 'Miner ID', '# transactions', 'Fee', 'Block Limit', 'Uncle Blocks'] + else: + df4.columns= ['Run ID', 'Block Depth', 'Block ID', 'Previous Block', 'Block Timestamp', 'Miner ID', '# transactions', 'Fee', 'Block Size'] writer = pd.ExcelWriter(fname, engine='xlsxwriter') - df1.to_excel(writer, sheet_name='InputConfig') - df2.to_excel(writer, sheet_name='SimOutput') - df3.to_excel(writer, sheet_name='Profit') - df4.to_excel(writer,sheet_name='Chain') + df1.to_excel(writer, sheet_name='InputConfig', startcol=-1) + df2.to_excel(writer, sheet_name='SimOutput', startcol=-1) + df3.to_excel(writer, sheet_name='Profit', startcol=-1) + df4.to_excel(writer, sheet_name='Chain', startcol=-1) writer.save() @@ -106,8 +112,7 @@ def reset(): Statistics.staleRate=0 Statistics.blockData=[] - def reset2(): - Statistics.blocksResults=[] - Statistics.profits= [[0 for x in range(7)] for y in range(p.Runs * len(p.NODES))] # rows number of miners * number of runs, columns =7 - Statistics.index=0 - Statistics.chain=[] + # def reset2(): + # Statistics.blocksResults=[] + # Statistics.profits= [[] for y in range(p.Runs * len(p.NODES))] # rows number of miners * number of runs, columns =7 + # Statistics.chain=[] From e049d70da1460d577e82c291eaf605848ea283a0 Mon Sep 17 00:00:00 2001 From: arman Date: Fri, 12 Mar 2021 12:10:13 +0530 Subject: [PATCH 05/26] Updated Bitcoin reward to 6.25 and added Bprice to track dollar profits --- InputsConfig.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/InputsConfig.py b/InputsConfig.py index f07205b..751f38b 100644 --- a/InputsConfig.py +++ b/InputsConfig.py @@ -7,7 +7,7 @@ class InputsConfig: 2 : Ethereum model 3 : AppendableBlock model """ - model = 3 + model = 1 ''' Input configurations for the base model ''' if model == 0: @@ -44,7 +44,8 @@ class InputsConfig: Binterval = 600 # Average time (in seconds)for creating a block in the blockchain Bsize = 1.0 # The block size in MB Bdelay = 0.42 # average block propogation delay in seconds, #Ref: https://bitslog.wordpress.com/2016/04/28/uncle-mining-an-ethereum-consensus-protocol-flaw/ - Breward = 12.5 # Reward for mining a block + Breward = 6.25 # Reward for mining a block + Bprice = 54000 ''' Transaction Parameters ''' hasTrans = True # True/False to enable/disable transactions in the simulator From ee93d8463b6f6c03fd0881367ebe1a5787654fba Mon Sep 17 00:00:00 2001 From: arman Date: Sat, 13 Mar 2021 00:52:08 +0530 Subject: [PATCH 06/26] Added mining pools and pool mining strategies --- InputsConfig.py | 29 ++++++++++++++++++++++------ Models/Bitcoin/Node.py | 5 +++-- Models/Incentives.py | 44 +++++++++++++++++++++++++++++++++++++++--- Models/Node.py | 3 ++- Statistics.py | 5 ++--- 5 files changed, 71 insertions(+), 15 deletions(-) diff --git a/InputsConfig.py b/InputsConfig.py index 751f38b..4643124 100644 --- a/InputsConfig.py +++ b/InputsConfig.py @@ -5,7 +5,7 @@ class InputsConfig: 0 : The base model 1 : Bitcoin model 2 : Ethereum model - 3 : AppendableBlock model + 3 : AppendableBlock model """ model = 1 @@ -58,15 +58,32 @@ class InputsConfig: ''' Node Parameters ''' Nn = 3 # the total number of nodes in the network - NODES = [] from Models.Bitcoin.Node import Node + from Models.Bitcoin.Pool import Pool + + POOLS = [Pool(_id=0, strategy='PPS', fee=3), Pool(_id=1, strategy='FPPS', fee=1), Pool(_id=2, strategy='PPS+', fee=1)] + # here as an example we define three nodes by assigning a unique id for each one + % of hash (computing) power - NODES = [Node(id=0, hashPower=50), Node( - id=1, hashPower=20), Node(id=2, hashPower=30)] + NODES = [ + Node(id=0, pool=POOLS[0], hashPower=25), + Node(id=1, pool=POOLS[1], hashPower=10), + Node(id=2, pool=POOLS[2], hashPower=15), + Node(id=3, pool=POOLS[0], hashPower=25), + Node(id=4, pool=POOLS[1], hashPower=10), + Node(id=5, pool=POOLS[2], hashPower=10), + Node(id=6, hashPower=5) + ] + + for pool in POOLS: + for node in NODES: + if node.pool == pool: + pool.nodes.append(node) + pool.hashPower += node.hashPower + # print(pool.__dict__) ''' Simulation Parameters ''' simTime = 10000 # the simulation length (in seconds) - Runs = 2 # Number of simulation runs + Runs = 3 # Number of simulation runs ''' Input configurations for Ethereum model ''' if model == 2: @@ -108,7 +125,7 @@ class InputsConfig: simTime = 500 # the simulation length (in seconds) Runs = 2 # Number of simulation runs - ''' Input configurations for AppendableBlock model ''' + ''' Input configurations for AppendableBlock model ''' if model == 3: ''' Transaction Parameters ''' hasTrans = True # True/False to enable/disable transactions in the simulator diff --git a/Models/Bitcoin/Node.py b/Models/Bitcoin/Node.py index ed982a8..d278f77 100644 --- a/Models/Bitcoin/Node.py +++ b/Models/Bitcoin/Node.py @@ -1,10 +1,11 @@ from Models.Block import Block from Models.Node import Node as BaseNode + class Node(BaseNode): - def __init__(self,id,hashPower): + def __init__(self, id, hashPower, pool=None): '''Initialize a new miner named name with hashrate measured in hashes per second.''' - super().__init__(id)#,blockchain,transactionsPool,blocks,balance) + super().__init__(id, pool) # ,blockchain,transactionsPool,blocks,balance) self.hashPower = hashPower # self.blockchain= []# create an array for each miner to store chain state locally # self.transactionsPool= [] diff --git a/Models/Incentives.py b/Models/Incentives.py index eed586a..84473b4 100644 --- a/Models/Incentives.py +++ b/Models/Incentives.py @@ -10,8 +10,46 @@ class Incentives: def distribute_rewards(): for bc in c.global_chain: for m in p.NODES: + if bc.miner == m.id: m.blocks += 1 - m.balance += p.Breward # increase the miner balance by the block reward - m.fee += bc.fee - m.balance += bc.fee # add transaction fees to balance + + if not m.pool: + if bc.miner == m.id: + m.balance += p.Breward # increase the miner balance by the block reward + m.fee += bc.fee + m.balance += bc.fee # add transaction fees to balance + + elif m.pool.strategy == 'PPS': + + reward = m.hashPower/100 * p.Breward + + m.balance += (100-m.pool.fee)/100 * reward + m.pool.balance += m.pool.fee/100 * reward + + if bc.miner == m.id: + m.pool.fee += bc.fee + m.pool.balance += bc.fee + + elif m.pool.strategy == 'FPPS': + + if bc.miner == m.id: + reward = p.Breward + m.pool.fee += m.pool.fee/100 * reward + reward = (100 - m.pool.fee)/100 * reward + + for node in m.pool.nodes: + node_fee = node.hashPower/m.pool.hashPower * bc.fee + node.fee += node_fee + node.balance += node.hashPower/m.pool.hashPower * reward + node.balance += node_fee + + elif m.pool.strategy == 'PPS+': + reward = m.hashPower/100 * p.Breward + + m.balance += (100-m.pool.fee)/100 * reward + m.pool.balance += m.pool.fee/100 * reward + + if bc.miner == m.id: + for node in m.pool.nodes: + node.fee += node.hashPower/m.pool.hashPower * bc.fee diff --git a/Models/Node.py b/Models/Node.py index cd349c7..b67d948 100644 --- a/Models/Node.py +++ b/Models/Node.py @@ -10,13 +10,14 @@ class Node(object): :param int blocks: the total number of blocks mined in the main chain :param int balance: the amount of cryptocurrencies a node has """ - def __init__(self,id): + def __init__(self, id, pool): self.id= id self.blockchain= [] self.transactionsPool= [] self.blocks= 0# self.fee = 0 self.balance= 0 + self.pool = pool # Generate the Genesis block and append it to the local blockchain for all nodes def generate_gensis_block(): diff --git a/Statistics.py b/Statistics.py index f71abf3..25846e3 100644 --- a/Statistics.py +++ b/Statistics.py @@ -45,7 +45,7 @@ def profit_results(run_id): for m in p.NODES: i = run_id * len(p.NODES) + m.id - Statistics.profits[i] += [run_id, m.id] + Statistics.profits[i] += [run_id, m.id, m.pool.strategy if m.pool else 'SOLO'] if p.model== 0: Statistics.profits[i].append("NA") else: @@ -84,10 +84,9 @@ def print_to_excel(fname): df2.columns= ['Run ID', 'Total Blocks', 'Main Blocks', 'Uncle blocks', 'Uncle Rate', 'Stale Blocks', 'Stale Rate', '# transactions'] df3 = pd.DataFrame(Statistics.profits) - df3.columns = ['Run ID', 'Miner ID', '% Hash Power','# Mined Blocks', '% of main blocks', '# Uncle Blocks','% of uncles', 'Fee', 'Profit (in crypto)', 'Profit in $'] + df3.columns = ['Run ID', 'Miner ID', 'Pool Strategy', '% Hash Power','# Mined Blocks', '% of main blocks', '# Uncle Blocks','% of uncles', 'Fee', 'Profit (in crypto)', 'Profit in $'] df4 = pd.DataFrame(Statistics.chain) - #df4.columns= ['Block Depth', 'Block ID', 'Previous Block', 'Block Timestamp', 'Miner ID', '# transactions','Block Size'] if p.model==2: df4.columns= ['Run ID', 'Block Depth', 'Block ID', 'Previous Block', 'Block Timestamp', 'Miner ID', '# transactions', 'Fee', 'Block Limit', 'Uncle Blocks'] else: From 4a53fbcc8afd51adca5f46321e8badce4c0d5934 Mon Sep 17 00:00:00 2001 From: arman Date: Sat, 13 Mar 2021 16:00:11 +0530 Subject: [PATCH 07/26] Missed adding Pools in the previos commit, fixed fee bug --- Models/Bitcoin/Pool.py | 10 ++++++++++ Models/Incentives.py | 3 +-- 2 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 Models/Bitcoin/Pool.py diff --git a/Models/Bitcoin/Pool.py b/Models/Bitcoin/Pool.py new file mode 100644 index 0000000..92c8624 --- /dev/null +++ b/Models/Bitcoin/Pool.py @@ -0,0 +1,10 @@ +class Pool(): + + def __init__(self, _id, strategy, fee): + self.id = _id + self.strategy = strategy + self.blocks= 0# + self.fee = fee + self.nodes = [] + self.hashPower = 0 + self.balance = 0 diff --git a/Models/Incentives.py b/Models/Incentives.py index 84473b4..f3269df 100644 --- a/Models/Incentives.py +++ b/Models/Incentives.py @@ -28,14 +28,13 @@ def distribute_rewards(): m.pool.balance += m.pool.fee/100 * reward if bc.miner == m.id: - m.pool.fee += bc.fee m.pool.balance += bc.fee elif m.pool.strategy == 'FPPS': if bc.miner == m.id: reward = p.Breward - m.pool.fee += m.pool.fee/100 * reward + m.pool.balance += m.pool.fee/100 * reward reward = (100 - m.pool.fee)/100 * reward for node in m.pool.nodes: From e7020c435ded1be182f51d9e3b380e58411feb8c Mon Sep 17 00:00:00 2001 From: arman Date: Sat, 13 Mar 2021 16:50:58 +0530 Subject: [PATCH 08/26] Tracking rewards better and added comments in Incentives, resetting Pool state after each run --- InputsConfig.py | 2 +- Main.py | 2 ++ Models/Bitcoin/Pool.py | 14 +++++++++++--- Models/Incentives.py | 42 ++++++++++++++++++++++++++++++------------ 4 files changed, 44 insertions(+), 16 deletions(-) diff --git a/InputsConfig.py b/InputsConfig.py index 4643124..08e5575 100644 --- a/InputsConfig.py +++ b/InputsConfig.py @@ -61,7 +61,7 @@ class InputsConfig: from Models.Bitcoin.Node import Node from Models.Bitcoin.Pool import Pool - POOLS = [Pool(_id=0, strategy='PPS', fee=3), Pool(_id=1, strategy='FPPS', fee=1), Pool(_id=2, strategy='PPS+', fee=1)] + POOLS = [Pool(_id=0, strategy='PPS', fee_rate=3), Pool(_id=1, strategy='FPPS', fee_rate=1), Pool(_id=2, strategy='PPS+', fee_rate=1)] # here as an example we define three nodes by assigning a unique id for each one + % of hash (computing) power NODES = [ diff --git a/Main.py b/Main.py index 0951b9e..53fc897 100644 --- a/Main.py +++ b/Main.py @@ -25,6 +25,7 @@ from Models.Bitcoin.Consensus import Consensus from Models.Transaction import LightTransaction as LT, FullTransaction as FT from Models.Bitcoin.Node import Node + from Models.Bitcoin.Pool import Pool from Models.Incentives import Incentives elif p.model == 0: @@ -77,6 +78,7 @@ def main(): ########## reset all global variable before the next run ############# Statistics.reset() # reset all variables used to calculate the results Node.resetState() # reset all the states (blockchains) for all nodes in the network + Pool.resetState() # reset all pools in the network fname = f"(Allverify)1day_{p.Bsize/1000000}M_{p.Tn/1000}K-{i}-{datetime.now()}.xlsx" # print all the simulation results in an excel file diff --git a/Models/Bitcoin/Pool.py b/Models/Bitcoin/Pool.py index 92c8624..e02e2a7 100644 --- a/Models/Bitcoin/Pool.py +++ b/Models/Bitcoin/Pool.py @@ -1,10 +1,18 @@ class Pool(): - def __init__(self, _id, strategy, fee): + def __init__(self, _id, strategy, fee_rate): self.id = _id self.strategy = strategy - self.blocks= 0# - self.fee = fee + self.fee_rate = fee_rate self.nodes = [] self.hashPower = 0 + self.blocks = 0 + self.block_fee = 0 self.balance = 0 + + def resetState(): + from InputsConfig import InputsConfig as p + for pool in p.POOLS: + pool.blocks = 0 # total number of blocks mined in the main chain + pool.block_fee = 0 # total transaction fee recieved from mined block + pool.balance = 0 # to count all reward that a miner made \ No newline at end of file diff --git a/Models/Incentives.py b/Models/Incentives.py index f3269df..9401452 100644 --- a/Models/Incentives.py +++ b/Models/Incentives.py @@ -11,11 +11,9 @@ def distribute_rewards(): for bc in c.global_chain: for m in p.NODES: - if bc.miner == m.id: - m.blocks += 1 - if not m.pool: if bc.miner == m.id: + m.blocks += 1 m.balance += p.Breward # increase the miner balance by the block reward m.fee += bc.fee m.balance += bc.fee # add transaction fees to balance @@ -23,32 +21,52 @@ def distribute_rewards(): elif m.pool.strategy == 'PPS': reward = m.hashPower/100 * p.Breward - - m.balance += (100-m.pool.fee)/100 * reward - m.pool.balance += m.pool.fee/100 * reward + # constant payout after deducting pool fee + m.balance -= m.pool.fee_rate/100 * reward + m.pool.balance += m.pool.fee_rate/100 * reward + m.pool.balance -= (100 - m.pool.fee_rate)/100 * reward + m.balance += (100 - m.pool.fee_rate)/100 * reward if bc.miner == m.id: + m.blocks += 1 + m.pool.blocks += 1 + # pool keeps block reward and transaction fee + m.pool.balance += p.Breward + m.pool.block_fee += bc.fee m.pool.balance += bc.fee elif m.pool.strategy == 'FPPS': if bc.miner == m.id: - reward = p.Breward - m.pool.balance += m.pool.fee/100 * reward - reward = (100 - m.pool.fee)/100 * reward + m.blocks += 1 + m.pool.blocks += 1 + # deducting pool fee + reward = (100 - m.pool.fee_rate)/100 * p.Breward + m.pool.balance += m.pool.fee_rate/100 * p.Breward + # all nodes share block reward and transaction fee for node in m.pool.nodes: + node.balance += node.hashPower/m.pool.hashPower * reward node_fee = node.hashPower/m.pool.hashPower * bc.fee node.fee += node_fee - node.balance += node.hashPower/m.pool.hashPower * reward node.balance += node_fee elif m.pool.strategy == 'PPS+': + reward = m.hashPower/100 * p.Breward - m.balance += (100-m.pool.fee)/100 * reward - m.pool.balance += m.pool.fee/100 * reward + # constant payout after deducting pool fee + m.balance -= m.pool.fee_rate/100 * reward + m.pool.balance += m.pool.fee_rate/100 * reward + m.pool.balance -= (100 - m.pool.fee_rate)/100 * reward + m.balance += (100 - m.pool.fee_rate)/100 * reward + # transaction fee shared by all nodes if bc.miner == m.id: + m.blocks += 1 + m.pool.blocks += 1 + # pool keeps block reward while the transaction fee is shared + m.pool.balance += p.Breward for node in m.pool.nodes: node.fee += node.hashPower/m.pool.hashPower * bc.fee + node.balance += node.hashPower/m.pool.hashPower * bc.fee From 23e21afaaf02e5bfa984a28c34a4058827746992 Mon Sep 17 00:00:00 2001 From: arman Date: Sat, 13 Mar 2021 16:52:38 +0530 Subject: [PATCH 09/26] Updated excel generation to include Pool results --- Statistics.py | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/Statistics.py b/Statistics.py index 25846e3..0ab0bec 100644 --- a/Statistics.py +++ b/Statistics.py @@ -16,13 +16,15 @@ class Statistics: staleRate=0 blockData=[] blocksResults=[] - profits= [[] for y in range(p.Runs * len(p.NODES))] # rows number of miners * number of runs, columns =7 + profits = [[] for y in range(p.Runs * len(p.NODES))] # number of miners * number of runs + pool_profits = [] chain=[] def calculate(run_id): - Statistics.global_chain(run_id) # print the global chain - Statistics.blocks_results(run_id) # calcuate and print block statistics e.g., # of accepted blocks and stale rate etc - Statistics.profit_results(run_id) # calculate and distribute the revenue or reward for miners + Statistics.global_chain(run_id) # print the global chain + Statistics.blocks_results(run_id) # calcuate and print block statistics e.g., # of accepted blocks and stale rate etc + Statistics.profit_results(run_id) # calculate and distribute the revenue or reward for miners + Statistics.pool_results(run_id) ########################################################### Calculate block statistics Results ########################################################################################### def blocks_results(run_id): @@ -45,7 +47,7 @@ def profit_results(run_id): for m in p.NODES: i = run_id * len(p.NODES) + m.id - Statistics.profits[i] += [run_id, m.id, m.pool.strategy if m.pool else 'SOLO'] + Statistics.profits[i] = [run_id, m.id, m.pool.strategy if m.pool else 'SOLO'] if p.model== 0: Statistics.profits[i].append("NA") else: @@ -63,6 +65,12 @@ def profit_results(run_id): Statistics.profits[i].append(m.balance * p.Bprice) + def pool_results(run_id): + for pool in p.POOLS: + Statistics.pool_profits.append([run_id, pool.id, pool.strategy, pool.fee_rate, pool.hashPower, pool.blocks, + round(pool.blocks/Statistics.mainBlocks * 100, 2), pool.block_fee, pool.balance, pool.balance * p.Bprice]) + + ########################################################### prepare the global chain ########################################################################################### def global_chain(run_id): if p.model==0 or p.model==1: @@ -84,19 +92,25 @@ def print_to_excel(fname): df2.columns= ['Run ID', 'Total Blocks', 'Main Blocks', 'Uncle blocks', 'Uncle Rate', 'Stale Blocks', 'Stale Rate', '# transactions'] df3 = pd.DataFrame(Statistics.profits) - df3.columns = ['Run ID', 'Miner ID', 'Pool Strategy', '% Hash Power','# Mined Blocks', '% of main blocks', '# Uncle Blocks','% of uncles', 'Fee', 'Profit (in crypto)', 'Profit in $'] + df3.columns = ['Run ID', 'Miner ID', 'Pool Strategy', '% Hash Power','# Mined Blocks', '% of main blocks', '# Uncle Blocks','% of uncles', 'Transaction Fee', 'Profit (in crypto)', 'Profit in $'] + + df4 = pd.DataFrame(Statistics.pool_profits) + df4.columns = ['Run ID', 'Pool ID', 'Pool Strategy', '% Fee Rate', '% Hash Power', '# Mined Blocks', '% of main blocks', 'Transaction Fee', 'Profit (in crypto)', 'Profit in $'] - df4 = pd.DataFrame(Statistics.chain) + df5 = pd.DataFrame(Statistics.chain) if p.model==2: - df4.columns= ['Run ID', 'Block Depth', 'Block ID', 'Previous Block', 'Block Timestamp', 'Miner ID', '# transactions', 'Fee', 'Block Limit', 'Uncle Blocks'] + df5.columns= ['Run ID', 'Block Depth', 'Block ID', 'Previous Block', 'Block Timestamp', 'Miner ID', '# transactions', 'Transaction Fee', 'Block Limit', 'Uncle Blocks'] else: - df4.columns= ['Run ID', 'Block Depth', 'Block ID', 'Previous Block', 'Block Timestamp', 'Miner ID', '# transactions', 'Fee', 'Block Size'] + df5.columns= ['Run ID', 'Block Depth', 'Block ID', 'Previous Block', 'Block Timestamp', 'Miner ID', '# transactions', 'Transaction Fee', 'Block Size'] + + writer = pd.ExcelWriter(fname, engine='xlsxwriter') df1.to_excel(writer, sheet_name='InputConfig', startcol=-1) df2.to_excel(writer, sheet_name='SimOutput', startcol=-1) df3.to_excel(writer, sheet_name='Profit', startcol=-1) - df4.to_excel(writer, sheet_name='Chain', startcol=-1) + df4.to_excel(writer, sheet_name='Pools', startcol=-1) + df5.to_excel(writer, sheet_name='Chain', startcol=-1) writer.save() From 2728efe8b26ebd6c65ac5d485d5d22ab47cb98da Mon Sep 17 00:00:00 2001 From: arman Date: Thu, 18 Mar 2021 20:02:41 +0530 Subject: [PATCH 10/26] Work in progress for implementing PPLNS --- InputsConfig.py | 18 +++++++++++------- Models/Bitcoin/Node.py | 5 ++++- Models/Incentives.py | 15 ++++++--------- Scheduler.py | 31 +++++++++++++++++++++++++++++-- 4 files changed, 50 insertions(+), 19 deletions(-) diff --git a/InputsConfig.py b/InputsConfig.py index 08e5575..760de16 100644 --- a/InputsConfig.py +++ b/InputsConfig.py @@ -46,6 +46,7 @@ class InputsConfig: Bdelay = 0.42 # average block propogation delay in seconds, #Ref: https://bitslog.wordpress.com/2016/04/28/uncle-mining-an-ethereum-consensus-protocol-flaw/ Breward = 6.25 # Reward for mining a block Bprice = 54000 + jump_threshold = 0.02 ''' Transaction Parameters ''' hasTrans = True # True/False to enable/disable transactions in the simulator @@ -61,7 +62,12 @@ class InputsConfig: from Models.Bitcoin.Node import Node from Models.Bitcoin.Pool import Pool - POOLS = [Pool(_id=0, strategy='PPS', fee_rate=3), Pool(_id=1, strategy='FPPS', fee_rate=1), Pool(_id=2, strategy='PPS+', fee_rate=1)] + # Creating the mining pools + POOLS = [ + Pool(_id=0, strategy='PPS', fee_rate=3), + Pool(_id=1, strategy='FPPS', fee_rate=1), + Pool(_id=2, strategy='PPS+', fee_rate=1) + ] # here as an example we define three nodes by assigning a unique id for each one + % of hash (computing) power NODES = [ @@ -74,12 +80,10 @@ class InputsConfig: Node(id=6, hashPower=5) ] - for pool in POOLS: - for node in NODES: - if node.pool == pool: - pool.nodes.append(node) - pool.hashPower += node.hashPower - # print(pool.__dict__) + # Giving every pool a reference to the nodes it contains. Also, update the total hashrate of a pool. + for node in NODES: + node.pool.nodes.append(node) + node.pool.hashPower += node.hashPower ''' Simulation Parameters ''' simTime = 10000 # the simulation length (in seconds) diff --git a/Models/Bitcoin/Node.py b/Models/Bitcoin/Node.py index d278f77..36ae5e4 100644 --- a/Models/Bitcoin/Node.py +++ b/Models/Bitcoin/Node.py @@ -3,10 +3,13 @@ class Node(BaseNode): - def __init__(self, id, hashPower, pool=None): + def __init__(self, id, hashPower, pool=None, joinTime=0, node_type='honest', node_strategy=None): '''Initialize a new miner named name with hashrate measured in hashes per second.''' super().__init__(id, pool) # ,blockchain,transactionsPool,blocks,balance) self.hashPower = hashPower + self.joinTime = joinTime + self.node_type = node_type + self.node_strategy = node_strategy # random, sequential, strategy_based # self.blockchain= []# create an array for each miner to store chain state locally # self.transactionsPool= [] # self.blocks= 0# total number of blocks mined in the main chain diff --git a/Models/Incentives.py b/Models/Incentives.py index 9401452..3481bd2 100644 --- a/Models/Incentives.py +++ b/Models/Incentives.py @@ -22,10 +22,9 @@ def distribute_rewards(): reward = m.hashPower/100 * p.Breward # constant payout after deducting pool fee - m.balance -= m.pool.fee_rate/100 * reward - m.pool.balance += m.pool.fee_rate/100 * reward - m.pool.balance -= (100 - m.pool.fee_rate)/100 * reward - m.balance += (100 - m.pool.fee_rate)/100 * reward + reward = (100 - m.pool.fee_rate)/100 * reward + m.pool.balance -= reward + m.balance += reward if bc.miner == m.id: m.blocks += 1 @@ -54,12 +53,10 @@ def distribute_rewards(): elif m.pool.strategy == 'PPS+': reward = m.hashPower/100 * p.Breward - + reward = (100 - m.pool.fee_rate)/100 * reward # constant payout after deducting pool fee - m.balance -= m.pool.fee_rate/100 * reward - m.pool.balance += m.pool.fee_rate/100 * reward - m.pool.balance -= (100 - m.pool.fee_rate)/100 * reward - m.balance += (100 - m.pool.fee_rate)/100 * reward + m.pool.balance -= reward + m.balance += reward # transaction fee shared by all nodes if bc.miner == m.id: diff --git a/Scheduler.py b/Scheduler.py index 5d05db2..e8938a4 100644 --- a/Scheduler.py +++ b/Scheduler.py @@ -1,8 +1,7 @@ from InputsConfig import InputsConfig as p import random -from Models.Block import Block from Event import Event, Queue - +from Node import Node if p.model == 2: from Models.Ethereum.Block import Block elif p.model == 3: @@ -70,3 +69,31 @@ def receive_tx_list_event(txList, gatewayId, tokenTime, eventTime): block.timestamp = tokenTime event = Event(eventType, gatewayId, eventTime, block) Queue.add_event(event) + + + + def create_change_pool_event(node, eventTime): + eventType = "change_pool" + if eventTime <= p.simTime: + node.joinTime = eventTime + if node.node_strategy == "random": + while True: + temp_pool = node.pool + choosePool = random.randint(0, len(p.POOLS)) + node.pool = p.POOLS[choosePool] + Queue.add_event(Event(eventType, node, eventTime)) + if node.pool != temp_pool: + break + + + elif node.node_strategy == "sequential": + pool_index = p.POOLS.index(node.pool) + pool_index = (pool_index + 1)%len(p.POOLS) + node.pool = p.POOLS[pool_index] + Queue.add_event(Event(eventType, node, eventTime)) + + + # elif node.node_strategy == 'strategy_based': + # pool_index = p.POOLS.index(node.pool) + + From 37f88e07767759e2e817f55dea10ba6d0b6f13d3 Mon Sep 17 00:00:00 2001 From: arman Date: Thu, 18 Mar 2021 20:03:04 +0530 Subject: [PATCH 11/26] Fixed filename issue when saving spreadsheet --- Main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Main.py b/Main.py index 53fc897..32618cd 100644 --- a/Main.py +++ b/Main.py @@ -80,7 +80,7 @@ def main(): Node.resetState() # reset all the states (blockchains) for all nodes in the network Pool.resetState() # reset all pools in the network - fname = f"(Allverify)1day_{p.Bsize/1000000}M_{p.Tn/1000}K-{i}-{datetime.now()}.xlsx" + fname = f"(Allverify)1day_{p.Bsize/1000000}M_{p.Tn/1000}K-{i}-{datetime.now()}.xlsx".replace(':', '_') # print all the simulation results in an excel file Statistics.print_to_excel(fname) # Statistics.reset2() # reset profit results From aefabfc04f7afe4a736bae231be1a3a695cd5308 Mon Sep 17 00:00:00 2001 From: Marius Date: Thu, 18 Mar 2021 20:26:51 +0200 Subject: [PATCH 12/26] Add gitignore --- .gitignore | 152 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..57feb1a --- /dev/null +++ b/.gitignore @@ -0,0 +1,152 @@ + +# Created by https://www.toptal.com/developers/gitignore/api/python +# Edit at https://www.toptal.com/developers/gitignore?templates=python + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +pytestdebug.log + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ +doc/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +#poetry.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +# .env +.env/ +.venv/ +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ +pythonenv* + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# operating system-related files +*.DS_Store #file properties cache/storage on macOS +Thumbs.db #thumbnail cache on Windows + +# profiling data +.prof + + +# End of https://www.toptal.com/developers/gitignore/api/python From 7625c047ef5c46d91aaee5647d697c74c7f15240 Mon Sep 17 00:00:00 2001 From: Marius Date: Thu, 18 Mar 2021 20:29:34 +0200 Subject: [PATCH 13/26] Fix incorrect reference assigning for nodes not in a pool --- InputsConfig.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/InputsConfig.py b/InputsConfig.py index 760de16..ac8d6b9 100644 --- a/InputsConfig.py +++ b/InputsConfig.py @@ -82,8 +82,9 @@ class InputsConfig: # Giving every pool a reference to the nodes it contains. Also, update the total hashrate of a pool. for node in NODES: - node.pool.nodes.append(node) - node.pool.hashPower += node.hashPower + if node.pool is not None: + node.pool.nodes.append(node) + node.pool.hashPower += node.hashPower ''' Simulation Parameters ''' simTime = 10000 # the simulation length (in seconds) From 03f214c00eaedb034ff1d15ee768ec210f8e0394 Mon Sep 17 00:00:00 2001 From: MishZ Date: Sun, 21 Mar 2021 01:26:08 +0500 Subject: [PATCH 14/26] PPLNS implemented --- InputsConfig.py | 15 ++++++--- Models/Bitcoin/Pool.py | 3 +- Models/Incentives.py | 74 +++++++++++++++++++++++++++++++++++++++++- Scheduler.py | 29 ----------------- 4 files changed, 85 insertions(+), 36 deletions(-) diff --git a/InputsConfig.py b/InputsConfig.py index ac8d6b9..a03755e 100644 --- a/InputsConfig.py +++ b/InputsConfig.py @@ -66,18 +66,23 @@ class InputsConfig: POOLS = [ Pool(_id=0, strategy='PPS', fee_rate=3), Pool(_id=1, strategy='FPPS', fee_rate=1), - Pool(_id=2, strategy='PPS+', fee_rate=1) + Pool(_id=2, strategy='PPS+', fee_rate=1), + Pool(_id=3, strategy='PPLNS', fee_rate=1, block_window=8), + Pool(_id=4, strategy='PPLNS', fee_rate=2, block_window=6), ] # here as an example we define three nodes by assigning a unique id for each one + % of hash (computing) power NODES = [ Node(id=0, pool=POOLS[0], hashPower=25), - Node(id=1, pool=POOLS[1], hashPower=10), + Node(id=1, pool=POOLS[1], hashPower=5), Node(id=2, pool=POOLS[2], hashPower=15), - Node(id=3, pool=POOLS[0], hashPower=25), + Node(id=3, pool=POOLS[3], hashPower=15), Node(id=4, pool=POOLS[1], hashPower=10), - Node(id=5, pool=POOLS[2], hashPower=10), - Node(id=6, hashPower=5) + Node(id=5, pool=POOLS[4], hashPower=10), + Node(id=6, pool=POOLS[4], hashPower=5), + Node(id=7, pool=POOLS[3], hashPower=5), + Node(id=8, pool=POOLS[3], hashPower=5), + Node(id=9, hashPower=5) ] # Giving every pool a reference to the nodes it contains. Also, update the total hashrate of a pool. diff --git a/Models/Bitcoin/Pool.py b/Models/Bitcoin/Pool.py index e02e2a7..a2b02d2 100644 --- a/Models/Bitcoin/Pool.py +++ b/Models/Bitcoin/Pool.py @@ -1,6 +1,6 @@ class Pool(): - def __init__(self, _id, strategy, fee_rate): + def __init__(self, _id, strategy, fee_rate, block_window=None): self.id = _id self.strategy = strategy self.fee_rate = fee_rate @@ -9,6 +9,7 @@ def __init__(self, _id, strategy, fee_rate): self.blocks = 0 self.block_fee = 0 self.balance = 0 + self.block_window = block_window def resetState(): from InputsConfig import InputsConfig as p diff --git a/Models/Incentives.py b/Models/Incentives.py index 3481bd2..31286c2 100644 --- a/Models/Incentives.py +++ b/Models/Incentives.py @@ -1,3 +1,4 @@ +import random from InputsConfig import InputsConfig as p from Models.Consensus import Consensus as c @@ -8,7 +9,29 @@ class Incentives: Defines the rewarded elements (block + transactions), calculate and distribute the rewards among the participating nodes """ def distribute_rewards(): - for bc in c.global_chain: + for i, bc in enumerate(c.global_chain): + + current_time = bc.timestamp + + cumulative_times = {} + for pool in p.POOLS: + if pool.strategy != 'PPLNS': + continue + + N = pool.block_window + if i > N: + window_timestamp = c.global_chain[i-N-1].timestamp + else: + window_timestamp = 0 + + resource = 0 + for pool_node in pool.nodes: + minimum_window_time = min(current_time - pool_node.joinTime, current_time - window_timestamp) + resource += minimum_window_time * pool_node.hashPower + + cumulative_times[pool] = resource + + for m in p.NODES: if not m.pool: @@ -34,6 +57,32 @@ def distribute_rewards(): m.pool.block_fee += bc.fee m.pool.balance += bc.fee + elif m.pool.strategy == 'PPLNS': + + if bc.miner == m.id: + m.blocks += 1 + m.pool.blocks += 1 + + reward = (100 - m.pool.fee_rate)/100 * p.Breward + m.pool.balance += m.pool.fee_rate/100 * p.Breward + + print('check') + for node in m.pool.nodes: + N = m.pool.block_window + if i > N: + window_timestamp = c.global_chain[i-N-1].timestamp + else: + window_timestamp = 0 + + minimum_window_time = min(current_time - node.joinTime, current_time - window_timestamp) + + frac = (minimum_window_time * node.hashPower)/cumulative_times[m.pool] + print(frac) + node.balance += frac * reward + node.fee += frac * bc.fee + node.balance += frac * bc.fee + + elif m.pool.strategy == 'FPPS': if bc.miner == m.id: @@ -67,3 +116,26 @@ def distribute_rewards(): for node in m.pool.nodes: node.fee += node.hashPower/m.pool.hashPower * bc.fee node.balance += node.hashPower/m.pool.hashPower * bc.fee + + + + for node in p.NODES: + if node.node_type == 'selfish': + if random.random() < jump_threshold: + + node.joinTime = current_time # TODO improve time assignment + + if node.node_strategy == "random": + while True: + temp_pool = node.pool + choosePool = random.randint(0, len(p.POOLS)) + node.pool = p.POOLS[choosePool] + if node.pool != temp_pool: + break + + elif node.node_strategy == "sequential": + pool_index = p.POOLS.index(node.pool) + pool_index = (pool_index + 1)%len(p.POOLS) + node.pool = p.POOLS[pool_index] + + # elif node.node_strategy == 'strategy_based' \ No newline at end of file diff --git a/Scheduler.py b/Scheduler.py index e8938a4..13823f0 100644 --- a/Scheduler.py +++ b/Scheduler.py @@ -1,7 +1,6 @@ from InputsConfig import InputsConfig as p import random from Event import Event, Queue -from Node import Node if p.model == 2: from Models.Ethereum.Block import Block elif p.model == 3: @@ -69,31 +68,3 @@ def receive_tx_list_event(txList, gatewayId, tokenTime, eventTime): block.timestamp = tokenTime event = Event(eventType, gatewayId, eventTime, block) Queue.add_event(event) - - - - def create_change_pool_event(node, eventTime): - eventType = "change_pool" - if eventTime <= p.simTime: - node.joinTime = eventTime - if node.node_strategy == "random": - while True: - temp_pool = node.pool - choosePool = random.randint(0, len(p.POOLS)) - node.pool = p.POOLS[choosePool] - Queue.add_event(Event(eventType, node, eventTime)) - if node.pool != temp_pool: - break - - - elif node.node_strategy == "sequential": - pool_index = p.POOLS.index(node.pool) - pool_index = (pool_index + 1)%len(p.POOLS) - node.pool = p.POOLS[pool_index] - Queue.add_event(Event(eventType, node, eventTime)) - - - # elif node.node_strategy == 'strategy_based': - # pool_index = p.POOLS.index(node.pool) - - From a02ed262d9710363dd3b04cd95d5883873373109 Mon Sep 17 00:00:00 2001 From: arman Date: Mon, 22 Mar 2021 19:12:14 +0530 Subject: [PATCH 15/26] Added pool tracking in node results --- Models/Bitcoin/Node.py | 25 ++++++++++++++++++------- Models/Node.py | 3 ++- Statistics.py | 8 ++++++-- 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/Models/Bitcoin/Node.py b/Models/Bitcoin/Node.py index 36ae5e4..37cdd07 100644 --- a/Models/Bitcoin/Node.py +++ b/Models/Bitcoin/Node.py @@ -5,13 +5,24 @@ class Node(BaseNode): def __init__(self, id, hashPower, pool=None, joinTime=0, node_type='honest', node_strategy=None): '''Initialize a new miner named name with hashrate measured in hashes per second.''' - super().__init__(id, pool) # ,blockchain,transactionsPool,blocks,balance) + super().__init__(id, pool, joinTime) # ,blockchain,transactionsPool,blocks,balance) self.hashPower = hashPower - self.joinTime = joinTime self.node_type = node_type self.node_strategy = node_strategy # random, sequential, strategy_based - # self.blockchain= []# create an array for each miner to store chain state locally - # self.transactionsPool= [] - # self.blocks= 0# total number of blocks mined in the main chain - # self.fee = 0 - # self.balance= 0# to count all reward that a miner made, including block rewards + uncle rewards + transactions fee + if self.pool: + self.original_pool = self.pool + self.pool_list = [pool.id] + self.blocks_list = [0] + + def resetState(): + from InputsConfig import InputsConfig as p + for node in p.NODES: + node.blockchain = [] # create an array for each miner to store chain state locally + node.transactionsPool = [] + node.blocks = 0 # total number of blocks mined in the main chain + node.fee = 0 # total transaction fee recieved from mined block + node.balance = 0 # to count all reward that a miner made + if node.pool: + node.pool = node.original_pool + node.pool_list = [node.original_pool.id] + node.blocks_list = [0] diff --git a/Models/Node.py b/Models/Node.py index b67d948..12b97e5 100644 --- a/Models/Node.py +++ b/Models/Node.py @@ -10,7 +10,7 @@ class Node(object): :param int blocks: the total number of blocks mined in the main chain :param int balance: the amount of cryptocurrencies a node has """ - def __init__(self, id, pool): + def __init__(self, id, pool, joinTime): self.id= id self.blockchain= [] self.transactionsPool= [] @@ -18,6 +18,7 @@ def __init__(self, id, pool): self.fee = 0 self.balance= 0 self.pool = pool + self.joinTime = joinTime # Generate the Genesis block and append it to the local blockchain for all nodes def generate_gensis_block(): diff --git a/Statistics.py b/Statistics.py index 0ab0bec..0841c05 100644 --- a/Statistics.py +++ b/Statistics.py @@ -47,7 +47,11 @@ def profit_results(run_id): for m in p.NODES: i = run_id * len(p.NODES) + m.id - Statistics.profits[i] = [run_id, m.id, m.pool.strategy if m.pool else 'SOLO'] + Statistics.profits[i] = [run_id, m.id] + if m.pool: + Statistics.profits[i] += [m.pool_list, m.blocks_list, m.pool.strategy] + else: + Statistics.profits[i] += [None, None, 'SOLO'] if p.model== 0: Statistics.profits[i].append("NA") else: @@ -92,7 +96,7 @@ def print_to_excel(fname): df2.columns= ['Run ID', 'Total Blocks', 'Main Blocks', 'Uncle blocks', 'Uncle Rate', 'Stale Blocks', 'Stale Rate', '# transactions'] df3 = pd.DataFrame(Statistics.profits) - df3.columns = ['Run ID', 'Miner ID', 'Pool Strategy', '% Hash Power','# Mined Blocks', '% of main blocks', '# Uncle Blocks','% of uncles', 'Transaction Fee', 'Profit (in crypto)', 'Profit in $'] + df3.columns = ['Run ID', 'Miner ID', 'Pool IDs', 'Blocks per pool', 'Pool Strategy', '% Hash Power','# Mined Blocks', '% of main blocks', '# Uncle Blocks','% of uncles', 'Transaction Fee', 'Profit (in crypto)', 'Profit in $'] df4 = pd.DataFrame(Statistics.pool_profits) df4.columns = ['Run ID', 'Pool ID', 'Pool Strategy', '% Fee Rate', '% Hash Power', '# Mined Blocks', '% of main blocks', 'Transaction Fee', 'Profit (in crypto)', 'Profit in $'] From d136de2e30424579c6aced3dbfef5cfba9469b74 Mon Sep 17 00:00:00 2001 From: arman Date: Mon, 22 Mar 2021 19:13:41 +0530 Subject: [PATCH 16/26] Fixed PPS+ and FPPS logic, cleaned code --- InputsConfig.py | 2 +- Models/Incentives.py | 160 ++++++++++++++++++++++++++----------------- 2 files changed, 99 insertions(+), 63 deletions(-) diff --git a/InputsConfig.py b/InputsConfig.py index a03755e..46ee9e6 100644 --- a/InputsConfig.py +++ b/InputsConfig.py @@ -66,7 +66,7 @@ class InputsConfig: POOLS = [ Pool(_id=0, strategy='PPS', fee_rate=3), Pool(_id=1, strategy='FPPS', fee_rate=1), - Pool(_id=2, strategy='PPS+', fee_rate=1), + Pool(_id=2, strategy='PPS+', fee_rate=1, block_window=8), Pool(_id=3, strategy='PPLNS', fee_rate=1, block_window=8), Pool(_id=4, strategy='PPLNS', fee_rate=2, block_window=6), ] diff --git a/Models/Incentives.py b/Models/Incentives.py index 31286c2..fd435fe 100644 --- a/Models/Incentives.py +++ b/Models/Incentives.py @@ -9,13 +9,25 @@ class Incentives: Defines the rewarded elements (block + transactions), calculate and distribute the rewards among the participating nodes """ def distribute_rewards(): - for i, bc in enumerate(c.global_chain): - + for i, bc in enumerate(c.global_chain[1:]): + current_time = bc.timestamp + miner = p.NODES[bc.miner] + miner.blocks += 1 + + if miner.pool: + miner_pool = miner.pool + miner_pool.blocks += 1 + miner.blocks_list[-1] += 1 + # pool gets block reward + miner_pool.balance += p.Breward + + + windows = {} cumulative_times = {} for pool in p.POOLS: - if pool.strategy != 'PPLNS': + if pool.strategy not in ['PPLNS', 'PPS+']: continue N = pool.block_window @@ -23,24 +35,25 @@ def distribute_rewards(): window_timestamp = c.global_chain[i-N-1].timestamp else: window_timestamp = 0 - + resource = 0 for pool_node in pool.nodes: - minimum_window_time = min(current_time - pool_node.joinTime, current_time - window_timestamp) - resource += minimum_window_time * pool_node.hashPower - + latest_window_time = max(pool_node.joinTime, window_timestamp) + resource += (current_time - latest_window_time) * pool_node.hashPower + + windows[pool] = window_timestamp cumulative_times[pool] = resource for m in p.NODES: if not m.pool: - if bc.miner == m.id: - m.blocks += 1 + if miner == m: m.balance += p.Breward # increase the miner balance by the block reward m.fee += bc.fee m.balance += bc.fee # add transaction fees to balance + elif m.pool.strategy == 'PPS': reward = m.hashPower/100 * p.Breward @@ -49,81 +62,98 @@ def distribute_rewards(): m.pool.balance -= reward m.balance += reward - if bc.miner == m.id: - m.blocks += 1 - m.pool.blocks += 1 - # pool keeps block reward and transaction fee - m.pool.balance += p.Breward - m.pool.block_fee += bc.fee - m.pool.balance += bc.fee + if miner == m: + # pool keeps transaction fee + miner_pool.block_fee += bc.fee + miner_pool.balance += bc.fee + + + elif m.pool.strategy == 'FPPS': + + reward = m.hashPower/100 * p.Breward + # constant payout after deducting pool fee + reward = (100 - m.pool.fee_rate)/100 * reward + m.pool.balance -= reward + m.balance += reward + + # expected transaction fee also paid out at each block + fee = m.hashPower/100 * bc.fee + m.pool.balance -= fee + m.fee += fee + m.balance = fee + + if miner == m: + # pool gets transaction fee in case block found + miner_pool.block_fee += bc.fee + miner_pool.balance += bc.fee + elif m.pool.strategy == 'PPLNS': - - if bc.miner == m.id: - m.blocks += 1 - m.pool.blocks += 1 - - reward = (100 - m.pool.fee_rate)/100 * p.Breward - m.pool.balance += m.pool.fee_rate/100 * p.Breward - - print('check') - for node in m.pool.nodes: - N = m.pool.block_window - if i > N: - window_timestamp = c.global_chain[i-N-1].timestamp - else: - window_timestamp = 0 - - minimum_window_time = min(current_time - node.joinTime, current_time - window_timestamp) - - frac = (minimum_window_time * node.hashPower)/cumulative_times[m.pool] - print(frac) + + # payout only occurs in case pool finds the block + if miner == m: + # reward to be distributed after deducting pool fee + reward = (100 - miner_pool.fee_rate)/100 * p.Breward + miner_pool.balance -= reward + + # print('check') + for node in miner_pool.nodes: + + latest_window_time = max(node.joinTime, windows[miner_pool]) + frac = (current_time - latest_window_time) * node.hashPower/cumulative_times[miner_pool] + # print(frac) + # transaction fee and reward distributed as per fraction of time and hashpower spent node.balance += frac * reward node.fee += frac * bc.fee node.balance += frac * bc.fee - elif m.pool.strategy == 'FPPS': + # elif m.pool.strategy == 'FPPS': + + # if bc.miner == m.id: + # m.blocks += 1 + # m.pool.blocks += 1 + # # deducting pool fee + # reward = (100 - m.pool.fee_rate)/100 * p.Breward + # m.pool.balance += m.pool.fee_rate/100 * p.Breward - if bc.miner == m.id: - m.blocks += 1 - m.pool.blocks += 1 - # deducting pool fee - reward = (100 - m.pool.fee_rate)/100 * p.Breward - m.pool.balance += m.pool.fee_rate/100 * p.Breward + # # all nodes share block reward and transaction fee + # for node in m.pool.nodes: + # node.balance += node.hashPower/m.pool.hashPower * reward + # node_fee = node.hashPower/m.pool.hashPower * bc.fee + # node.fee += node_fee + # node.balance += node_fee - # all nodes share block reward and transaction fee - for node in m.pool.nodes: - node.balance += node.hashPower/m.pool.hashPower * reward - node_fee = node.hashPower/m.pool.hashPower * bc.fee - node.fee += node_fee - node.balance += node_fee elif m.pool.strategy == 'PPS+': reward = m.hashPower/100 * p.Breward + # constant payout after deducting pool fee (PPS) reward = (100 - m.pool.fee_rate)/100 * reward - # constant payout after deducting pool fee m.pool.balance -= reward m.balance += reward - # transaction fee shared by all nodes - if bc.miner == m.id: - m.blocks += 1 - m.pool.blocks += 1 - # pool keeps block reward while the transaction fee is shared - m.pool.balance += p.Breward - for node in m.pool.nodes: - node.fee += node.hashPower/m.pool.hashPower * bc.fee - node.balance += node.hashPower/m.pool.hashPower * bc.fee + if miner == m: + + # print('check2') + for node in miner_pool.nodes: + latest_window_time = max(node.joinTime, windows[miner_pool]) + frac = (current_time - latest_window_time) * node.hashPower/cumulative_times[miner_pool] + # print(frac) + # transaction fee shared by PPLNS method as per fraction of time and hashpower + node.fee += frac * bc.fee + node.balance += frac * bc.fee + # pool hopping for node in p.NODES: if node.node_type == 'selfish': if random.random() < jump_threshold: - - node.joinTime = current_time # TODO improve time assignment + + print('jumping...') + node.pool.hashPower -= node.hashPower + node.pool.nodes.remove(node) if node.node_strategy == "random": while True: @@ -138,4 +168,10 @@ def distribute_rewards(): pool_index = (pool_index + 1)%len(p.POOLS) node.pool = p.POOLS[pool_index] - # elif node.node_strategy == 'strategy_based' \ No newline at end of file + # elif node.node_strategy == 'strategy_based' + + node.joinTime = current_time # TODO improve time assignment + node.pool.hashPower += node.hashPower + node.pool.nodes.append(node) + node.pool_list.append(node.pool.id) + node.blocks_list.append(0) From f3a45ed1bfb8092cf8c8fdccf239d9600924d9aa Mon Sep 17 00:00:00 2001 From: arman Date: Tue, 23 Mar 2021 22:54:23 +0530 Subject: [PATCH 17/26] Pool hopping implemented --- InputsConfig.py | 30 ++++++++++++++++++--------- Models/Bitcoin/Pool.py | 9 ++++++++- Models/Incentives.py | 46 ++++++++++++++++++++++++++---------------- 3 files changed, 58 insertions(+), 27 deletions(-) diff --git a/InputsConfig.py b/InputsConfig.py index 46ee9e6..2c48614 100644 --- a/InputsConfig.py +++ b/InputsConfig.py @@ -65,24 +65,36 @@ class InputsConfig: # Creating the mining pools POOLS = [ Pool(_id=0, strategy='PPS', fee_rate=3), - Pool(_id=1, strategy='FPPS', fee_rate=1), - Pool(_id=2, strategy='PPS+', fee_rate=1, block_window=8), + Pool(_id=1, strategy='FPPS', fee_rate=3), + Pool(_id=2, strategy='PPS+', fee_rate=3, block_window=8), Pool(_id=3, strategy='PPLNS', fee_rate=1, block_window=8), Pool(_id=4, strategy='PPLNS', fee_rate=2, block_window=6), + Pool(_id=5, strategy='PPS', fee_rate=3), + Pool(_id=6, strategy='FPPS', fee_rate=4), + Pool(_id=7, strategy='PPS+', fee_rate=1, block_window=10), ] # here as an example we define three nodes by assigning a unique id for each one + % of hash (computing) power NODES = [ - Node(id=0, pool=POOLS[0], hashPower=25), + Node(id=0, pool=POOLS[0], hashPower=12), Node(id=1, pool=POOLS[1], hashPower=5), - Node(id=2, pool=POOLS[2], hashPower=15), - Node(id=3, pool=POOLS[3], hashPower=15), - Node(id=4, pool=POOLS[1], hashPower=10), + Node(id=2, pool=POOLS[2], hashPower=5), + Node(id=3, pool=POOLS[6], hashPower=12), + Node(id=4, pool=POOLS[7], hashPower=5), Node(id=5, pool=POOLS[4], hashPower=10), Node(id=6, pool=POOLS[4], hashPower=5), Node(id=7, pool=POOLS[3], hashPower=5), - Node(id=8, pool=POOLS[3], hashPower=5), - Node(id=9, hashPower=5) + Node(id=8, pool=POOLS[6], hashPower=5), + Node(id=9, pool=POOLS[0], hashPower=7, node_type='selfish', node_strategy='strategy_based'), + Node(id=10, pool=POOLS[3], hashPower=6, node_type='selfish', node_strategy='strategy_based'), + Node(id=11, pool=POOLS[4], hashPower=8, node_type='selfish', node_strategy='strategy_based'), + Node(id=12, pool=POOLS[5], hashPower=5, node_type='selfish', node_strategy='strategy_based'), + Node(id=13, pool=POOLS[7], hashPower=2), + Node(id=14, pool=POOLS[1], hashPower=3), + Node(id=15, hashPower=1), + Node(id=16, hashPower=1), + Node(id=17, hashPower=1), + Node(id=18, hashPower=2) ] # Giving every pool a reference to the nodes it contains. Also, update the total hashrate of a pool. @@ -92,7 +104,7 @@ class InputsConfig: node.pool.hashPower += node.hashPower ''' Simulation Parameters ''' - simTime = 10000 # the simulation length (in seconds) + simTime = 24 * 60 * 60 # the simulation length (in seconds) Runs = 3 # Number of simulation runs ''' Input configurations for Ethereum model ''' diff --git a/Models/Bitcoin/Pool.py b/Models/Bitcoin/Pool.py index a2b02d2..8e9cedc 100644 --- a/Models/Bitcoin/Pool.py +++ b/Models/Bitcoin/Pool.py @@ -16,4 +16,11 @@ def resetState(): for pool in p.POOLS: pool.blocks = 0 # total number of blocks mined in the main chain pool.block_fee = 0 # total transaction fee recieved from mined block - pool.balance = 0 # to count all reward that a miner made \ No newline at end of file + pool.balance = 0 # to count all reward that a miner made + pool.nodes = [] + pool.hashPower = 0 + + for node in p.NODES: + if node.pool: + node.pool.nodes.append(node) + node.pool.hashPower += node.hashPower diff --git a/Models/Incentives.py b/Models/Incentives.py index fd435fe..4b79ed9 100644 --- a/Models/Incentives.py +++ b/Models/Incentives.py @@ -146,31 +146,43 @@ def distribute_rewards(): node.balance += frac * bc.fee + avg_payout = 0 + pool_payout = {} + print() + for pool in p.POOLS: + print('pool', pool.id, [node.id for node in pool.nodes]) + if pool.nodes and pool.strategy in ['PPS', 'PPLNS']: + mu = ((100 - pool.fee_rate)/100) * ((p.Breward + p.Tfee * 6000)/len(pool.nodes)) * pool.hashPower/100 + pool_payout[pool] = mu + avg_payout += mu + + avg_payout /= len(pool_payout) + # pool hopping for node in p.NODES: - if node.node_type == 'selfish': - if random.random() < jump_threshold: + if node.node_type == 'selfish' and node.pool.strategy in ['PPS', 'PPLNS']: + + mu = pool_payout[node.pool] + + if mu < avg_payout and random.random() < (avg_payout - mu)/avg_payout: + print('jumping...', node.id) - print('jumping...') node.pool.hashPower -= node.hashPower node.pool.nodes.remove(node) - if node.node_strategy == "random": - while True: - temp_pool = node.pool - choosePool = random.randint(0, len(p.POOLS)) - node.pool = p.POOLS[choosePool] - if node.pool != temp_pool: - break - - elif node.node_strategy == "sequential": - pool_index = p.POOLS.index(node.pool) - pool_index = (pool_index + 1)%len(p.POOLS) - node.pool = p.POOLS[pool_index] + if node.node_strategy == "strategy_based": + strategy_pools = [pool for pool in p.POOLS if pool.strategy == node.pool.strategy and pool != node.pool] + choosenPool = random.randint(0, len(strategy_pools) - 1) + node.pool = strategy_pools[choosenPool] - # elif node.node_strategy == 'strategy_based' + elif node.node_strategy == "across_strategies": + strategy_pools = [pool for pool in p.POOLS if pool.strategy in ['PPS', 'PPLNS'] and pool != node.pool] + choosenPool = random.randint(0, len(strategy_pools) - 1) + node.pool = strategy_pools[choosenPool] - node.joinTime = current_time # TODO improve time assignment + # c.global_chain[i-1].timestamp + 0.432 * random.expovariate(hashPower * 1/p.Binterval) # TODO improve time assignment + # TODO random delay + node.joinTime = current_time node.pool.hashPower += node.hashPower node.pool.nodes.append(node) node.pool_list.append(node.pool.id) From b1270e187ec151d3f014f628afc2c2cc3642e853 Mon Sep 17 00:00:00 2001 From: arman Date: Thu, 25 Mar 2021 17:32:47 +0530 Subject: [PATCH 18/26] Minor changes --- InputsConfig.py | 7 ------- Main.py | 18 ++++++++++++++++++ Models/Bitcoin/Pool.py | 5 ----- Models/Incentives.py | 10 ++++++---- 4 files changed, 24 insertions(+), 16 deletions(-) diff --git a/InputsConfig.py b/InputsConfig.py index 2c48614..ca454a9 100644 --- a/InputsConfig.py +++ b/InputsConfig.py @@ -94,15 +94,8 @@ class InputsConfig: Node(id=15, hashPower=1), Node(id=16, hashPower=1), Node(id=17, hashPower=1), - Node(id=18, hashPower=2) ] - # Giving every pool a reference to the nodes it contains. Also, update the total hashrate of a pool. - for node in NODES: - if node.pool is not None: - node.pool.nodes.append(node) - node.pool.hashPower += node.hashPower - ''' Simulation Parameters ''' simTime = 24 * 60 * 60 # the simulation length (in seconds) Runs = 3 # Number of simulation runs diff --git a/Main.py b/Main.py index 32618cd..1c634ab 100644 --- a/Main.py +++ b/Main.py @@ -40,6 +40,24 @@ def main(): for i in range(p.Runs): + + hashPower = 0 + # Giving every pool a reference to the nodes it contains. Also, update the total hashrate of a pool. + for node in p.NODES: + hashPower += node.hashPower + if node.pool: + node.pool.nodes.append(node) + node.pool.hashPower += node.hashPower + + print() + print('-'*10, f'Run: {i+1}', '-'*10) + print('No. of Miners:', len(p.NODES)) + print('Total hash power:', hashPower) + print('Pools:') + for pool in p.POOLS: + print(' -', pool.id, [node.id for node in pool.nodes], pool.hashPower) + print('\n') + clock = 0 # set clock to 0 at the start of the simulation if p.hasTrans: if p.Ttechnique == "Light": diff --git a/Models/Bitcoin/Pool.py b/Models/Bitcoin/Pool.py index 8e9cedc..eca742c 100644 --- a/Models/Bitcoin/Pool.py +++ b/Models/Bitcoin/Pool.py @@ -19,8 +19,3 @@ def resetState(): pool.balance = 0 # to count all reward that a miner made pool.nodes = [] pool.hashPower = 0 - - for node in p.NODES: - if node.pool: - node.pool.nodes.append(node) - node.pool.hashPower += node.hashPower diff --git a/Models/Incentives.py b/Models/Incentives.py index 4b79ed9..fad0df7 100644 --- a/Models/Incentives.py +++ b/Models/Incentives.py @@ -148,15 +148,16 @@ def distribute_rewards(): avg_payout = 0 pool_payout = {} - print() for pool in p.POOLS: - print('pool', pool.id, [node.id for node in pool.nodes]) + # print('pool', pool.id, [node.id for node in pool.nodes]) if pool.nodes and pool.strategy in ['PPS', 'PPLNS']: - mu = ((100 - pool.fee_rate)/100) * ((p.Breward + p.Tfee * 6000)/len(pool.nodes)) * pool.hashPower/100 + mu = ((100 - pool.fee_rate)/100) * ((p.Breward + p.Tfee * 2000)/len(pool.nodes)) * pool.hashPower/100 pool_payout[pool] = mu avg_payout += mu avg_payout /= len(pool_payout) + # print(avg_payout) + # print([(pool.id, pool.strategy, pay) for pool, pay in pool_payout.items()]) # pool hopping for node in p.NODES: @@ -165,7 +166,7 @@ def distribute_rewards(): mu = pool_payout[node.pool] if mu < avg_payout and random.random() < (avg_payout - mu)/avg_payout: - print('jumping...', node.id) + print(node.id, ':', node.pool.id, end=' ') node.pool.hashPower -= node.hashPower node.pool.nodes.remove(node) @@ -186,4 +187,5 @@ def distribute_rewards(): node.pool.hashPower += node.hashPower node.pool.nodes.append(node) node.pool_list.append(node.pool.id) + print('--->', node.pool.id, [n.id for n in node.pool.nodes]) node.blocks_list.append(0) From 615f350b6294d0d5e7b0459551a26127c89c8978 Mon Sep 17 00:00:00 2001 From: arman Date: Fri, 26 Mar 2021 18:48:28 +0530 Subject: [PATCH 19/26] FPPS bug fix --- Models/Incentives.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Models/Incentives.py b/Models/Incentives.py index fad0df7..68c419e 100644 --- a/Models/Incentives.py +++ b/Models/Incentives.py @@ -80,7 +80,7 @@ def distribute_rewards(): fee = m.hashPower/100 * bc.fee m.pool.balance -= fee m.fee += fee - m.balance = fee + m.balance += fee if miner == m: # pool gets transaction fee in case block found From d01ad2981f03f8951a999345f47825dfac80e984 Mon Sep 17 00:00:00 2001 From: arman Date: Tue, 30 Mar 2021 11:02:34 +0530 Subject: [PATCH 20/26] Minor format changes, etc --- Main.py | 7 ++++--- Models/Incentives.py | 3 +++ Statistics.py | 11 ++++++----- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/Main.py b/Main.py index 1c634ab..b6c2b46 100644 --- a/Main.py +++ b/Main.py @@ -49,13 +49,13 @@ def main(): node.pool.nodes.append(node) node.pool.hashPower += node.hashPower - print() + print(p.sim_type) print('-'*10, f'Run: {i+1}', '-'*10) print('No. of Miners:', len(p.NODES)) print('Total hash power:', hashPower) print('Pools:') for pool in p.POOLS: - print(' -', pool.id, [node.id for node in pool.nodes], pool.hashPower) + print(' -', pool.id, pool.strategy, 'Nodes:', [node.id for node in pool.nodes], 'Hash power:', pool.hashPower) print('\n') clock = 0 # set clock to 0 at the start of the simulation @@ -98,7 +98,8 @@ def main(): Node.resetState() # reset all the states (blockchains) for all nodes in the network Pool.resetState() # reset all pools in the network - fname = f"(Allverify)1day_{p.Bsize/1000000}M_{p.Tn/1000}K-{i}-{datetime.now()}.xlsx".replace(':', '_') + # fname = f"(Allverify)1day_{p.Bsize/1000000}M_{p.Tn/1000}K-{i}-{datetime.now()}.xlsx".replace(':', '_') + fname = f"{p.sim_type}_{int(p.simTime/(24*60*60))}days_{datetime.now()}.xlsx".replace(':', '_') # print all the simulation results in an excel file Statistics.print_to_excel(fname) # Statistics.reset2() # reset profit results diff --git a/Models/Incentives.py b/Models/Incentives.py index 68c419e..d3aff0a 100644 --- a/Models/Incentives.py +++ b/Models/Incentives.py @@ -155,6 +155,9 @@ def distribute_rewards(): pool_payout[pool] = mu avg_payout += mu + if len(pool_payout) == 0: + continue + avg_payout /= len(pool_payout) # print(avg_payout) # print([(pool.id, pool.strategy, pay) for pool, pay in pool_payout.items()]) diff --git a/Statistics.py b/Statistics.py index 0841c05..b1ffe3b 100644 --- a/Statistics.py +++ b/Statistics.py @@ -49,9 +49,9 @@ def profit_results(run_id): i = run_id * len(p.NODES) + m.id Statistics.profits[i] = [run_id, m.id] if m.pool: - Statistics.profits[i] += [m.pool_list, m.blocks_list, m.pool.strategy] + Statistics.profits[i] += [m.pool_list, m.blocks_list, m.pool.strategy, m.pool.fee_rate] else: - Statistics.profits[i] += [None, None, 'SOLO'] + Statistics.profits[i] += [None, None, 'SOLO', None] if p.model== 0: Statistics.profits[i].append("NA") else: @@ -71,7 +71,7 @@ def profit_results(run_id): def pool_results(run_id): for pool in p.POOLS: - Statistics.pool_profits.append([run_id, pool.id, pool.strategy, pool.fee_rate, pool.hashPower, pool.blocks, + Statistics.pool_profits.append([run_id, pool.id, pool.strategy, pool.fee_rate, pool.block_window, pool.hashPower, pool.blocks, round(pool.blocks/Statistics.mainBlocks * 100, 2), pool.block_fee, pool.balance, pool.balance * p.Bprice]) @@ -96,10 +96,11 @@ def print_to_excel(fname): df2.columns= ['Run ID', 'Total Blocks', 'Main Blocks', 'Uncle blocks', 'Uncle Rate', 'Stale Blocks', 'Stale Rate', '# transactions'] df3 = pd.DataFrame(Statistics.profits) - df3.columns = ['Run ID', 'Miner ID', 'Pool IDs', 'Blocks per pool', 'Pool Strategy', '% Hash Power','# Mined Blocks', '% of main blocks', '# Uncle Blocks','% of uncles', 'Transaction Fee', 'Profit (in crypto)', 'Profit in $'] + df3.columns = ['Run ID', 'Miner ID', 'Pool IDs', 'Blocks per pool', 'Pool Strategy', 'Pool Fee', '% Hash Power','# Mined Blocks', '% of main blocks', '# Uncle Blocks','% of uncles', 'Transaction Fee', 'Profit (in crypto)', 'Profit in $'] df4 = pd.DataFrame(Statistics.pool_profits) - df4.columns = ['Run ID', 'Pool ID', 'Pool Strategy', '% Fee Rate', '% Hash Power', '# Mined Blocks', '% of main blocks', 'Transaction Fee', 'Profit (in crypto)', 'Profit in $'] + if len(df4) > 0: + df4.columns = ['Run ID', 'Pool ID', 'Pool Strategy', '% Fee Rate', 'Block Window', '% Hash Power', '# Mined Blocks', '% of main blocks', 'Transaction Fee', 'Profit (in crypto)', 'Profit in $'] df5 = pd.DataFrame(Statistics.chain) if p.model==2: From 2a48d3ca020d3aa88da562526c075b5815301729 Mon Sep 17 00:00:00 2001 From: arman Date: Tue, 30 Mar 2021 11:03:01 +0530 Subject: [PATCH 21/26] Defining simulations --- InputsConfig.py | 175 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 141 insertions(+), 34 deletions(-) diff --git a/InputsConfig.py b/InputsConfig.py index ca454a9..e36db43 100644 --- a/InputsConfig.py +++ b/InputsConfig.py @@ -1,3 +1,4 @@ +import random class InputsConfig: @@ -62,43 +63,149 @@ class InputsConfig: from Models.Bitcoin.Node import Node from Models.Bitcoin.Pool import Pool - # Creating the mining pools - POOLS = [ - Pool(_id=0, strategy='PPS', fee_rate=3), - Pool(_id=1, strategy='FPPS', fee_rate=3), - Pool(_id=2, strategy='PPS+', fee_rate=3, block_window=8), - Pool(_id=3, strategy='PPLNS', fee_rate=1, block_window=8), - Pool(_id=4, strategy='PPLNS', fee_rate=2, block_window=6), - Pool(_id=5, strategy='PPS', fee_rate=3), - Pool(_id=6, strategy='FPPS', fee_rate=4), - Pool(_id=7, strategy='PPS+', fee_rate=1, block_window=10), - ] + pool_types = { + 'F2Pool': ('PPS+', 2), + 'Poolin': ('PPS+', 2), + 'BTC.com': ('FPPS', 2), + 'AntPool': ('PPLNS', 2), + 'Huobi': ('PPLNS', 1), + 'Binance Pool': ('PPS', 3), + 'ViaBTC': ('PPS', 4), + '1THash': ('FPPS', 4), + # 'OKExPool':, + # 'SlushPool': '', + # 'BTC Guild': 'PPLNS', + # 'GHash.IO':, + # 'BitFury':, + # 'BTCC': 'PPS' + } - # here as an example we define three nodes by assigning a unique id for each one + % of hash (computing) power - NODES = [ - Node(id=0, pool=POOLS[0], hashPower=12), - Node(id=1, pool=POOLS[1], hashPower=5), - Node(id=2, pool=POOLS[2], hashPower=5), - Node(id=3, pool=POOLS[6], hashPower=12), - Node(id=4, pool=POOLS[7], hashPower=5), - Node(id=5, pool=POOLS[4], hashPower=10), - Node(id=6, pool=POOLS[4], hashPower=5), - Node(id=7, pool=POOLS[3], hashPower=5), - Node(id=8, pool=POOLS[6], hashPower=5), - Node(id=9, pool=POOLS[0], hashPower=7, node_type='selfish', node_strategy='strategy_based'), - Node(id=10, pool=POOLS[3], hashPower=6, node_type='selfish', node_strategy='strategy_based'), - Node(id=11, pool=POOLS[4], hashPower=8, node_type='selfish', node_strategy='strategy_based'), - Node(id=12, pool=POOLS[5], hashPower=5, node_type='selfish', node_strategy='strategy_based'), - Node(id=13, pool=POOLS[7], hashPower=2), - Node(id=14, pool=POOLS[1], hashPower=3), - Node(id=15, hashPower=1), - Node(id=16, hashPower=1), - Node(id=17, hashPower=1), - ] + NODES = [] + POOLS = [] + sim_type = 'baseline' + for pool, (strat, fee) in pool_types.items(): + if strat in ['PPLNS', 'PPS+']: + POOLS.append(Pool(_id=pool, strategy=strat, fee_rate=fee, block_window=8)) + else: + POOLS.append(Pool(_id=pool, strategy=strat, fee_rate=fee)) + + for i, pool in enumerate(POOLS): + NODES.append( + Node(id=i, pool=pool, hashPower=12.5)) + + # sim_type = 'solo' + + # hp = 100 + # for i in range(8): + # hp /= 2 + # NODES.append(Node(id=i, hashPower=hp)) + + # sim_type = 'pps' + + # for i, name in enumerate(pool_types): + # POOLS.append(Pool(_id=name, strategy='PPS', fee_rate=i)) + + # hp = 100 + # for i, pool in enumerate(POOLS): + # hp /= 2 + # NODES.append(Node(id=i, pool=pool, hashPower=hp)) + + # sim_type = 'fpps' + + # for i, name in enumerate(pool_types): + # POOLS.append(Pool(_id=name, strategy='FPPS', fee_rate=i)) + + # hp = 100 + # for i, pool in enumerate(POOLS): + # hp /= 2 + # NODES.append(Node(id=i, pool=pool, hashPower=hp)) + + # sim_type = 'pplns' + + # for i, name in enumerate(pool_types): + # POOLS.append(Pool(_id=name, strategy='PPLNS', fee_rate=i, block_window=8)) + + # hp = 100 + # for i, pool in enumerate(POOLS): + # hp /= 2 + # NODES.append(Node(id=i, pool=pool, hashPower=hp)) + + # sim_type = 'pplns_windows' + + # for i, name in enumerate(pool_types): + # POOLS.append(Pool(_id=name, strategy='PPLNS', fee_rate=2, block_window=i+2)) + + # hp = 100 + # for i, pool in enumerate(POOLS): + # hp /= 2 + # NODES.append(Node(id=i, pool=pool, hashPower=hp)) + + # sim_type = 'pps+' + + # for i, name in enumerate(pool_types): + # POOLS.append(Pool(_id=name, strategy='PPS+', fee_rate=i, block_window=8)) + + # hp = 100 + # for i, pool in enumerate(POOLS): + # hp /= 2 + # NODES.append(Node(id=i, pool=pool, hashPower=hp)) + + # sim_type = 'pps+_windows' + + # for i, name in enumerate(pool_types): + # POOLS.append(Pool(_id=name, strategy='PPS+', fee_rate=3, block_window=i+2)) + + # hp = 100 + # for i, pool in enumerate(POOLS): + # hp /= 2 + # NODES.append(Node(id=i, pool=pool, hashPower=hp)) + + + # Creating the mining pools + # POOLS = [ + # Pool(_id=0, strategy='PPS', fee_rate=3), + # Pool(_id=1, strategy='FPPS', fee_rate=3), + # Pool(_id=2, strategy='PPS+', fee_rate=3, block_window=8), + # Pool(_id=3, strategy='PPLNS', fee_rate=1, block_window=8), + # Pool(_id=4, strategy='PPLNS', fee_rate=2, block_window=6), + # Pool(_id=5, strategy='PPS', fee_rate=3), + # Pool(_id=6, strategy='FPPS', fee_rate=4), + # Pool(_id=7, strategy='PPS+', fee_rate=1, block_window=10), + # Pool(_id=8, strategy='PPS', fee_rate=3), + # ] + + # # here as an example we define three nodes by assigning a unique id for each one + % of hash (computing) power + # NODES = [ + # Node(id=0, pool=POOLS[0], hashPower=7), + # Node(id=1, pool=POOLS[1], hashPower=5), + # Node(id=2, pool=POOLS[2], hashPower=5), + # Node(id=3, pool=POOLS[6], hashPower=8), + # Node(id=4, pool=POOLS[7], hashPower=5), + # Node(id=5, pool=POOLS[4], hashPower=8), + # Node(id=6, pool=POOLS[4], hashPower=5), + # Node(id=7, pool=POOLS[3], hashPower=5), + # Node(id=8, pool=POOLS[6], hashPower=5), + # Node(id=9, pool=POOLS[0], hashPower=7, node_type='selfish', node_strategy='strategy_based'), + # Node(id=10, pool=POOLS[3], hashPower=6, node_type='selfish', node_strategy='strategy_based'), + # Node(id=11, pool=POOLS[4], hashPower=8, node_type='selfish', node_strategy='strategy_based'), + # Node(id=12, pool=POOLS[5], hashPower=5, node_type='selfish', node_strategy='strategy_based'), + # Node(id=13, pool=POOLS[7], hashPower=2), + # Node(id=14, pool=POOLS[1], hashPower=3), + # Node(id=15, hashPower=1), + # Node(id=16, hashPower=1), + # Node(id=17, hashPower=1), + # Node(id=18, hashPower=2), + # Node(id=19, pool=POOLS[5], hashPower=2), + # Node(id=20, pool=POOLS[5], hashPower=2, node_type='selfish', node_strategy='strategy_based'), + # Node(id=21, hashPower=1), + # Node(id=22, pool=POOLS[8], hashPower=3, node_type='selfish', node_strategy='strategy_based'), + # Node(id=23, pool=POOLS[8], hashPower=2), + # Node(id=24, pool=POOLS[8], hashPower=1), + # ] ''' Simulation Parameters ''' - simTime = 24 * 60 * 60 # the simulation length (in seconds) - Runs = 3 # Number of simulation runs + simTime = 10 * 24 * 60 * 60 # the simulation length (in seconds) + Runs = 1 # Number of simulation runs ''' Input configurations for Ethereum model ''' if model == 2: From ac675704493796e3ec6f5a3b595b924fdf07b2f4 Mon Sep 17 00:00:00 2001 From: arman Date: Fri, 2 Apr 2021 17:43:31 +0530 Subject: [PATCH 22/26] Refactored code and updated config values for transaction fee and bitcoin price --- InputsConfig.py | 4 ++-- Main.py | 10 +++++----- Models/Bitcoin/Node.py | 4 ++-- Models/Bitcoin/Pool.py | 4 ++-- Models/Incentives.py | 33 ++++++++++++++++++++------------- Models/Node.py | 4 ++-- Statistics.py | 2 +- 7 files changed, 34 insertions(+), 27 deletions(-) diff --git a/InputsConfig.py b/InputsConfig.py index e36db43..90d4c28 100644 --- a/InputsConfig.py +++ b/InputsConfig.py @@ -46,7 +46,7 @@ class InputsConfig: Bsize = 1.0 # The block size in MB Bdelay = 0.42 # average block propogation delay in seconds, #Ref: https://bitslog.wordpress.com/2016/04/28/uncle-mining-an-ethereum-consensus-protocol-flaw/ Breward = 6.25 # Reward for mining a block - Bprice = 54000 + Bprice = 58000 jump_threshold = 0.02 ''' Transaction Parameters ''' @@ -55,7 +55,7 @@ class InputsConfig: Tn = 10 # The rate of the number of transactions to be created per second # The average transaction propagation delay in seconds (Only if Full technique is used) Tdelay = 5.1 - Tfee = 0.000062 # The average transaction fee + Tfee = 0.00029 # The average transaction fee Tsize = 0.000546 # The average transaction size in MB ''' Node Parameters ''' diff --git a/Main.py b/Main.py index b6c2b46..cf5487c 100644 --- a/Main.py +++ b/Main.py @@ -41,21 +41,21 @@ def main(): for i in range(p.Runs): - hashPower = 0 + hash_power = 0 # Giving every pool a reference to the nodes it contains. Also, update the total hashrate of a pool. for node in p.NODES: - hashPower += node.hashPower + hash_power += node.hashPower if node.pool: node.pool.nodes.append(node) - node.pool.hashPower += node.hashPower + node.pool.hash_power += node.hashPower print(p.sim_type) print('-'*10, f'Run: {i+1}', '-'*10) print('No. of Miners:', len(p.NODES)) - print('Total hash power:', hashPower) + print('Total hash power:', hash_power) print('Pools:') for pool in p.POOLS: - print(' -', pool.id, pool.strategy, 'Nodes:', [node.id for node in pool.nodes], 'Hash power:', pool.hashPower) + print(' -', pool.id, pool.strategy, 'Nodes:', [node.id for node in pool.nodes], 'Hash power:', pool.hash_power) print('\n') clock = 0 # set clock to 0 at the start of the simulation diff --git a/Models/Bitcoin/Node.py b/Models/Bitcoin/Node.py index 37cdd07..9494959 100644 --- a/Models/Bitcoin/Node.py +++ b/Models/Bitcoin/Node.py @@ -3,9 +3,9 @@ class Node(BaseNode): - def __init__(self, id, hashPower, pool=None, joinTime=0, node_type='honest', node_strategy=None): + def __init__(self, id, hashPower, pool=None, join_time=0, node_type='honest', node_strategy=None): '''Initialize a new miner named name with hashrate measured in hashes per second.''' - super().__init__(id, pool, joinTime) # ,blockchain,transactionsPool,blocks,balance) + super().__init__(id, pool, join_time) # ,blockchain,transactionsPool,blocks,balance) self.hashPower = hashPower self.node_type = node_type self.node_strategy = node_strategy # random, sequential, strategy_based diff --git a/Models/Bitcoin/Pool.py b/Models/Bitcoin/Pool.py index eca742c..4bfba04 100644 --- a/Models/Bitcoin/Pool.py +++ b/Models/Bitcoin/Pool.py @@ -5,7 +5,7 @@ def __init__(self, _id, strategy, fee_rate, block_window=None): self.strategy = strategy self.fee_rate = fee_rate self.nodes = [] - self.hashPower = 0 + self.hash_power = 0 self.blocks = 0 self.block_fee = 0 self.balance = 0 @@ -18,4 +18,4 @@ def resetState(): pool.block_fee = 0 # total transaction fee recieved from mined block pool.balance = 0 # to count all reward that a miner made pool.nodes = [] - pool.hashPower = 0 + pool.hash_power = 0 diff --git a/Models/Incentives.py b/Models/Incentives.py index d3aff0a..9647cfd 100644 --- a/Models/Incentives.py +++ b/Models/Incentives.py @@ -9,6 +9,9 @@ class Incentives: Defines the rewarded elements (block + transactions), calculate and distribute the rewards among the participating nodes """ def distribute_rewards(): + + total_transactions = 0 + for i, bc in enumerate(c.global_chain[1:]): current_time = bc.timestamp @@ -26,6 +29,7 @@ def distribute_rewards(): windows = {} cumulative_times = {} + # calculating sum of shares for PPLNS and PPS+ pools since the block window, or the join time for pool in p.POOLS: if pool.strategy not in ['PPLNS', 'PPS+']: continue @@ -36,13 +40,13 @@ def distribute_rewards(): else: window_timestamp = 0 - resource = 0 + shares = 0 for pool_node in pool.nodes: - latest_window_time = max(pool_node.joinTime, window_timestamp) - resource += (current_time - latest_window_time) * pool_node.hashPower + latest_window_time = max(pool_node.join_time, window_timestamp) + shares += (current_time - latest_window_time) * pool_node.hashPower windows[pool] = window_timestamp - cumulative_times[pool] = resource + cumulative_times[pool] = shares for m in p.NODES: @@ -99,7 +103,7 @@ def distribute_rewards(): # print('check') for node in miner_pool.nodes: - latest_window_time = max(node.joinTime, windows[miner_pool]) + latest_window_time = max(node.join_time, windows[miner_pool]) frac = (current_time - latest_window_time) * node.hashPower/cumulative_times[miner_pool] # print(frac) # transaction fee and reward distributed as per fraction of time and hashpower spent @@ -108,7 +112,7 @@ def distribute_rewards(): node.balance += frac * bc.fee - # elif m.pool.strategy == 'FPPS': + # elif m.pool.strategy == 'Proportional': # if bc.miner == m.id: # m.blocks += 1 @@ -119,8 +123,8 @@ def distribute_rewards(): # # all nodes share block reward and transaction fee # for node in m.pool.nodes: - # node.balance += node.hashPower/m.pool.hashPower * reward - # node_fee = node.hashPower/m.pool.hashPower * bc.fee + # node.balance += node.hashPower/m.pool.hash_power * reward + # node_fee = node.hashPower/m.pool.hash_power * bc.fee # node.fee += node_fee # node.balance += node_fee @@ -138,7 +142,7 @@ def distribute_rewards(): # print('check2') for node in miner_pool.nodes: - latest_window_time = max(node.joinTime, windows[miner_pool]) + latest_window_time = max(node.join_time, windows[miner_pool]) frac = (current_time - latest_window_time) * node.hashPower/cumulative_times[miner_pool] # print(frac) # transaction fee shared by PPLNS method as per fraction of time and hashpower @@ -146,12 +150,15 @@ def distribute_rewards(): node.balance += frac * bc.fee + total_transactions += len(bc.transactions) + S = round(total_transactions/(i+1), 2) + print(total_transactions, S) avg_payout = 0 pool_payout = {} for pool in p.POOLS: # print('pool', pool.id, [node.id for node in pool.nodes]) if pool.nodes and pool.strategy in ['PPS', 'PPLNS']: - mu = ((100 - pool.fee_rate)/100) * ((p.Breward + p.Tfee * 2000)/len(pool.nodes)) * pool.hashPower/100 + mu = ((100 - pool.fee_rate)/100) * ((p.Breward + p.Tfee * S)/len(pool.nodes)) * pool.hash_power/100 pool_payout[pool] = mu avg_payout += mu @@ -171,7 +178,7 @@ def distribute_rewards(): if mu < avg_payout and random.random() < (avg_payout - mu)/avg_payout: print(node.id, ':', node.pool.id, end=' ') - node.pool.hashPower -= node.hashPower + node.pool.hash_power -= node.hash_power node.pool.nodes.remove(node) if node.node_strategy == "strategy_based": @@ -186,8 +193,8 @@ def distribute_rewards(): # c.global_chain[i-1].timestamp + 0.432 * random.expovariate(hashPower * 1/p.Binterval) # TODO improve time assignment # TODO random delay - node.joinTime = current_time - node.pool.hashPower += node.hashPower + node.join_time = current_time + node.pool.hash_power += node.hash_power node.pool.nodes.append(node) node.pool_list.append(node.pool.id) print('--->', node.pool.id, [n.id for n in node.pool.nodes]) diff --git a/Models/Node.py b/Models/Node.py index 12b97e5..8fa6ab2 100644 --- a/Models/Node.py +++ b/Models/Node.py @@ -10,7 +10,7 @@ class Node(object): :param int blocks: the total number of blocks mined in the main chain :param int balance: the amount of cryptocurrencies a node has """ - def __init__(self, id, pool, joinTime): + def __init__(self, id, pool, join_time): self.id= id self.blockchain= [] self.transactionsPool= [] @@ -18,7 +18,7 @@ def __init__(self, id, pool, joinTime): self.fee = 0 self.balance= 0 self.pool = pool - self.joinTime = joinTime + self.join_time = join_time # Generate the Genesis block and append it to the local blockchain for all nodes def generate_gensis_block(): diff --git a/Statistics.py b/Statistics.py index b1ffe3b..458d468 100644 --- a/Statistics.py +++ b/Statistics.py @@ -71,7 +71,7 @@ def profit_results(run_id): def pool_results(run_id): for pool in p.POOLS: - Statistics.pool_profits.append([run_id, pool.id, pool.strategy, pool.fee_rate, pool.block_window, pool.hashPower, pool.blocks, + Statistics.pool_profits.append([run_id, pool.id, pool.strategy, pool.fee_rate, pool.block_window, pool.hash_power, pool.blocks, round(pool.blocks/Statistics.mainBlocks * 100, 2), pool.block_fee, pool.balance, pool.balance * p.Bprice]) From cfafd705f22adaa788dfeea4c294e37489279ae7 Mon Sep 17 00:00:00 2001 From: arman Date: Fri, 2 Apr 2021 17:44:43 +0530 Subject: [PATCH 23/26] Added 2 more pool hopping strategies --- Models/Incentives.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Models/Incentives.py b/Models/Incentives.py index 9647cfd..7721fb1 100644 --- a/Models/Incentives.py +++ b/Models/Incentives.py @@ -181,12 +181,19 @@ def distribute_rewards(): node.pool.hash_power -= node.hash_power node.pool.nodes.remove(node) - if node.node_strategy == "strategy_based": + if node.node_strategy == 'best' + node.pool = max(pool_payout, key=pool_payout.get) + + elif node.node_strategy == 'best by strategy': + strategy_pools = [pool for pool in sorted(pool_payout, key=pool_payout.get) if pool.strategy == node.pool.strategy] + node.pool = strategy_pools + + elif node.node_strategy == "strategy based": strategy_pools = [pool for pool in p.POOLS if pool.strategy == node.pool.strategy and pool != node.pool] choosenPool = random.randint(0, len(strategy_pools) - 1) node.pool = strategy_pools[choosenPool] - elif node.node_strategy == "across_strategies": + elif node.node_strategy == "across strategy": strategy_pools = [pool for pool in p.POOLS if pool.strategy in ['PPS', 'PPLNS'] and pool != node.pool] choosenPool = random.randint(0, len(strategy_pools) - 1) node.pool = strategy_pools[choosenPool] From 72768eedbdc0d15f33cf13b83a71f1c591108fa3 Mon Sep 17 00:00:00 2001 From: arman Date: Fri, 2 Apr 2021 17:46:30 +0530 Subject: [PATCH 24/26] typo --- Models/Incentives.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Models/Incentives.py b/Models/Incentives.py index 7721fb1..295a48d 100644 --- a/Models/Incentives.py +++ b/Models/Incentives.py @@ -181,12 +181,12 @@ def distribute_rewards(): node.pool.hash_power -= node.hash_power node.pool.nodes.remove(node) - if node.node_strategy == 'best' + if node.node_strategy == 'best': node.pool = max(pool_payout, key=pool_payout.get) elif node.node_strategy == 'best by strategy': strategy_pools = [pool for pool in sorted(pool_payout, key=pool_payout.get) if pool.strategy == node.pool.strategy] - node.pool = strategy_pools + node.pool = strategy_pools[0] elif node.node_strategy == "strategy based": strategy_pools = [pool for pool in p.POOLS if pool.strategy == node.pool.strategy and pool != node.pool] From 1c15e5d740849175d1ef5e66b8367caf2477a57c Mon Sep 17 00:00:00 2001 From: arman Date: Fri, 9 Apr 2021 09:46:45 +0530 Subject: [PATCH 25/26] Refactoring, changing sim specifications and output file --- InputsConfig.py | 160 +++++++++++++++++++++++++++++++++++++---- Main.py | 15 ++-- Models/Bitcoin/Node.py | 4 ++ Models/Incentives.py | 24 ++++--- Statistics.py | 19 +++-- 5 files changed, 191 insertions(+), 31 deletions(-) diff --git a/InputsConfig.py b/InputsConfig.py index 90d4c28..0419ff8 100644 --- a/InputsConfig.py +++ b/InputsConfig.py @@ -80,18 +80,158 @@ class InputsConfig: # 'BTCC': 'PPS' } + ''' Simulation Parameters ''' + simTime = 50 * 24 * 60 * 60 # the simulation length (in seconds) + Runs = 1 # Number of simulation runs + NODES = [] POOLS = [] - sim_type = 'baseline' - for pool, (strat, fee) in pool_types.items(): - if strat in ['PPLNS', 'PPS+']: - POOLS.append(Pool(_id=pool, strategy=strat, fee_rate=fee, block_window=8)) + + i = 0 + j = 0 + base_rate = {'PPS': 2.5, 'FPPS': 2.5, 'PPS+': 2.5, 'PPLNS': 0} + + + + sim_type = 'honest' + hopping = False + for pool_type in ['SOLO', 'PPS', 'FPPS', 'PPLNS', 'PPS+']: + + if pool_type == 'SOLO': + hp = 12 + for _ in range(10): + hp /= 2 + NODES.append(Node(id=j, hashPower=hp)) + j += 1 else: - POOLS.append(Pool(_id=pool, strategy=strat, fee_rate=fee)) + rate = base_rate[pool_type] + + if pool_type in ['PPS', 'FPPS']: + for f in range(4): + hp = 4 + + pool = Pool(_id=i, strategy=pool_type, fee_rate=rate) + POOLS.append(pool) + + i += 1 + rate += 0.5 + + n = random.randint(7, 10) + for k in range(n): + hp /= 2 + NODES.append(Node(id=j, pool=pool, hashPower=hp)) + j += 1 + + else: + for w in range(4): + if rate in [1, 3]: + for bw in [6, 8, 10, 12]: + hp = 4 + + pool = Pool(_id=i, strategy=pool_type, fee_rate=rate, block_window=bw) + POOLS.append(pool) + i += 1 + + n = random.randint(7, 10) + for k in range(n): + hp /= 2 + NODES.append(Node(id=j, pool=pool, hashPower=hp)) + j += 1 + + rate += 0.5 + + else: + hp = 4 + + pool = Pool(_id=i, strategy=pool_type, fee_rate=rate, block_window=8) + POOLS.append(pool) + + i += 1 + rate += 0.5 + + n = random.randint(7, 10) + for k in range(n): + hp /= 2 + NODES.append(Node(id=j, pool=pool, hashPower=hp)) + j += 1 + + + + + # sim_type = 'hopping' + # hopping = True + # for pool_type in ['PPS', 'PPLNS']: + + # # if pool_type == 'SOLO': + # # hp = 12 + # # for _ in range(10): + # # hp /= 2 + # # NODES.append(Node(id=j, hashPower=hp)) + # # j += 1 + # # else: + # rate = base_rate[pool_type] + + # if pool_type in ['PPS']: + # for f in range(4): + # hp = 5 + + # pool = Pool(_id=i, strategy=pool_type, fee_rate=rate) + # POOLS.append(pool) + # i += 1 + # rate += 0.5 + + # n = random.randint(7, 10) + # for k in range(n): + # hp /= 2 + # r = random.random() + # if r < 0.25: + # NODES.append(Node(id=j, pool=pool, hashPower=hp, node_type='selfish', node_strategy='best')) + # elif r < 0.5: + # NODES.append(Node(id=j, pool=pool, hashPower=hp, node_type='selfish', node_strategy='strategy based')) + # elif r < 0.75: + # NODES.append(Node(id=j, pool=pool, hashPower=hp, node_type='selfish', node_strategy='random')) + # else: + # NODES.append(Node(id=j, pool=pool, hashPower=hp)) + # j += 1 + + # else: + # for w in range(4): + # for bw in [6, 8, 10, 12]: + # hp = 5 + + # pool = Pool(_id=i, strategy=pool_type, fee_rate=rate, block_window=bw) + # POOLS.append(pool) + # i += 1 + + # n = random.randint(7, 10) + # for k in range(n): + # hp /= 2 + # r = random.random() + # if r < 0.25: + # NODES.append(Node(id=j, pool=pool, hashPower=hp, node_type='selfish', node_strategy='best')) + # elif r < 0.5: + # NODES.append(Node(id=j, pool=pool, hashPower=hp, node_type='selfish', node_strategy='strategy based')) + # elif r < 0.75: + # NODES.append(Node(id=j, pool=pool, hashPower=hp, node_type='selfish', node_strategy='random')) + # else: + # NODES.append(Node(id=j, pool=pool, hashPower=hp)) + # j += 1 + + # rate += 0.5 + + + + + # sim_type = 'baseline' + # for pool, (strat, fee) in pool_types.items(): + # if strat in ['PPLNS', 'PPS+']: + # POOLS.append(Pool(_id=pool, strategy=strat, fee_rate=fee, block_window=8)) + # else: + # POOLS.append(Pool(_id=pool, strategy=strat, fee_rate=fee)) - for i, pool in enumerate(POOLS): - NODES.append( - Node(id=i, pool=pool, hashPower=12.5)) + # for i, pool in enumerate(POOLS): + # NODES.append( + # Node(id=i, pool=pool, hashPower=12.5)) # sim_type = 'solo' @@ -203,10 +343,6 @@ class InputsConfig: # Node(id=24, pool=POOLS[8], hashPower=1), # ] - ''' Simulation Parameters ''' - simTime = 10 * 24 * 60 * 60 # the simulation length (in seconds) - Runs = 1 # Number of simulation runs - ''' Input configurations for Ethereum model ''' if model == 2: diff --git a/Main.py b/Main.py index cf5487c..812c1ed 100644 --- a/Main.py +++ b/Main.py @@ -40,23 +40,26 @@ def main(): for i in range(p.Runs): + print('-'*10, f'Run: {i+1}', '-'*10) + print(p.sim_type) + print('No. of Miners:', len(p.NODES)) hash_power = 0 # Giving every pool a reference to the nodes it contains. Also, update the total hashrate of a pool. + print('SOLO Nodes: ', end='') for node in p.NODES: hash_power += node.hashPower if node.pool: node.pool.nodes.append(node) node.pool.hash_power += node.hashPower + else: + print(node.id, end=', ') + print() - print(p.sim_type) - print('-'*10, f'Run: {i+1}', '-'*10) - print('No. of Miners:', len(p.NODES)) - print('Total hash power:', hash_power) print('Pools:') for pool in p.POOLS: - print(' -', pool.id, pool.strategy, 'Nodes:', [node.id for node in pool.nodes], 'Hash power:', pool.hash_power) - print('\n') + print(' -', pool.id, pool.strategy, 'Fee Rate:', pool.fee_rate, 'Nodes:', [node.id for node in pool.nodes], 'Hash power:', pool.hash_power) + print('Total hash power:', hash_power, '\n') clock = 0 # set clock to 0 at the start of the simulation if p.hasTrans: diff --git a/Models/Bitcoin/Node.py b/Models/Bitcoin/Node.py index 9494959..7be6ff4 100644 --- a/Models/Bitcoin/Node.py +++ b/Models/Bitcoin/Node.py @@ -13,6 +13,8 @@ def __init__(self, id, hashPower, pool=None, join_time=0, node_type='honest', no self.original_pool = self.pool self.pool_list = [pool.id] self.blocks_list = [0] + self.reward_list = [0] + self.balance_list = [0] def resetState(): from InputsConfig import InputsConfig as p @@ -26,3 +28,5 @@ def resetState(): node.pool = node.original_pool node.pool_list = [node.original_pool.id] node.blocks_list = [0] + node.reward_list = [0] + node.balance_list = [0] diff --git a/Models/Incentives.py b/Models/Incentives.py index 295a48d..c0f28ed 100644 --- a/Models/Incentives.py +++ b/Models/Incentives.py @@ -28,7 +28,7 @@ def distribute_rewards(): windows = {} - cumulative_times = {} + cumulative_shares = {} # calculating sum of shares for PPLNS and PPS+ pools since the block window, or the join time for pool in p.POOLS: if pool.strategy not in ['PPLNS', 'PPS+']: @@ -46,7 +46,7 @@ def distribute_rewards(): shares += (current_time - latest_window_time) * pool_node.hashPower windows[pool] = window_timestamp - cumulative_times[pool] = shares + cumulative_shares[pool] = shares for m in p.NODES: @@ -65,6 +65,8 @@ def distribute_rewards(): reward = (100 - m.pool.fee_rate)/100 * reward m.pool.balance -= reward m.balance += reward + m.balance_list[-1] += reward + m.reward_list[-1] += reward if miner == m: # pool keeps transaction fee @@ -104,13 +106,16 @@ def distribute_rewards(): for node in miner_pool.nodes: latest_window_time = max(node.join_time, windows[miner_pool]) - frac = (current_time - latest_window_time) * node.hashPower/cumulative_times[miner_pool] + frac = (current_time - latest_window_time) * node.hashPower/cumulative_shares[miner_pool] # print(frac) # transaction fee and reward distributed as per fraction of time and hashpower spent node.balance += frac * reward node.fee += frac * bc.fee node.balance += frac * bc.fee + node.reward_list[-1] + frac * reward + node.balance_list[-1] += frac * (bc.fee + reward) + # elif m.pool.strategy == 'Proportional': @@ -143,7 +148,7 @@ def distribute_rewards(): for node in miner_pool.nodes: latest_window_time = max(node.join_time, windows[miner_pool]) - frac = (current_time - latest_window_time) * node.hashPower/cumulative_times[miner_pool] + frac = (current_time - latest_window_time) * node.hashPower/cumulative_shares[miner_pool] # print(frac) # transaction fee shared by PPLNS method as per fraction of time and hashpower node.fee += frac * bc.fee @@ -175,17 +180,18 @@ def distribute_rewards(): mu = pool_payout[node.pool] + # avg2 = (avg_payout - mu)/(len(pool_payout)-1) if mu < avg_payout and random.random() < (avg_payout - mu)/avg_payout: print(node.id, ':', node.pool.id, end=' ') - node.pool.hash_power -= node.hash_power + node.pool.hash_power -= node.hashPower node.pool.nodes.remove(node) if node.node_strategy == 'best': node.pool = max(pool_payout, key=pool_payout.get) elif node.node_strategy == 'best by strategy': - strategy_pools = [pool for pool in sorted(pool_payout, key=pool_payout.get) if pool.strategy == node.pool.strategy] + strategy_pools = [pool for pool in sorted(pool_payout, key=pool_payout.get) if pool.strategy == node.pool.strategy and pool != node.pool] node.pool = strategy_pools[0] elif node.node_strategy == "strategy based": @@ -193,7 +199,7 @@ def distribute_rewards(): choosenPool = random.randint(0, len(strategy_pools) - 1) node.pool = strategy_pools[choosenPool] - elif node.node_strategy == "across strategy": + elif node.node_strategy == "random": strategy_pools = [pool for pool in p.POOLS if pool.strategy in ['PPS', 'PPLNS'] and pool != node.pool] choosenPool = random.randint(0, len(strategy_pools) - 1) node.pool = strategy_pools[choosenPool] @@ -201,8 +207,10 @@ def distribute_rewards(): # c.global_chain[i-1].timestamp + 0.432 * random.expovariate(hashPower * 1/p.Binterval) # TODO improve time assignment # TODO random delay node.join_time = current_time - node.pool.hash_power += node.hash_power + node.pool.hash_power += node.hashPower node.pool.nodes.append(node) node.pool_list.append(node.pool.id) print('--->', node.pool.id, [n.id for n in node.pool.nodes]) node.blocks_list.append(0) + node.balance_list.append(0) + node.reward_list.append(0) diff --git a/Statistics.py b/Statistics.py index 458d468..1a51ca8 100644 --- a/Statistics.py +++ b/Statistics.py @@ -47,11 +47,17 @@ def profit_results(run_id): for m in p.NODES: i = run_id * len(p.NODES) + m.id - Statistics.profits[i] = [run_id, m.id] - if m.pool: - Statistics.profits[i] += [m.pool_list, m.blocks_list, m.pool.strategy, m.pool.fee_rate] + Statistics.profits[i] = [run_id, m.id, m.node_type] + if p.hopping: + if m.pool: + Statistics.profits[i] += [m.node_strategy, m.pool_list, m.blocks_list, m.reward_list, m.balance_list] + else: + Statistics.profits[i] += [m.node_strategy, None, None] else: - Statistics.profits[i] += [None, None, 'SOLO', None] + if m.pool: + Statistics.profits[i] += [m.pool.id, m.pool.strategy, m.pool.fee_rate] + else: + Statistics.profits[i] += [None, 'SOLO', None] if p.model== 0: Statistics.profits[i].append("NA") else: @@ -96,7 +102,10 @@ def print_to_excel(fname): df2.columns= ['Run ID', 'Total Blocks', 'Main Blocks', 'Uncle blocks', 'Uncle Rate', 'Stale Blocks', 'Stale Rate', '# transactions'] df3 = pd.DataFrame(Statistics.profits) - df3.columns = ['Run ID', 'Miner ID', 'Pool IDs', 'Blocks per pool', 'Pool Strategy', 'Pool Fee', '% Hash Power','# Mined Blocks', '% of main blocks', '# Uncle Blocks','% of uncles', 'Transaction Fee', 'Profit (in crypto)', 'Profit in $'] + if p.hopping: + df3.columns = ['Run ID', 'Miner ID', 'Miner Type', 'Hopping Strategy', 'Pool IDs', 'Blocks per pool', 'Reward per pool', 'Balance per pool', '% Hash Power','# Mined Blocks', '% of main blocks', '# Uncle Blocks','% of uncles', 'Transaction Fee', 'Profit (in crypto)', 'Profit in $'] + else: + df3.columns = ['Run ID', 'Miner ID', 'Miner Type', 'Pool Id', 'Pool Strategy', 'Pool Fee', '% Hash Power','# Mined Blocks', '% of main blocks', '# Uncle Blocks','% of uncles', 'Transaction Fee', 'Profit (in crypto)', 'Profit in $'] df4 = pd.DataFrame(Statistics.pool_profits) if len(df4) > 0: From cb0b72299a40dd27f3daec611f7c8a36b9003838 Mon Sep 17 00:00:00 2001 From: arman Date: Tue, 13 Apr 2021 10:50:45 +0530 Subject: [PATCH 26/26] Code commenting and cleanup --- InputsConfig.py | 221 ++++++++++++++++++----------------------- Main.py | 4 +- Models/Bitcoin/Node.py | 25 +++-- Models/Bitcoin/Pool.py | 16 +-- Models/Incentives.py | 59 +++++++---- Models/Node.py | 4 +- 6 files changed, 166 insertions(+), 163 deletions(-) diff --git a/InputsConfig.py b/InputsConfig.py index 0419ff8..a5e224f 100644 --- a/InputsConfig.py +++ b/InputsConfig.py @@ -1,4 +1,6 @@ import random +from Models.Bitcoin.Pool import Pool +from Models.Bitcoin.Node import Node class InputsConfig: @@ -60,8 +62,6 @@ class InputsConfig: ''' Node Parameters ''' Nn = 3 # the total number of nodes in the network - from Models.Bitcoin.Node import Node - from Models.Bitcoin.Pool import Pool pool_types = { 'F2Pool': ('PPS+', 2), @@ -81,145 +81,122 @@ class InputsConfig: } ''' Simulation Parameters ''' - simTime = 50 * 24 * 60 * 60 # the simulation length (in seconds) + simTime = 1 * 24 * 60 * 60 # the simulation length (in seconds) Runs = 1 # Number of simulation runs - NODES = [] - POOLS = [] - - i = 0 - j = 0 - base_rate = {'PPS': 2.5, 'FPPS': 2.5, 'PPS+': 2.5, 'PPLNS': 0} - - - + # choose which sim to run sim_type = 'honest' - hopping = False - for pool_type in ['SOLO', 'PPS', 'FPPS', 'PPLNS', 'PPS+']: - - if pool_type == 'SOLO': - hp = 12 - for _ in range(10): - hp /= 2 - NODES.append(Node(id=j, hashPower=hp)) - j += 1 - else: - rate = base_rate[pool_type] - - if pool_type in ['PPS', 'FPPS']: - for f in range(4): - hp = 4 - - pool = Pool(_id=i, strategy=pool_type, fee_rate=rate) - POOLS.append(pool) - - i += 1 - rate += 0.5 - - n = random.randint(7, 10) - for k in range(n): - hp /= 2 - NODES.append(Node(id=j, pool=pool, hashPower=hp)) - j += 1 + # sim_type = 'hopping' + i = 0 # counter to track pool objects + j = 0 # counter to track node objects + NODES = [] # list of node objects + POOLS = [] # list of pool objects + + # function to assign nodes with decreasing hash power to pools + def create_nodes(node_id, pool, hash_power, NODES=NODES): + n = random.randint(7, 10) + for _ in range(n): + hash_power /= 2 + NODES.append(Node(id=node_id, pool=pool, hashPower=hash_power)) + node_id += 1 + return node_id + + # function to create miner nodes for the selfish sim + def create_selfish_nodes(node_id, pool, hash_power, NODES=NODES): + n = random.randint(7, 10) + for _ in range(n): + hash_power /= 2 + r = random.random() + # each hopping strategy (honest, best, strategy-based, random) has a 25% chance of being adopted by a node + if r < 0.25: + NODES.append(Node(id=node_id, pool=pool, hashPower=hash_power, node_type='selfish', node_strategy='best')) + elif r < 0.5: + NODES.append(Node(id=node_id, pool=pool, hashPower=hash_power, node_type='selfish', node_strategy='strategy based')) + elif r < 0.75: + NODES.append(Node(id=node_id, pool=pool, hashPower=hash_power, node_type='selfish', node_strategy='random')) else: - for w in range(4): - if rate in [1, 3]: - for bw in [6, 8, 10, 12]: - hp = 4 - - pool = Pool(_id=i, strategy=pool_type, fee_rate=rate, block_window=bw) - POOLS.append(pool) - i += 1 + NODES.append(Node(id=node_id, pool=pool, hashPower=hash_power)) + node_id += 1 + return node_id - n = random.randint(7, 10) - for k in range(n): - hp /= 2 - NODES.append(Node(id=j, pool=pool, hashPower=hp)) - j += 1 + # initialising base fee rates + base_rate = {'PPS': 2.5, 'FPPS': 2.5, 'PPS+': 2.5, 'PPLNS': 0} - rate += 0.5 + if sim_type == 'honest': + hopping = False + # for each kind of strategy instantiate node and pool objects + for pool_type in ['SOLO', 'PPS', 'FPPS', 'PPLNS', 'PPS+']: + + if pool_type == 'SOLO': + hp = 12 + # 10 solo miners with monotonically decreasing hash rates + for _ in range(10): + hp /= 2 + NODES.append(Node(id=j, hashPower=hp)) + j += 1 + else: + rate = base_rate[pool_type] - else: + # 4 pools for PPS and FPPS strategies each + if pool_type in ['PPS', 'FPPS']: + for f in range(4): hp = 4 - - pool = Pool(_id=i, strategy=pool_type, fee_rate=rate, block_window=8) + pool = Pool(_id=i, strategy=pool_type, fee_rate=rate) POOLS.append(pool) - + j = create_nodes(j, pool, hp) i += 1 rate += 0.5 - n = random.randint(7, 10) - for k in range(n): - hp /= 2 - NODES.append(Node(id=j, pool=pool, hashPower=hp)) - j += 1 - + else: + for w in range(4): + if rate in [1, 3]: + # for PPS+ and PPLNS pools we vary block window as well + for bw in [6, 8, 10, 12]: + hp = 4 + pool = Pool(_id=i, strategy=pool_type, fee_rate=rate, block_window=bw) + POOLS.append(pool) + j = create_nodes(j, pool, hp) + i += 1 + rate += 0.5 + else: + hp = 4 + pool = Pool(_id=i, strategy=pool_type, fee_rate=rate, block_window=8) + POOLS.append(pool) + j = create_nodes(j, pool, hp) + i += 1 + rate += 0.5 - # sim_type = 'hopping' - # hopping = True - # for pool_type in ['PPS', 'PPLNS']: - - # # if pool_type == 'SOLO': - # # hp = 12 - # # for _ in range(10): - # # hp /= 2 - # # NODES.append(Node(id=j, hashPower=hp)) - # # j += 1 - # # else: - # rate = base_rate[pool_type] - - # if pool_type in ['PPS']: - # for f in range(4): - # hp = 5 - - # pool = Pool(_id=i, strategy=pool_type, fee_rate=rate) - # POOLS.append(pool) - # i += 1 - # rate += 0.5 - - # n = random.randint(7, 10) - # for k in range(n): - # hp /= 2 - # r = random.random() - # if r < 0.25: - # NODES.append(Node(id=j, pool=pool, hashPower=hp, node_type='selfish', node_strategy='best')) - # elif r < 0.5: - # NODES.append(Node(id=j, pool=pool, hashPower=hp, node_type='selfish', node_strategy='strategy based')) - # elif r < 0.75: - # NODES.append(Node(id=j, pool=pool, hashPower=hp, node_type='selfish', node_strategy='random')) - # else: - # NODES.append(Node(id=j, pool=pool, hashPower=hp)) - # j += 1 + else: + hopping = True + # hopping sim only considers PPS and PPLNS + for pool_type in ['PPS', 'PPLNS']: - # else: - # for w in range(4): - # for bw in [6, 8, 10, 12]: - # hp = 5 - - # pool = Pool(_id=i, strategy=pool_type, fee_rate=rate, block_window=bw) - # POOLS.append(pool) - # i += 1 - - # n = random.randint(7, 10) - # for k in range(n): - # hp /= 2 - # r = random.random() - # if r < 0.25: - # NODES.append(Node(id=j, pool=pool, hashPower=hp, node_type='selfish', node_strategy='best')) - # elif r < 0.5: - # NODES.append(Node(id=j, pool=pool, hashPower=hp, node_type='selfish', node_strategy='strategy based')) - # elif r < 0.75: - # NODES.append(Node(id=j, pool=pool, hashPower=hp, node_type='selfish', node_strategy='random')) - # else: - # NODES.append(Node(id=j, pool=pool, hashPower=hp)) - # j += 1 - - # rate += 0.5 + # using the same base fee rates as the honest sim + rate = base_rate[pool_type] + # create PPS pools of varying fee rate + if pool_type in ['PPS']: + for f in range(4): + hp = 5 + pool = Pool(_id=i, strategy=pool_type, fee_rate=rate) + POOLS.append(pool) + j = create_selfish_nodes(j, pool, hp) + i += 1 + rate += 0.5 + else: + # in case of PPLNS, we vary the block window as well + for w in range(4): + for bw in [6, 8, 10, 12]: + hp = 5 + pool = Pool(_id=i, strategy=pool_type, fee_rate=rate, block_window=bw) + POOLS.append(pool) + j = create_selfish_nodes(j, pool, hp) + i += 1 + rate += 0.5 # sim_type = 'baseline' diff --git a/Main.py b/Main.py index 812c1ed..da4d669 100644 --- a/Main.py +++ b/Main.py @@ -101,12 +101,14 @@ def main(): Node.resetState() # reset all the states (blockchains) for all nodes in the network Pool.resetState() # reset all pools in the network - # fname = f"(Allverify)1day_{p.Bsize/1000000}M_{p.Tn/1000}K-{i}-{datetime.now()}.xlsx".replace(':', '_') + # set file name for results fname = f"{p.sim_type}_{int(p.simTime/(24*60*60))}days_{datetime.now()}.xlsx".replace(':', '_') + # fname = f"(Allverify)1day_{p.Bsize/1000000}M_{p.Tn/1000}K-{i}-{datetime.now()}.xlsx".replace(':', '_') # print all the simulation results in an excel file Statistics.print_to_excel(fname) # Statistics.reset2() # reset profit results ######################################################## Run Main method ##################################################################### + if __name__ == '__main__': main() diff --git a/Models/Bitcoin/Node.py b/Models/Bitcoin/Node.py index 7be6ff4..1ed324f 100644 --- a/Models/Bitcoin/Node.py +++ b/Models/Bitcoin/Node.py @@ -5,12 +5,18 @@ class Node(BaseNode): def __init__(self, id, hashPower, pool=None, join_time=0, node_type='honest', node_strategy=None): '''Initialize a new miner named name with hashrate measured in hashes per second.''' - super().__init__(id, pool, join_time) # ,blockchain,transactionsPool,blocks,balance) + super().__init__(id) # blockchain, transactionsPool, blocks, balance self.hashPower = hashPower - self.node_type = node_type - self.node_strategy = node_strategy # random, sequential, strategy_based + self.pool = pool + self.node_type = node_type # honest or selfish + self.node_strategy = node_strategy # best, strategy_based, random + + # initialise attributes to track pool related information if self.pool: + self.join_time = join_time self.original_pool = self.pool + # the following lists are used to maintain a record of + # multiple pools when pool hopping occurs self.pool_list = [pool.id] self.blocks_list = [0] self.reward_list = [0] @@ -18,12 +24,15 @@ def __init__(self, id, hashPower, pool=None, join_time=0, node_type='honest', no def resetState(): from InputsConfig import InputsConfig as p + for node in p.NODES: - node.blockchain = [] # create an array for each miner to store chain state locally - node.transactionsPool = [] - node.blocks = 0 # total number of blocks mined in the main chain - node.fee = 0 # total transaction fee recieved from mined block - node.balance = 0 # to count all reward that a miner made + node.blockchain = [] # reset array for each miner to store chain state locally + node.transactionsPool = [] # reset transaction pool + node.blocks = 0 # reset total number of blocks mined in the main chain + node.fee = 0 # reset total transaction fee recieved from mined block + node.balance = 0 # reset miner reward + + # reset all pool related information if node.pool: node.pool = node.original_pool node.pool_list = [node.original_pool.id] diff --git a/Models/Bitcoin/Pool.py b/Models/Bitcoin/Pool.py index 4bfba04..5562073 100644 --- a/Models/Bitcoin/Pool.py +++ b/Models/Bitcoin/Pool.py @@ -1,18 +1,20 @@ class Pool(): def __init__(self, _id, strategy, fee_rate, block_window=None): + # initialise pool parameters self.id = _id - self.strategy = strategy + self.strategy = strategy # PPS, PPLNS, PPS+, FPPS self.fee_rate = fee_rate - self.nodes = [] - self.hash_power = 0 - self.blocks = 0 - self.block_fee = 0 - self.balance = 0 - self.block_window = block_window + self.nodes = [] # nodes currently minnig in the pool + self.hash_power = 0 # % of hash power controlled by pool + self.blocks = 0 # number of blocks mined by miners in pool + self.block_fee = 0 # total transaction fee kept by pool + self.balance = 0 # pool balance + self.block_window = block_window # block window in case of PPLNS and PPS+ pools def resetState(): from InputsConfig import InputsConfig as p + # reset pool attribures after each run for pool in p.POOLS: pool.blocks = 0 # total number of blocks mined in the main chain pool.block_fee = 0 # total transaction fee recieved from mined block diff --git a/Models/Incentives.py b/Models/Incentives.py index c0f28ed..1025ec5 100644 --- a/Models/Incentives.py +++ b/Models/Incentives.py @@ -16,25 +16,29 @@ def distribute_rewards(): current_time = bc.timestamp + # save node miner object miner = p.NODES[bc.miner] + # increment miner block count miner.blocks += 1 + # in case miner belongs to a pool, update pool attributes if miner.pool: miner_pool = miner.pool miner_pool.blocks += 1 miner.blocks_list[-1] += 1 - # pool gets block reward + # pool gets block reward when a block is found miner_pool.balance += p.Breward windows = {} cumulative_shares = {} - # calculating sum of shares for PPLNS and PPS+ pools since the block window, or the join time + # calculating sum of shares for PPLNS and PPS+ pools since block window or the join time for pool in p.POOLS: if pool.strategy not in ['PPLNS', 'PPS+']: continue N = pool.block_window + # extract timestamp of earliest block within window if i > N: window_timestamp = c.global_chain[i-N-1].timestamp else: @@ -42,15 +46,19 @@ def distribute_rewards(): shares = 0 for pool_node in pool.nodes: + # select the latest timestamp from the pool join time and block windows latest_window_time = max(pool_node.join_time, window_timestamp) + # calculate shares contributed since the latest time shares += (current_time - latest_window_time) * pool_node.hashPower + # save window timestamp and total shares for later use windows[pool] = window_timestamp cumulative_shares[pool] = shares for m in p.NODES: + # for solo miners, pay miner directly in case block found if not m.pool: if miner == m: m.balance += p.Breward # increase the miner balance by the block reward @@ -61,8 +69,9 @@ def distribute_rewards(): elif m.pool.strategy == 'PPS': reward = m.hashPower/100 * p.Breward - # constant payout after deducting pool fee + # miners get a constant payout after deducting pool fee reward = (100 - m.pool.fee_rate)/100 * reward + # decrease pool balance and increase miner balance m.pool.balance -= reward m.balance += reward m.balance_list[-1] += reward @@ -77,8 +86,9 @@ def distribute_rewards(): elif m.pool.strategy == 'FPPS': reward = m.hashPower/100 * p.Breward - # constant payout after deducting pool fee + # miners get a constant payout after deducting pool fee reward = (100 - m.pool.fee_rate)/100 * reward + # decrease pool balance and increase miner balance m.pool.balance -= reward m.balance += reward @@ -102,12 +112,11 @@ def distribute_rewards(): reward = (100 - miner_pool.fee_rate)/100 * p.Breward miner_pool.balance -= reward - # print('check') for node in miner_pool.nodes: - + # once again, calculate the latest time to be considered calculating miner shares latest_window_time = max(node.join_time, windows[miner_pool]) + # calculate miner shares as a fraction of total shares frac = (current_time - latest_window_time) * node.hashPower/cumulative_shares[miner_pool] - # print(frac) # transaction fee and reward distributed as per fraction of time and hashpower spent node.balance += frac * reward node.fee += frac * bc.fee @@ -137,29 +146,30 @@ def distribute_rewards(): elif m.pool.strategy == 'PPS+': reward = m.hashPower/100 * p.Breward - # constant payout after deducting pool fee (PPS) + # miner gets a constant payout of block reward after deducting pool fee (PPS) reward = (100 - m.pool.fee_rate)/100 * reward m.pool.balance -= reward m.balance += reward if miner == m: - # print('check2') for node in miner_pool.nodes: - + # calculate the latest time to be considered calculating miner shares latest_window_time = max(node.join_time, windows[miner_pool]) + # calculate miner shares as a fraction of total shares frac = (current_time - latest_window_time) * node.hashPower/cumulative_shares[miner_pool] - # print(frac) # transaction fee shared by PPLNS method as per fraction of time and hashpower node.fee += frac * bc.fee node.balance += frac * bc.fee + # increment total transactions and calculate average transactions per block, S total_transactions += len(bc.transactions) S = round(total_transactions/(i+1), 2) - print(total_transactions, S) + avg_payout = 0 pool_payout = {} + # calculate expected payout for each pool for pool in p.POOLS: # print('pool', pool.id, [node.id for node in pool.nodes]) if pool.nodes and pool.strategy in ['PPS', 'PPLNS']: @@ -171,28 +181,30 @@ def distribute_rewards(): continue avg_payout /= len(pool_payout) - # print(avg_payout) # print([(pool.id, pool.strategy, pay) for pool, pay in pool_payout.items()]) - # pool hopping + # implementation of pool hopping for node in p.NODES: if node.node_type == 'selfish' and node.pool.strategy in ['PPS', 'PPLNS']: mu = pool_payout[node.pool] - # avg2 = (avg_payout - mu)/(len(pool_payout)-1) + # if current payout is lesser than average payout over all pools then the difference + # between average and current pool payouts decides probability of pool hopping if mu < avg_payout and random.random() < (avg_payout - mu)/avg_payout: - print(node.id, ':', node.pool.id, end=' ') + # print(node.id, ':', node.pool.id, end=' ') + # remove miner and hash power from current pool node.pool.hash_power -= node.hashPower node.pool.nodes.remove(node) + # implement node hopping strategies if node.node_strategy == 'best': node.pool = max(pool_payout, key=pool_payout.get) - elif node.node_strategy == 'best by strategy': - strategy_pools = [pool for pool in sorted(pool_payout, key=pool_payout.get) if pool.strategy == node.pool.strategy and pool != node.pool] - node.pool = strategy_pools[0] + # elif node.node_strategy == 'best by strategy': + # strategy_pools = [pool for pool in sorted(pool_payout, key=pool_payout.get) if pool.strategy == node.pool.strategy and pool != node.pool] + # node.pool = strategy_pools[0] elif node.node_strategy == "strategy based": strategy_pools = [pool for pool in p.POOLS if pool.strategy == node.pool.strategy and pool != node.pool] @@ -204,13 +216,16 @@ def distribute_rewards(): choosenPool = random.randint(0, len(strategy_pools) - 1) node.pool = strategy_pools[choosenPool] - # c.global_chain[i-1].timestamp + 0.432 * random.expovariate(hashPower * 1/p.Binterval) # TODO improve time assignment - # TODO random delay + # TODO improve time assignment + # c.global_chain[i-1].timestamp + 0.432 * random.expovariate(hashPower * 1/p.Binterval) + + # update node join time and add miner to new pool node.join_time = current_time node.pool.hash_power += node.hashPower node.pool.nodes.append(node) node.pool_list.append(node.pool.id) - print('--->', node.pool.id, [n.id for n in node.pool.nodes]) + # print('--->', node.pool.id, [n.id for n in node.pool.nodes]) + # add 0 to all pool tracker to begin tracking new pool counts node.blocks_list.append(0) node.balance_list.append(0) node.reward_list.append(0) diff --git a/Models/Node.py b/Models/Node.py index 8fa6ab2..23f0e5e 100644 --- a/Models/Node.py +++ b/Models/Node.py @@ -10,15 +10,13 @@ class Node(object): :param int blocks: the total number of blocks mined in the main chain :param int balance: the amount of cryptocurrencies a node has """ - def __init__(self, id, pool, join_time): + def __init__(self, id): self.id= id self.blockchain= [] self.transactionsPool= [] self.blocks= 0# self.fee = 0 self.balance= 0 - self.pool = pool - self.join_time = join_time # Generate the Genesis block and append it to the local blockchain for all nodes def generate_gensis_block():