\xb5\xbcv\x84\x92\v\xcf\u060f\xe0w\x15\xa1\u02c4\x9c\xa3\xb4P0r\xc0\xc8\tFNj@.\xd2\xdc\xc0\xc2ILk\xe4\xbd\x19\xe8\xde\x14\x88\b\x8b\x0f\u078d\x9b>\xfc>|\xed}W\xe1\xee/^\xe3\x81|,\xfeNy\x97O\u71cb\x0e\x1e\x9dLmj\xf6\xd6\x03L\xf5\n\xdc\x04N\xdco\x9cT\xdbW\x89\xc2\xe8=|'Ck\r\u055d\x02 \x18,\xec\u0093w\u0742\a\xbe\xfd\x05\x89\xc7\u007f\xf4]\xac>\xf9\xc4\x1a\xaf?\x05\x1fr\xb2\xb6\x18\xb9u\xeb\xd7g\xc7<\xfb\xd9\u063c\xf9\u87dey\xe6\xf3\xff\xdb\xdb\xdf\xfe[\u007fCD%\xa1\xfew\u007fw5]~\xf9\xeb\xe4\x99t}O\xc0|?_\x9f\xfc\xe4?\"J\xb0\xbe\xfa\xd5/\x9fu\xd5Uo\xfa\xd4\xddw\u07fdqO\xbf\xb3\xf5\x97\u07ccs\xff\xaf\xff\x86a\xbf\x0f\xe3\xbc\xe6\xc0%~\xdee\xf5\x89\xb4\x8a[;\x977\xf5\aA\x00s\xae\x02\xdek\r\xb8\xe8\u0451zm\xd5\xf4\xdb4\x0e\xf0.i\u6943N\xa9\x94\xb2)\xb3k\x1bY\x97\x96*|\xefT\xc5\u068a\x17\x91q*\xa6mx\x96\x1a\xb4Jk\xc61\xa1\xe6E\xd2\xd4\xfbKc6\xaa\x91i\f\x91\x8a\u058a?\xdf\u059c\xe6@\xa3\xa8D\xaa\x19u\xe21\xc4\u00c6\xc1\x1e\x13\xbc\xe6\u0460Q$\xd26Toz\x131\xf2\xde\f\xc4\x19,\x1b'\x9cp\xc2\xe3\x17\\p\xde'\xae\xbc\xf27\xff\x84\x88\x1eM\u007f\xe7\xc6\x1b\xbf\x87\xe7?\xff\xccg\u0735>\x01\xf3g\xc0\xfa\xad\xdfz\ab(\xed\u55ff\xe6=\xd7_\xff\u03ff\xbfc\u01ce1\xfca\xe5#\xe3\x9e\xf5\x9c\xad\xb8\xf4?}\n\a\x1c\xbd\x19+\xbb\x97|\xb2O\xe9\xa1RM\xbb\xa4\x81p\xcdj=\x8eo\xa3\x99l\x8fj\x80\xa4\xe4\xe0\x13\x80\xb1\rpX\x8b\n\xdf\xe3\x9c\u0358\u007f\xeb\xdaMPZk\x8eH\xc6\xf9c\xd9\x03\xc5Q\xbb\xfd\xda\xff'\u05b9\te\xd5\u49db\xf7W5\x1b\x83\xdb\xe0\x1a\x9b\x95\xb4\x9cL\u0186_\xd1p/\xa0\xea=\xe2F\u056e\x89\x02\x95\xe2}T\xac\xab\xde,\x17\x14)#G\xb0.\xd17%Fk\xe9f\xcb\xf1\x15P\ny\xb7\v3\x1aa\xe7Oo\xc3#?\xf8:\x1e\xf8\xe6ux\xe2\xf6\x9bP\xac.\xb5\x03Sp\xe3,CK 8\xe4Y\a\xe3\xb8\xe3\x8e\xdbu\xce9g\xff\x8f\xdf\xff\xfd\xf7\xfd\x15\x11\u0756\xfe\u03bd\xf7\u0783g?\xfb\xd8g\xecu>\x01\xf3g\xc0\xfa\xc8G\xfe_\xfc\xfa\xaf\xbf1\x80\x89\x1cp\uee7f\xf0\xc9o~\xf3\x9b\x17:\xd7\xeeq\xca\xcc8\xe1\u016f\xc1\x19W\xbe\x17sG\x1e\x8b\xe1\xb0@Q\x14\x10g\xe1b-^*Y\xa8\x01\xec\xd5m\xa9\x84\x03\x88\xba\xe3X\u5e44#\x8f|y\xe4mC&\xd1X\xc5\f\xaa\uedd5{nC\xf9\xa6\xcdk\v\xa72\xc6}\xaf\xc5\xe7\xa0e\x02\xb26\xfe)\xf5\r\xa3\xb4}\xa5\xca\u05e6e\a\x8aI;M@\x8f\x9e.\xdeU\xb0\xb1\xb1H;\xdf.\xc9k]\xbe\xee\u912bT NR\u077f\"Ow\xe9\xa0\x15\x8f\x16\u0205\x93\x92B\x89\u0787\"\xd28uH\xa9\x18W\xd1\x06\x81\t\xac2\xe8N\x0ek,\x1e\xbf\xed{\xb8\xef\x9b\xd7\xe1\xc1\xef\u0740\x9d\xf7\xfc\b\xb6\x18\xb5\xa2Q\x19p\x12\xde\xebLkt{\x1d\x9cx\xc2\t\u063a\xf5\xb9?\xb8\xe0\xfc\xf3\xdf\xf1\xcaW\xbd\xfa\xeb\xe9\xaf\xfd\xdd\xdf]\x8d\xcb/\u007f\xdd3\xfe:\x9f\x80\xf93d\xfd\xf8\u01f7\u2913\x9e\v\x00\xf8\xda\u05fe\xb2\xf5\xfd\xef\u007f\xff\u05ef\xbf\xfe\x86\xb9Vr84\x167m=\a[^q%\x8e8\xfbE\xe8\x1e\xf8,\x18\xe3P\f\ap\u0468\xab\x85w\x8d\x00V\xafM\xa5\xf4\xb3\x8eG\xf8\xf8;&\xe1\xc7K\x87\xbb\xa4t\x96\x96t\x84\xb1\xea\xbcm\x80\xa6\r\xe9Z\x88\xf65\xbdRP\xc9\xef\x9a\x00\xdb\x1c\xe0\u065b>[jS\xad\xd5\xf3\xad\x03yx\xf6m\x8a\x97\xb0\xf9U:q\n\x95\xaf\xff\xa6kP[Q:\x18+\xed\u0606\xb4e\xe5N5\xc0\x8f\xf7i\xa4\xd2RF\x8f\x1eA4.KN\x19\xa8d\x89\x11\xc0\xe3\xaeKD\xe0,\x83\xce;p\xd6b\xdb\x1d?\xc4\xdd_\xfe\x14\xee\xff\xe6ux\xf2\xbe\xdb\xd1\xf4\a\x8a\x0f\xa0\x04p?\xf5C\x9dN\x8e\x99\xe9\x19l\u06b4\t\xcf{\xde)\v'\x9ex\u009f\xfc\xd6o\xbd\xf3CD\xb4\v\x00>\xfe\xf1\x8f\xf1k_\xfbknreO\xc0\xfc\x19\xb9n\xba\xe9\xfbt\xdai\xa7\v\x00|\xe2\x13\xff\xf0\x92\xff\xfc\x9f\xff\xeb5\xdf\xfe\xf6wf\xdb>\x16\x14F\xfa\xa7\xd6o\xc0\xc6\u7783\x13^\xf6F\x1cv\xea\xb9PS3\xb0\xc6\xc1\fVk\x8d\xae\x8a\xb2\x90\x06\x8f^\x81I\x12(_\x1bXI\xc1\xbcf\xb0DU\xd5O\r\xad\xf2ZMKip\xed\xcd\x1fn\xca'E\x9a\xbe1\x82*\xbb\xa1\x82cn\xb1*H=l\u02a1\x9e\xf0}\xa6\xf1\xfec\x94]J\xa8d\u02f0\x86\xe4a\xa6\x1b\x9dC}X\b\t\xe0\u01e0\xb44\x06/Fw\xc6\u7903\xc3c\xf4\b72\xde\xe5\xb0R\xdf\\R\x8a\xab&-M^\u0426\xb5B\xa9Ya\x8dNo\n\"\x82'\xef\xbb\x1dw|\xe1\xbf\xe3\xbeo~\x1e\xbb\x1f\xb8\x13f4h1\x81!\x1f$\xce\f\x11'\xe2\x84\xe6\xe6f1;;\x83C\x0e9\x14g\x9f}\xe6\u03a3\x8f>\xfa/^x\xf1%\u007f{\u0496\x93\x1e\x04\x80W\xbe\xf2\x15\xea\x13\x9f\xf8\x94\xdd_\u0331&`>Y\xff\xcb\xeb\xaf\xfe\xea/\xf8Moz\x8b\x03\x80\xab\xaf\xfe\u0605\x1f\xf9\xc8G\xae\xf9\xcew\xbe\xbba4\x1a\x19\xa5\x94\xb2\u05b5\xea3f\x0f9\x1c\x87\x9dz.\x8e\xb9\xe8Wp\xe8\xd6s\xa0{3(\x06\xabp\x81\x96i~\xa4\u04a37S\x134+d\xa5\x06\xa7]w\xf1\xab6\x95\xa6\xba\xa5\xc9\x17\xd7\x14\x19M\xab\xdb=\xad\x06h1\x118\xdcge\x0e\x15\x81\x8f\xcaA\xa7X\xc4:Y;u'\x1d\xb2\xa9\x01:\u0579\xf9\x96t\xb9\xbd6C}\x90\x03\x95\x9bb\xc6\x04\x15\xc0\x19\xa5\xc5,\xd5\f\u04cc\xabd\xa4\xae\xda&\x91\xe4\x8d\xd4\r\xba\xa4\x02o$\x9b\x05\xa5\xd2$q0\u0387C\xe4\xdd\x0e\x96\x1f\u007f\bw\xdf\xf0I\xfc\xf8\u068fc\xe7Oo\x83\xb4T\xe2Q\x99\xa2\x98\xc59g\x8c)p\xd0A\af\x1b6\x1c\x8c\x83\x0f>x\xfb\x19g<\xff\xd6\xd3N;\xf5\u00ff\xf2+\xbfz\x1d\x11-\xc7\u07fb\xfa\xea\x8f\xd2\xeb^\xf7z\x99\\\xc5\x130\x9f\xac\xb0>\xf4\xa1\xbf\xe4\xdf\xfc\xcd7;\x00\xf8\xf4\xa7?y\xc1\xd5W_\xfd\x0f\xdf\xfa\u05b77\xec\xdc\xf9$\x98\u0649\b\xa7>.13\x93\x94\xc6\xf4\xc1\x1b\xb1\xf9\u0717\xe1\x84\u02ee\xc0\xfag\x9f\xfc?\u06fb\xd2\xe0:\xaa3{\uef7d\xbcE\xfb\xd3\xf2$\u02ca6\x8c\x1d/x\x19\xc6\xc66v\x8c\x1c\x98\x94\xcb\xd4\f\x1e\x02\xe4G25E\xe18\x19\xa8\u0250T`H\x82a\x96\x84Jf\x8a\u0270XP@\xa8\n!\xc9\x04\xc8x\x02\x89\a\x13\x1b\xe3\x15\x19[\xc6c\xcb\xd6bY\x8b%\xdbOz\u04b3\x96\xb7t\xf7\xbd\xf3\xa3\xbb_w?=9T*\x10\x92\xf4\xf7G\xb6\xf4\xd4oQ\xdfs\xbf{\xbe\xf3\x9d\x0f\x86a@OM[G\xe5\x1c\x8b\u05dc\xbc\xd3[\xfa\x83\x8bs\xc7\fj\x06\xee6}\v\xa0DNES\xe4aT\x80Y\\\r?\xc0\x02p\x80\xdb\x04t\x99\xda\xca\x0e\x93;\xd6sN\x10N\x929s\xd7\x103\xb2~GuCs\x00\xde\xfe\xbf\xbd9\xe4\xb6\xf0\xbb\xaf\u0248\xa3\xef'\xd9l\xdetx\xb4\u01aaZ\x80k\xda\xc9\xdat\x89\xbb{\x96{\x8a\xc1b\xc6)\x04\x96~\xdcV\xbc\x10\x97g\x8b\xd3[`\x1e\x9f$E\x85\x12\n \x99\xb8\x82\xc1\xf7\xf6\xa1\xfd\xa7O\xa1\xef\u0777\xf2r\xe2\x84R\xd3\b\x8b1\b\xce\rn\x18,\x12)CCC\x03\xaa\xab\xab/,\\\xb8\xf0\xf5\x96\x96\x96\xc7ZZ6\x9es\xff\xde7\xbf\xf9\x10Y\xb6l\x99\xb8\xed\xb6\xbf\xf6\x17\xaf\x0f\xe6~\xe4\u01b7\xbe\xf5M\xf2\xe8\xa3\xff$\x00\xa0\xad\xed\xf0\x86\x1d;\x9e\xf9\xcf\xfd\xfb\x0f,s/\x01\xaf\u0305\xe4\xe4\xa2\xc45\x9d&\x97\x1b\xcf*'0\xcb88\u0324C\x90\x876\u0235\x17\x80\u02e6\xd56\xf9\xa2\xc4\xecj\x14 \x969\x94p\x81\"q\x15\x05\x85\xd7\u070b\xe4Q\x9c\xe4\xd0\x14\x94\x98\xf2=B\xecY\x1e\xf6\xa7 r\xe4\x92\xe6\xc6B@\xac\xe767\x99,\x03\xef\xca\xfeM\x1bY1\xc3\u01c6\xbbOA\xc8\xd5\xec\v\xab\x17\x80xL\xbe$\xd7H;\xee\xca\u0429e\x8aEe\x19JH\x8561\x81\xe1\xe3\xef\xa0\xe3\xcdW\xd0\xf5\xf6N$\xc7F\xf2\x838\xa1`\x8cZ\x9b\tGEy\x05\xe6\u039d\x8b\x9a\x9a\xe8\xb1\r\x1b6\xec\u07f6\ud2ed\x8a\x12<\xed\xfe\xbd\x05\v\xaeEkk+\u05ad\xfb\x94\xbf`}0\xf7\xe3j\xf1\xe0\x83_'\xdf\xfe\xf6cV3\xa5\xa8\u07fe\xfd\xe1\xc7\xf6\xed\xdb\xf7\u064e\x8e3\x88\xc7\u3982\u015d\xa1Sj6n\x00\xe0Z\x06\xc1\xb2J\xd4\u07f8\tsW\u0742\xaa\xeb\u05a00Z\vC3`his\xaa}\x0e\xaa\x12\xe1:\xda\xc3\xd5\xe5\txG\xb3[w\xa6c\x80\ubc3c\xd9\xd6~kX\xb4\x10\xb3\xdf\xd0\xf9\x9a\x98\xf2\xb5\xc5S7\x9dB\\\xad\xed\"\xbf.\xfb>bg\x8ea\xe4\\\a\x92c1\xa4\xa7&\xc0\r}\xc6\xe7I)\u02f6\u061b_\xad\xb1mB\x18\xe9t\x9a\x97\x95\x95\xc9s\xe6\xd4`\xfe\xfc\xf9\xf1\x95+\xff\xfc\xdf\xee\xbf\xffk\xdf%\x84d\xc7\x02}\xe7;\xffJ\x1ex\xe0\x1f}y\xa1\x0f\xe6~|T\xf1\xea\xab?\xc3\xee\u077b\xc9SO\xed\xc8.\xbcw\xde\u0673\xf1\xe5\x97\u007f\xf2\x8d\xe3\xc7O\xac\xef\xec\xec\xc4\xe8\u8a1d\x9cR\x0f\x8d`\x1d\xbd\x05\u7812\x84py\r\xe6,[\x83\xba\x1bnA\xe5\u0095(\xaem\x02Q$\xe8i\u00f4\b\xe0\x1c\x82\xebV\xd6\xeed\xa6\xe6\x14!W\xcb\xd1U-\x02\xf3\xd3(\xf4K\xd5\x00\x00\v\xecIDAT\"\x1e\x83+K\x8d\xe1\xc9\xfc\x89\xfd\xe2g\aL\x9b\xe20r&M\xb8\x9b\x99f{\x1dW\x1b\xd6\xecH\xfeL\x12;+[\xb4~n\xb8\xb2\xf3|\x1c8r\b!\x02\xaf2\x881\x06EU!)\x12\x88\x00\u04898&c\x171q\xb1\x0f\xa3\xbd\x1d\x88u\x9d\xc4\u04296\\\x19\ua0de\x9a\xc2\f{\a\xf7\xe9\x8b8~\xf2\x0emC@\b\x11\x9a\x96\xe1\xa1P\x98\u035bw\r\xea\xea\xea\x06\x97-[\xba\xf3\xcb_\xfe\u04bfG\"\x95=\xeekuvv`\u07bc\x05\xfe\xe2\xf2\xc1\u070f\xdfG\xb4\xb6>\x8d\xad[\xb7\xb9\x8e\xf9B~\xe9\xa5\x1f\u07bdk\u05ee;\xfb\xfb\a\u059dF\xb2lR.yd\x83e\xd6\xf8\xca*\x80:\x1b\x94p\x9aw(1\x1b\xf8)\x01\xb3(-JMb\x852\n\x89\x02Z:\x83\x89K\x03&h\xf7\x9e\xc1X\xdfY\x8c\xf5wc|\xa0\v\x13\x17\a\xbd\xe3\xe0(3yn\xeb\x14E\x89W\xc6#\xf2\fN\x15B\x80R\x02YV \xcb\x12\x1a\x1b\x1b\xd1\xdc\xdc<\x19\x8dV\xb5\xdeu\u05dd?Y\xb5jM\x9b\xfb\xf1\xdf\xff\xfe\xe3\xb8\uffbf\xf7\x17\x90\x0f\xe6~|\x9c\xe2\x95W\xfe\v[\xb6\u070e#G\x0ea\u03de\xbd\xec\x81\a\x1e\xb4\x95/U/\xbf\xfc\u04a7\xdb\xdbO\xdc\xdd\xd6vt\xfd\xa9S\xa7\x90H$\x90N\xa7=L\xc5L\n\xc603hj\x82<\xa1\x14L\x0e@\t\x17A\n\x85!\a\v\xa0\x84\x8b\xa0\x16\x95!\x14\xa9B\xb8j.\xc2\x15s\x10(.\x85\x1a.B\xb0\xb8\x14\xc1\x92\n\xa8\x85%\x90\x14\x15\xce \x04\x0eC7\xcci\xedB\x98\x13L\x05\xcf\x0e\f\xa6\xd4\xd2e\u00dc\x96\x03 ;\xcb\xd46\xb0\u02b5\x9b\xe5B@\xe3f\xe1\xd5\x00\xcd\u038c\x13v\xeaL\b\xe0\x18E\xb9\xc6\u06f9\xac\xa9,>\u01b1\x036\v\xae\xb6\xbe\x9c\x11\xc7!\xd11,s\rL&f\x87'\x17\x02\f\xe6&\x96\x99\x9e\u0115\xe1~\x8c\xf5w\"~\xfe,F{;\x90\x18\xee\xc3\xc4\xf0\x00&b\x17\xc0u\x87\xf3\xa6L\u029aY\x99\xb2A\xe21\xf52\x9d\x10E\xdeF,\xc30\x04!\x84\x84B!\x14\x15\x15\xa2\xb1\xb1\x11\x8b\x17/>\xdd\xd4\xd4\xf8\x8b\xa5K\x97\xbd|\xd3M-\xed\x00\xb0~\xfd\x8dt\u06f6m\xb8\xf3\xce\xcf\xf9\xea\x14\x1f\xcc\xfd\xf8C\x897\xde\xf8\x05}\xe3\x8d_\xb2'\x9ex\xd2V\xbe\x84\u007f\xf0\x83\x176\x1f=\xfa\u07b7\x8e\x1e=\xba``\xa0\x1f\xb1X\f\x9a\xa6\xe7-\t\xba\xb3C\x13\xe0I6\xd3\x16\x82Cp\xd7\fw\xca \xa9A\u0221\x02H\x81\x10\xa4@\x10J\xa8\x00\x81\xc2R\x84\"QD\x1a\xe6#\u04bc\b\xa5\x9f\xb8\x16\xe1\xb2JH\x8a\nI\x92 I\xd41\xc4\x12\x00\xb8\x01\xce-B\xc5eW\xeb1\x8d\xb2\xa8\ffm2 \x04\x19\x01d,x\xd2\f\xb3\u02d2[\x1bD\x96\n\xe2\xe6\tA\b\x93'2\xbf\xaf\xc3\xd0\xd20\xd2Ip]\x03\xd7M\x1baSJHA\xc0A\xb9n\x82\xb3\xaeA\x18:\fC\x03\x84\x80\x1a.\x82\x1c\f\x9b\x12A-\x8d\xf4D\x02S\xf1\u02d8\x8c\rc\xf2R?\x12\xc3\x03\x98\x8a_F*1\x8aTb\x14\xc9\u0118\xc7\a\x85P\x06\xcaXv\x93\xb3\v\x964\x0f\x80C\x88\x19\x12Ka\x06!\x04(**FYY\x19\xae\xb9\xa6\x19MM\x8d?^\xb0`\xc1\xd3\u06f6}\xb9\x8b\x102l?]k\xeb\xd3l\xeb\xd6m\xba\xbf2|0\xf7\xe3\x0f4\xac\x81\xd1n\xe5\vmm\xdd\xf1\xb9\u00c7\x0f\xdf\u007f\xf2\xe4\xff-\x1d\x19\x19\xc1\xf0\xf002\x99\x8cA\b\x04\xa5L2\v\x9d9\x1c\xac\xc5E\x10\xf7\x84g\xe7\xa2\x10\xdc\u021b5RJ!\a\xc3P\vK\x10,-G\u025cFT^\xb3\b%\xd1Z\x14\x94U \\Z\t\xb5\xa8\x14r(\fI\t\x80I2d\x89\x81P\xe6iN2\v\x8f\xe6f\xc2u\x03\x84\xeb\xa0\xe0\xa000\x9d\xd611\x95\xc4\u0115+\x98\x1a\x1f\xc5\xd4D\x02\xa9\xa9\t\xe8\xa9$2\xa9\x142\x13cHO\x8cCKN\xc2H\xa7`d\xd204\xf3\xab\x9eJ\x82k\xa6m\xb00\f\ap\xadZ\x02\xb1\xc0\x94s\xdd,\x06\x1b\xe6\xfb\xb4\x1b\xb3\f-\x03\xaee\xa0k\x19\xf3\u07fav\xf5\x05\x9d\x95\x8bRPP\xc7\xc7\x1c\x8e\u01ce;\x03'\xc43(\x9b\xeb\xban\b\xc1%UUI$\x12AYY\x19\xae\xbdv~b\xf9\xf2\xa5\xaf/_\xbe\xec_n\xbe\xf93\x1e>\xfc\xb5\xd7^!\xd5\xd5Q\xb1j\xd5\x1a\u007f1\xf8`\xee\xc7\x1fC\xec\xdc\xf9sr\xeb\xad\u007f)r8\u04ed\x1d\x1d\x1d[\xbb\xba\xba\xe7\x0f\r\r\x05c\xb1\x18\xe2\xf11!\x047\b\xa1T\bn+(H.\x1d\xe3\xf6Gw\xeeT\xe2\x92\xf59\xed\xa3B\x18.\x87?\x02I\r\x80\xc92\x98\xacB\t\x86\xa0\x14\x14\xa1\xb8\xbc\x1a\x85\x955\b\x16\x95@\r\x86!)*\x18\xa5Y!$#\x020t\x18\x99\f2\xa9i\xd3\xe27\x93\x82\xd0\u04d8\xba2\x8exl\x04\x93\x93W\x90\x9e\x9e\x86\xaee\xc0\r\xdd\x04^+\x03\xe7\u0724v\x84\xf8\xe8Tw6\x10\x13W\xb3\x8eS\xb4\x9c\xc9w\xbb5\xdd\xc4\xf1+\x067\x83J\x12#\x95\x95\x95(++CAA\xc1x]]]\xfb\xfa\xf5\xeb\xf7\xdes\xcf\u059f\x12B:\xdc\xd7{\xf0\xc1\xaf\x93{\xef\xbdW\xd4\xd4\xd4\xfa7\xbf\x0f\xe6~\xfc\xb1\xc5\xd3O?\x85S\xa7N\x91'\x9ex\u049d\xa9\xb3C\x87\x0e\u073as\xe7\xceM\xdd\xdd=\xab\x93\xc9\xe4\x82X,\x86\x81\x81ALOOA\xd7uLO'!\xc4\a2\xac\xbd\u028dLr\xe7\xd2\xffN\x16E\xde\xd1l\x1f\xe5\x02\xf5H\x02]_\xad\xb6y\u4302\xcb97y\u078d\xfd\x18\xce\x058\xe7\x90$\t\xaa\xaa\xa2\xa8\xa8\b\xd1h\x14\x91H\xd9XII\xc9\u19a6\xa6C\x1b7\xb6\x1c\xfd\u0527n\xdaE\b\xf1\xf0\xdfo\xbe\xf9+|\xfa\xd3\u007f\xe1\xdf\xec>\x98\xfb\xf1\xa7\x10\xb3\xa9\x18\x84\xe0\u05fe\xf8\xe2\x8b\x1b\xdb\xdb\xdb\xe7\uaeb6\xe2\u0295\x89\xf2s\xe7\xceM\x85\xc3\xe1\u56a6\x05{{\xcf#\x91H \x93I\xc308\f\xc3@&\x93\xf9\xe3\\x\xb9Y2!\x1e\xd31\xea\xea\xc1w\x17D\xed,;\xdf\x06f\u007f\x9fR{\xec\x9a\xd5\xf9i\xa9V\x18cP\xd5\x00**\xcaQ[[\v\u01a4\xe9`0\xd0Y]]=\xac\xaa\u02ae\xdbn\xdbrv\u035a\xb5\xbf\xca\u05d5\xb9k\xd7/q\xcb-\x9f\xf1on\x1f\xcc\xfd\xf8S\x8c\x8b\x17\x87\x10\x8d\xd6`p\xb0\x8f\xd4\xd6~B\xe4\x1c\xfb\xab\x00\x94\xec\u077b7\xd9\xdb\xdb3\xaf\xaf\xaf\xaf\xee\u0295\xc9\x06Bp\xfd\xc8H\x8c\xc6b#)JiEQQ\u046a\u02d7/c``\x10\x13\x13\x13\u0434\f4M\x83\xae\x1b0\f\x03\x9a\xa6\x9b<\xb7\xdbf\xe0Cj\x11\xbf\x9a\x89\x96\x99\a\x93\x19\xf3D\xb3\t\xf2l\xb9\xbdG\xcbm6\xfa0f\xd3%\xc4j\xa42m\x87\u0740\x9d}FB!\u02e6R\xc56\xb7b\x8cZ\xe0mf\u07a5\xa5%\xa8\xad\xadE0\x18\ucaac\xac\xec\f\x04\x82GC\xa1`\xe7\xbcy\u05cc\xb6\xb4l8\x1f\x8d\xd6^$\x84$\xec\xabvv\x9e!\xf3\xe6\xcd\x17\x00\xb0c\xc7S\u063cy3\xe6\u0319\xeb\xdf\xd0>\x98\xfb\xf1\xa7\x1c==\x9dhj\x9a\a!\x04v\xee\xfc9\x89\xc5b\xec\xc9'\x9f\x16\u01cf\x1f7\xf2\x03\xa6\xa8\xb0\xeeK\xbd\xa3\xe3T\xb8\xaf\xaf\u007fN[[[@Q\xe4\u0159Lf\xe5\u0673g\xb5\xe9\xe9diII\xf1\xba\x8b\x17/\x97\x0e\f\f`||\x1c\xba\xae!\x93\u0450L\x9a\xfe/6\xd8Sk B~\x80\xcf\xef\xed2\xc3\xfb\u071e=*\x04r\x8b\xb7\xb2,\x83Rj6\xf5\xe4\\U\x96e(\xb2\x9c\x05W\x0f\xc7M\x88w,\x9b\x95=\xeb\xba\x0e\u039d\xe9M\xf6\xefp\xceA)\x81$IP\x14\x15\xb2,\x811\x06J\x19dYBqq\xb1\x88F\xa3\xa4\xa4\xa48\x1d\x8f\xc7\x0f\x8c\x8d\x8d\x9fkhh\b44\u05031\xe98\xe7\xbcm\xf5\xea\x1b\x86W\xaf^;F\b\x19\xcd}\xcf\xf7\xddw\xaf\xb4b\xc5r\xb1l\xd92\xbed\xc9R\xf1\xfa\xeb\xff\x83M\x9b6\xfb7\xb0\x0f\xe6~\xf8q\xb5\fW\xe0\u0529\xf7iOO/y\xfb\xed}(,\f\xd3D\xe2\n}\xfc\xf1\xffH\xff\x86\xdf\v\x02\x10\xa9\u0514|\xe4\xc8\xe1\u04b6\xb6cJEE\xf9\xc6\u04e7;\x16\x8d\x8c\u0116\x8d\x8c\x8c.\x1e\x1a\x1a\n'\x12\xe3\xd40\xcc\x06\x97t:\x8d\xa9\xa9)LM%a\xaa\r\t4M\x87\xa6i\xf9\x93k\x80\u0232\fIbfc\x90p\x18sU\r\xa0\xa8\xa8\x10\x81@\x00\x92$\x81s\x81d2\t\x00P\x14\x05\xc1`\x00\x92$\x9bEXY\x02!$I\b\x9d,..DAA\x01\x14EE \xa0BUU(\x8a\n\xc6X\xf6\x89UU\xa5\xc1`\x80^\xb80\xb4\xef\xfc\xf9\xf3\xa7\xc2\xe10K\xa7\u04d9\xea\xea\xea\xe6`0T\xdc\xdf\xdfwZQ\x14c\xee\u0739\xa4\xbe\xbe\x1e\xc5\xc5E\b\x85B2\x80\xc1c\xc7\xda\xf7LNN&\u05acY%777\x19\r\r\r\xf1H\xa4j\n\xf6\xdc\rB\x92\xb9\xeb~\xfb\xf6\x87\x95T*i\xd4\xd7\u05cbU\xabV\x8a\xa5KW\xf8\xdap\x1f\xcc\xfd\xf0\xe3\u00cb\xae\xae3\u063d\xfb\xd7\u0636\xedK\xbf\xf1\xb1\xd3\xd3\x13U\x87\x0e\x1d^\xb7\u007f\xff\x81\x8at:\xb5H\b\\\xdf\xdd\u074dX,\x16\xe6\\\xd4%\x12\t#\x95JrEQKB\xa1\x90\xb5A8\x14\rc\fB\b$\x12\t=\x9dNO\xaa\xaaJM\xe0UH(\x14\x82,+\xa3\x15\x15\x91X}}=\"\x91\x88$\x84\xb8\xd4\xd9\xd9\xf9v:\x9d\x89777K\xcd\xcdM(..\x86$1Z\\\\\"\x15\x16\x16\xbe;22z|\xf5\xea\x1b\fYV\xd9\aY\x8b\x84\x90\xe9<\x9b\x98G\xfe\xf9\xdb\xc4\x0f\u007f\xf8\"\u05ad[\x87\xba\xba\x06\xff\xa6\xf2\xc1\xdc\x0f?>>\xd1\xde\xfe\x1e\x1e}\xf4\x9f\xf1\uaaef}\x90\x93@\xf9\x89\x13\xc7n>r\xa4-}\xe6\xcc\x19\xbd\xb2\xb2\xe2\x9a\xf2\xf2\xc8\n\xceE\x88s\xce3\x99\x8c\xb0\xb2c\x99\x10\x92\xec\xed=\xff\xf6\x85\v\x17\x06\xeb\xea\xeaX]]\x1d\xa9\xae\x8e\xb2\x86\x86\x06\xb2h\u0452vB\xc8\u064f\xe3\xe71<<\x88\xeaj_&\xe8\x87\x1f~\xfc\x81\xc7\xd0\xd0\xc0\xc7\xf2u\xdd\u007f\xff\xdfbp\xb0\xdf\xff\x03\xf9\xf1\xa1\xc7\xff\x03 \x99\rH\xa6\x0e6\x92\x00\x00\x00\x00IEND\xaeB`\x82")
-
-// dashboardDockerfile is the Dockerfile required to build an dashboard container
-// to aggregate various private network services under one easily accessible page.
-var dashboardDockerfile = `
-FROM mhart/alpine-node:latest
-
-WORKDIR /usr/app
-
-RUN \
- npm install connect serve-static && \
- \
- echo 'var connect = require("connect");' > server.js && \
- echo 'var serveStatic = require("serve-static");' >> server.js && \
- echo 'connect().use(serveStatic("/dashboard")).listen(80, function(){' >> server.js && \
- echo ' console.log("Server running on 80...");' >> server.js && \
- echo '});' >> server.js
-
-ADD {{.Network}}.json /dashboard/{{.Network}}.json
-ADD {{.Network}}-cpp.json /dashboard/{{.Network}}-cpp.json
-ADD {{.Network}}-harmony.json /dashboard/{{.Network}}-harmony.json
-ADD {{.Network}}-parity.json /dashboard/{{.Network}}-parity.json
-ADD {{.Network}}-python.json /dashboard/{{.Network}}-python.json
-ADD index.html /dashboard/index.html
-ADD puppeth.png /dashboard/puppeth.png
-
-EXPOSE 80
-
-CMD ["node", "./server.js"]
-`
-
-// dashboardComposefile is the docker-compose.yml file required to deploy and
-// maintain an service aggregating dashboard.
-var dashboardComposefile = `
-version: '2'
-services:
- dashboard:
- build: .
- image: {{.Network}}/dashboard{{if not .VHost}}
- ports:
- - "{{.Port}}:80"{{end}}
- environment:
- - ETHSTATS_PAGE={{.EthstatsPage}}
- - EXPLORER_PAGE={{.ExplorerPage}}
- - WALLET_PAGE={{.WalletPage}}
- - FAUCET_PAGE={{.FaucetPage}}{{if .VHost}}
- - VIRTUAL_HOST={{.VHost}}{{end}}
- logging:
- driver: "json-file"
- options:
- max-size: "1m"
- max-file: "10"
- restart: always
-`
-
-// deployDashboard deploys a new dashboard container to a remote machine via SSH,
-// docker and docker-compose. If an instance with the specified network name
-// already exists there, it will be overwritten!
-func deployDashboard(client *sshClient, network string, conf *config, config *dashboardInfos, nocache bool) ([]byte, error) {
- // Generate the content to upload to the server
- workdir := fmt.Sprintf("%d", rand.Int63())
- files := make(map[string][]byte)
-
- dockerfile := new(bytes.Buffer)
- template.Must(template.New("").Parse(dashboardDockerfile)).Execute(dockerfile, map[string]interface{}{
- "Network": network,
- })
- files[filepath.Join(workdir, "Dockerfile")] = dockerfile.Bytes()
-
- composefile := new(bytes.Buffer)
- template.Must(template.New("").Parse(dashboardComposefile)).Execute(composefile, map[string]interface{}{
- "Network": network,
- "Port": config.port,
- "VHost": config.host,
- "EthstatsPage": config.ethstats,
- "ExplorerPage": config.explorer,
- "FaucetPage": config.faucet,
- })
- files[filepath.Join(workdir, "docker-compose.yaml")] = composefile.Bytes()
-
- statsLogin := fmt.Sprintf("yournode:%s", conf.ethstats)
- if !config.trusted {
- statsLogin = ""
- }
- indexfile := new(bytes.Buffer)
- bootCpp := make([]string, len(conf.bootnodes))
- for i, boot := range conf.bootnodes {
- bootCpp[i] = "required:" + strings.TrimPrefix(boot, "enode://")
- }
- bootHarmony := make([]string, len(conf.bootnodes))
- for i, boot := range conf.bootnodes {
- bootHarmony[i] = fmt.Sprintf("-Dpeer.active.%d.url=%s", i, boot)
- }
- bootPython := make([]string, len(conf.bootnodes))
- for i, boot := range conf.bootnodes {
- bootPython[i] = "'" + boot + "'"
- }
- template.Must(template.New("").Parse(dashboardContent)).Execute(indexfile, map[string]interface{}{
- "Network": network,
- "NetworkID": conf.Genesis.Config.ChainID,
- "NetworkTitle": strings.Title(network),
- "EthstatsPage": config.ethstats,
- "ExplorerPage": config.explorer,
- "FaucetPage": config.faucet,
- "GethGenesis": network + ".json",
- "Bootnodes": conf.bootnodes,
- "BootnodesFlat": strings.Join(conf.bootnodes, ","),
- "Ethstats": statsLogin,
- "Ethash": conf.Genesis.Config.Ethash != nil,
- "CppGenesis": network + "-cpp.json",
- "CppBootnodes": strings.Join(bootCpp, " "),
- "HarmonyGenesis": network + "-harmony.json",
- "HarmonyBootnodes": strings.Join(bootHarmony, " "),
- "ParityGenesis": network + "-parity.json",
- "PythonGenesis": network + "-python.json",
- "PythonBootnodes": strings.Join(bootPython, ","),
- "Homestead": conf.Genesis.Config.HomesteadBlock,
- "Tangerine": conf.Genesis.Config.EIP150Block,
- "Spurious": conf.Genesis.Config.EIP155Block,
- "Byzantium": conf.Genesis.Config.ByzantiumBlock,
- "Constantinople": conf.Genesis.Config.ConstantinopleBlock,
- "ConstantinopleFix": conf.Genesis.Config.PetersburgBlock,
- })
- files[filepath.Join(workdir, "index.html")] = indexfile.Bytes()
-
- // Marshal the genesis spec files for go-ethereum and all the other clients
- genesis, _ := conf.Genesis.MarshalJSON()
- files[filepath.Join(workdir, network+".json")] = genesis
- files[filepath.Join(workdir, "puppeth.png")] = dashboardMascot
-
- // Upload the deployment files to the remote server (and clean up afterwards)
- if out, err := client.Upload(files); err != nil {
- return out, err
- }
- defer client.Run("rm -rf " + workdir)
-
- // Build and deploy the dashboard service
- if nocache {
- return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s build --pull --no-cache && docker-compose -p %s up -d --force-recreate --timeout 60", workdir, network, network))
- }
- return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s up -d --build --force-recreate --timeout 60", workdir, network))
-}
-
-// dashboardInfos is returned from a dashboard status check to allow reporting
-// various configuration parameters.
-type dashboardInfos struct {
- host string
- port int
- trusted bool
-
- ethstats string
- explorer string
- faucet string
-}
-
-// Report converts the typed struct into a plain string->string map, containing
-// most - but not all - fields for reporting to the user.
-func (info *dashboardInfos) Report() map[string]string {
- return map[string]string{
- "Website address": info.host,
- "Website listener port": strconv.Itoa(info.port),
- "Ethstats service": info.ethstats,
- "Explorer service": info.explorer,
- "Faucet service": info.faucet,
- }
-}
-
-// checkDashboard does a health-check against a dashboard container to verify if
-// it's running, and if yes, gathering a collection of useful infos about it.
-func checkDashboard(client *sshClient, network string) (*dashboardInfos, error) {
- // Inspect a possible ethstats container on the host
- infos, err := inspectContainer(client, fmt.Sprintf("%s_dashboard_1", network))
- if err != nil {
- return nil, err
- }
- if !infos.running {
- return nil, ErrServiceOffline
- }
- // Resolve the port from the host, or the reverse proxy
- port := infos.portmap["80/tcp"]
- if port == 0 {
- if proxy, _ := checkNginx(client, network); proxy != nil {
- port = proxy.port
- }
- }
- if port == 0 {
- return nil, ErrNotExposed
- }
- // Resolve the host from the reverse-proxy and configure the connection string
- host := infos.envvars["VIRTUAL_HOST"]
- if host == "" {
- host = client.server
- }
- // Run a sanity check to see if the port is reachable
- if err = checkPort(host, port); err != nil {
- log.Warn("Dashboard service seems unreachable", "server", host, "port", port, "err", err)
- }
- // Container available, assemble and return the useful infos
- return &dashboardInfos{
- host: host,
- port: port,
- ethstats: infos.envvars["ETHSTATS_PAGE"],
- explorer: infos.envvars["EXPLORER_PAGE"],
- faucet: infos.envvars["FAUCET_PAGE"],
- }, nil
-}
diff --git a/cmd/puppeth/module_ethstats.go b/cmd/puppeth/module_ethstats.go
deleted file mode 100644
index abed4db5fc..0000000000
--- a/cmd/puppeth/module_ethstats.go
+++ /dev/null
@@ -1,176 +0,0 @@
-// Copyright 2017 The go-ethereum Authors
-// This file is part of go-ethereum.
-//
-// go-ethereum is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// go-ethereum is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with go-ethereum. If not, see .
-
-package main
-
-import (
- "bytes"
- "fmt"
- "math/rand"
- "path/filepath"
- "strconv"
- "strings"
- "text/template"
-
- "github.com/ethereum/go-ethereum/log"
-)
-
-// ethstatsDockerfile is the Dockerfile required to build an ethstats backend
-// and associated monitoring site.
-var ethstatsDockerfile = `
-FROM puppeth/ethstats:latest
-
-RUN echo 'module.exports = {trusted: [{{.Trusted}}], banned: [{{.Banned}}], reserved: ["yournode"]};' > lib/utils/config.js
-`
-
-// ethstatsComposefile is the docker-compose.yml file required to deploy and
-// maintain an ethstats monitoring site.
-var ethstatsComposefile = `
-version: '2'
-services:
- ethstats:
- build: .
- image: {{.Network}}/ethstats
- container_name: {{.Network}}_ethstats_1{{if not .VHost}}
- ports:
- - "{{.Port}}:3000"{{end}}
- environment:
- - WS_SECRET={{.Secret}}{{if .VHost}}
- - VIRTUAL_HOST={{.VHost}}{{end}}{{if .Banned}}
- - BANNED={{.Banned}}{{end}}
- logging:
- driver: "json-file"
- options:
- max-size: "1m"
- max-file: "10"
- restart: always
-`
-
-// deployEthstats deploys a new ethstats container to a remote machine via SSH,
-// docker and docker-compose. If an instance with the specified network name
-// already exists there, it will be overwritten!
-func deployEthstats(client *sshClient, network string, port int, secret string, vhost string, trusted []string, banned []string, nocache bool) ([]byte, error) {
- // Generate the content to upload to the server
- workdir := fmt.Sprintf("%d", rand.Int63())
- files := make(map[string][]byte)
-
- trustedLabels := make([]string, len(trusted))
- for i, address := range trusted {
- trustedLabels[i] = fmt.Sprintf("\"%s\"", address)
- }
- bannedLabels := make([]string, len(banned))
- for i, address := range banned {
- bannedLabels[i] = fmt.Sprintf("\"%s\"", address)
- }
-
- dockerfile := new(bytes.Buffer)
- template.Must(template.New("").Parse(ethstatsDockerfile)).Execute(dockerfile, map[string]interface{}{
- "Trusted": strings.Join(trustedLabels, ", "),
- "Banned": strings.Join(bannedLabels, ", "),
- })
- files[filepath.Join(workdir, "Dockerfile")] = dockerfile.Bytes()
-
- composefile := new(bytes.Buffer)
- template.Must(template.New("").Parse(ethstatsComposefile)).Execute(composefile, map[string]interface{}{
- "Network": network,
- "Port": port,
- "Secret": secret,
- "VHost": vhost,
- "Banned": strings.Join(banned, ","),
- })
- files[filepath.Join(workdir, "docker-compose.yaml")] = composefile.Bytes()
-
- // Upload the deployment files to the remote server (and clean up afterwards)
- if out, err := client.Upload(files); err != nil {
- return out, err
- }
- defer client.Run("rm -rf " + workdir)
-
- // Build and deploy the ethstats service
- if nocache {
- return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s build --pull --no-cache && docker-compose -p %s up -d --force-recreate --timeout 60", workdir, network, network))
- }
- return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s up -d --build --force-recreate --timeout 60", workdir, network))
-}
-
-// ethstatsInfos is returned from an ethstats status check to allow reporting
-// various configuration parameters.
-type ethstatsInfos struct {
- host string
- port int
- secret string
- config string
- banned []string
-}
-
-// Report converts the typed struct into a plain string->string map, containing
-// most - but not all - fields for reporting to the user.
-func (info *ethstatsInfos) Report() map[string]string {
- return map[string]string{
- "Website address": info.host,
- "Website listener port": strconv.Itoa(info.port),
- "Login secret": info.secret,
- "Banned addresses": strings.Join(info.banned, "\n"),
- }
-}
-
-// checkEthstats does a health-check against an ethstats server to verify whether
-// it's running, and if yes, gathering a collection of useful infos about it.
-func checkEthstats(client *sshClient, network string) (*ethstatsInfos, error) {
- // Inspect a possible ethstats container on the host
- infos, err := inspectContainer(client, fmt.Sprintf("%s_ethstats_1", network))
- if err != nil {
- return nil, err
- }
- if !infos.running {
- return nil, ErrServiceOffline
- }
- // Resolve the port from the host, or the reverse proxy
- port := infos.portmap["3000/tcp"]
- if port == 0 {
- if proxy, _ := checkNginx(client, network); proxy != nil {
- port = proxy.port
- }
- }
- if port == 0 {
- return nil, ErrNotExposed
- }
- // Resolve the host from the reverse-proxy and configure the connection string
- host := infos.envvars["VIRTUAL_HOST"]
- if host == "" {
- host = client.server
- }
- secret := infos.envvars["WS_SECRET"]
- config := fmt.Sprintf("%s@%s", secret, host)
- if port != 80 && port != 443 {
- config += fmt.Sprintf(":%d", port)
- }
- // Retrieve the IP banned list
- banned := strings.Split(infos.envvars["BANNED"], ",")
-
- // Run a sanity check to see if the port is reachable
- if err = checkPort(host, port); err != nil {
- log.Warn("Ethstats service seems unreachable", "server", host, "port", port, "err", err)
- }
- // Container available, assemble and return the useful infos
- return ðstatsInfos{
- host: host,
- port: port,
- secret: secret,
- config: config,
- banned: banned,
- }, nil
-}
diff --git a/cmd/puppeth/module_explorer.go b/cmd/puppeth/module_explorer.go
deleted file mode 100644
index 3812f9fdb9..0000000000
--- a/cmd/puppeth/module_explorer.go
+++ /dev/null
@@ -1,194 +0,0 @@
-// Copyright 2017 The go-ethereum Authors
-// This file is part of go-ethereum.
-//
-// go-ethereum is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// go-ethereum is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with go-ethereum. If not, see .
-
-package main
-
-import (
- "bytes"
- "fmt"
- "html/template"
- "math/rand"
- "path/filepath"
- "strconv"
- "strings"
-
- "github.com/ethereum/go-ethereum/log"
-)
-
-// explorerDockerfile is the Dockerfile required to run a block explorer.
-var explorerDockerfile = `
-FROM puppeth/blockscout:latest
-
-ADD genesis.json /genesis.json
-RUN \
- echo 'geth --cache 512 init /genesis.json' > explorer.sh && \
- echo $'geth --networkid {{.NetworkID}} --syncmode "full" --gcmode "archive" --port {{.EthPort}} --bootnodes {{.Bootnodes}} --ethstats \'{{.Ethstats}}\' --cache=512 --http --http.api "net,web3,eth,debug,txpool" --http.corsdomain "*" --http.vhosts "*" --ws --ws.origins "*" --exitwhensynced' >> explorer.sh && \
- echo $'exec geth --networkid {{.NetworkID}} --syncmode "full" --gcmode "archive" --port {{.EthPort}} --bootnodes {{.Bootnodes}} --ethstats \'{{.Ethstats}}\' --cache=512 --http --http.api "net,web3,eth,debug,txpool" --http.corsdomain "*" --http.vhosts "*" --ws --ws.origins "*" &' >> explorer.sh && \
- echo '/usr/local/bin/docker-entrypoint.sh postgres &' >> explorer.sh && \
- echo 'sleep 5' >> explorer.sh && \
- echo 'mix do ecto.drop --force, ecto.create, ecto.migrate' >> explorer.sh && \
- echo 'mix phx.server' >> explorer.sh
-
-ENTRYPOINT ["/bin/sh", "explorer.sh"]
-`
-
-// explorerComposefile is the docker-compose.yml file required to deploy and
-// maintain a block explorer.
-var explorerComposefile = `
-version: '2'
-services:
- explorer:
- build: .
- image: {{.Network}}/explorer
- container_name: {{.Network}}_explorer_1
- ports:
- - "{{.EthPort}}:{{.EthPort}}"
- - "{{.EthPort}}:{{.EthPort}}/udp"{{if not .VHost}}
- - "{{.WebPort}}:4000"{{end}}
- environment:
- - ETH_PORT={{.EthPort}}
- - ETH_NAME={{.EthName}}
- - BLOCK_TRANSFORMER={{.Transformer}}{{if .VHost}}
- - VIRTUAL_HOST={{.VHost}}
- - VIRTUAL_PORT=4000{{end}}
- volumes:
- - {{.Datadir}}:/opt/app/.ethereum
- - {{.DBDir}}:/var/lib/postgresql/data
- logging:
- driver: "json-file"
- options:
- max-size: "1m"
- max-file: "10"
- restart: always
-`
-
-// deployExplorer deploys a new block explorer container to a remote machine via
-// SSH, docker and docker-compose. If an instance with the specified network name
-// already exists there, it will be overwritten!
-func deployExplorer(client *sshClient, network string, bootnodes []string, config *explorerInfos, nocache bool, isClique bool) ([]byte, error) {
- // Generate the content to upload to the server
- workdir := fmt.Sprintf("%d", rand.Int63())
- files := make(map[string][]byte)
-
- dockerfile := new(bytes.Buffer)
- template.Must(template.New("").Parse(explorerDockerfile)).Execute(dockerfile, map[string]interface{}{
- "NetworkID": config.node.network,
- "Bootnodes": strings.Join(bootnodes, ","),
- "Ethstats": config.node.ethstats,
- "EthPort": config.node.port,
- })
- files[filepath.Join(workdir, "Dockerfile")] = dockerfile.Bytes()
-
- transformer := "base"
- if isClique {
- transformer = "clique"
- }
- composefile := new(bytes.Buffer)
- template.Must(template.New("").Parse(explorerComposefile)).Execute(composefile, map[string]interface{}{
- "Network": network,
- "VHost": config.host,
- "Ethstats": config.node.ethstats,
- "Datadir": config.node.datadir,
- "DBDir": config.dbdir,
- "EthPort": config.node.port,
- "EthName": getEthName(config.node.ethstats),
- "WebPort": config.port,
- "Transformer": transformer,
- })
- files[filepath.Join(workdir, "docker-compose.yaml")] = composefile.Bytes()
- files[filepath.Join(workdir, "genesis.json")] = config.node.genesis
-
- // Upload the deployment files to the remote server (and clean up afterwards)
- if out, err := client.Upload(files); err != nil {
- return out, err
- }
- defer client.Run("rm -rf " + workdir)
-
- // Build and deploy the boot or seal node service
- if nocache {
- return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s build --pull --no-cache && docker-compose -p %s up -d --force-recreate --timeout 60", workdir, network, network))
- }
- return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s up -d --build --force-recreate --timeout 60", workdir, network))
-}
-
-// explorerInfos is returned from a block explorer status check to allow reporting
-// various configuration parameters.
-type explorerInfos struct {
- node *nodeInfos
- dbdir string
- host string
- port int
-}
-
-// Report converts the typed struct into a plain string->string map, containing
-// most - but not all - fields for reporting to the user.
-func (info *explorerInfos) Report() map[string]string {
- report := map[string]string{
- "Website address ": info.host,
- "Website listener port ": strconv.Itoa(info.port),
- "Ethereum listener port ": strconv.Itoa(info.node.port),
- "Ethstats username": info.node.ethstats,
- }
- return report
-}
-
-// checkExplorer does a health-check against a block explorer server to verify
-// whether it's running, and if yes, whether it's responsive.
-func checkExplorer(client *sshClient, network string) (*explorerInfos, error) {
- // Inspect a possible explorer container on the host
- infos, err := inspectContainer(client, fmt.Sprintf("%s_explorer_1", network))
- if err != nil {
- return nil, err
- }
- if !infos.running {
- return nil, ErrServiceOffline
- }
- // Resolve the port from the host, or the reverse proxy
- port := infos.portmap["4000/tcp"]
- if port == 0 {
- if proxy, _ := checkNginx(client, network); proxy != nil {
- port = proxy.port
- }
- }
- if port == 0 {
- return nil, ErrNotExposed
- }
- // Resolve the host from the reverse-proxy and the config values
- host := infos.envvars["VIRTUAL_HOST"]
- if host == "" {
- host = client.server
- }
- // Run a sanity check to see if the devp2p is reachable
- p2pPort := infos.portmap[infos.envvars["ETH_PORT"]+"/tcp"]
- if err = checkPort(host, p2pPort); err != nil {
- log.Warn("Explorer node seems unreachable", "server", host, "port", p2pPort, "err", err)
- }
- if err = checkPort(host, port); err != nil {
- log.Warn("Explorer service seems unreachable", "server", host, "port", port, "err", err)
- }
- // Assemble and return the useful infos
- stats := &explorerInfos{
- node: &nodeInfos{
- datadir: infos.volumes["/opt/app/.ethereum"],
- port: infos.portmap[infos.envvars["ETH_PORT"]+"/tcp"],
- ethstats: infos.envvars["ETH_NAME"],
- },
- dbdir: infos.volumes["/var/lib/postgresql/data"],
- host: host,
- port: port,
- }
- return stats, nil
-}
diff --git a/cmd/puppeth/module_faucet.go b/cmd/puppeth/module_faucet.go
deleted file mode 100644
index a4f6e65694..0000000000
--- a/cmd/puppeth/module_faucet.go
+++ /dev/null
@@ -1,254 +0,0 @@
-// Copyright 2017 The go-ethereum Authors
-// This file is part of go-ethereum.
-//
-// go-ethereum is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// go-ethereum is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with go-ethereum. If not, see .
-
-package main
-
-import (
- "bytes"
- "encoding/json"
- "fmt"
- "html/template"
- "math/rand"
- "path/filepath"
- "strconv"
- "strings"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/log"
-)
-
-// faucetDockerfile is the Dockerfile required to build a faucet container to
-// grant crypto tokens based on GitHub authentications.
-var faucetDockerfile = `
-FROM ethereum/client-go:alltools-latest
-
-ADD genesis.json /genesis.json
-ADD account.json /account.json
-ADD account.pass /account.pass
-
-EXPOSE 8080 30303 30303/udp
-
-ENTRYPOINT [ \
- "faucet", "--genesis", "/genesis.json", "--network", "{{.NetworkID}}", "--bootnodes", "{{.Bootnodes}}", "--ethstats", "{{.Ethstats}}", "--ethport", "{{.EthPort}}", \
- "--faucet.name", "{{.FaucetName}}", "--faucet.amount", "{{.FaucetAmount}}", "--faucet.minutes", "{{.FaucetMinutes}}", "--faucet.tiers", "{{.FaucetTiers}}", \
- "--account.json", "/account.json", "--account.pass", "/account.pass" \
- {{if .CaptchaToken}}, "--captcha.token", "{{.CaptchaToken}}", "--captcha.secret", "{{.CaptchaSecret}}"{{end}}{{if .NoAuth}}, "--noauth"{{end}} \
- {{if .TwitterToken}}, "--twitter.token.v1", "{{.TwitterToken}}"{{end}} \
-]`
-
-// faucetComposefile is the docker-compose.yml file required to deploy and maintain
-// a crypto faucet.
-var faucetComposefile = `
-version: '2'
-services:
- faucet:
- build: .
- image: {{.Network}}/faucet
- container_name: {{.Network}}_faucet_1
- ports:
- - "{{.EthPort}}:{{.EthPort}}"
- - "{{.EthPort}}:{{.EthPort}}/udp"{{if not .VHost}}
- - "{{.ApiPort}}:8080"{{end}}
- volumes:
- - {{.Datadir}}:/root/.faucet
- environment:
- - ETH_PORT={{.EthPort}}
- - ETH_NAME={{.EthName}}
- - FAUCET_AMOUNT={{.FaucetAmount}}
- - FAUCET_MINUTES={{.FaucetMinutes}}
- - FAUCET_TIERS={{.FaucetTiers}}
- - CAPTCHA_TOKEN={{.CaptchaToken}}
- - CAPTCHA_SECRET={{.CaptchaSecret}}
- - TWITTER_TOKEN={{.TwitterToken}}
- - NO_AUTH={{.NoAuth}}{{if .VHost}}
- - VIRTUAL_HOST={{.VHost}}
- - VIRTUAL_PORT=8080{{end}}
- logging:
- driver: "json-file"
- options:
- max-size: "1m"
- max-file: "10"
- restart: always
-`
-
-// deployFaucet deploys a new faucet container to a remote machine via SSH,
-// docker and docker-compose. If an instance with the specified network name
-// already exists there, it will be overwritten!
-func deployFaucet(client *sshClient, network string, bootnodes []string, config *faucetInfos, nocache bool) ([]byte, error) {
- // Generate the content to upload to the server
- workdir := fmt.Sprintf("%d", rand.Int63())
- files := make(map[string][]byte)
-
- dockerfile := new(bytes.Buffer)
- template.Must(template.New("").Parse(faucetDockerfile)).Execute(dockerfile, map[string]interface{}{
- "NetworkID": config.node.network,
- "Bootnodes": strings.Join(bootnodes, ","),
- "Ethstats": config.node.ethstats,
- "EthPort": config.node.port,
- "CaptchaToken": config.captchaToken,
- "CaptchaSecret": config.captchaSecret,
- "FaucetName": strings.Title(network),
- "FaucetAmount": config.amount,
- "FaucetMinutes": config.minutes,
- "FaucetTiers": config.tiers,
- "NoAuth": config.noauth,
- "TwitterToken": config.twitterToken,
- })
- files[filepath.Join(workdir, "Dockerfile")] = dockerfile.Bytes()
-
- composefile := new(bytes.Buffer)
- template.Must(template.New("").Parse(faucetComposefile)).Execute(composefile, map[string]interface{}{
- "Network": network,
- "Datadir": config.node.datadir,
- "VHost": config.host,
- "ApiPort": config.port,
- "EthPort": config.node.port,
- "EthName": getEthName(config.node.ethstats),
- "CaptchaToken": config.captchaToken,
- "CaptchaSecret": config.captchaSecret,
- "FaucetAmount": config.amount,
- "FaucetMinutes": config.minutes,
- "FaucetTiers": config.tiers,
- "NoAuth": config.noauth,
- "TwitterToken": config.twitterToken,
- })
- files[filepath.Join(workdir, "docker-compose.yaml")] = composefile.Bytes()
-
- files[filepath.Join(workdir, "genesis.json")] = config.node.genesis
- files[filepath.Join(workdir, "account.json")] = []byte(config.node.keyJSON)
- files[filepath.Join(workdir, "account.pass")] = []byte(config.node.keyPass)
-
- // Upload the deployment files to the remote server (and clean up afterwards)
- if out, err := client.Upload(files); err != nil {
- return out, err
- }
- defer client.Run("rm -rf " + workdir)
-
- // Build and deploy the faucet service
- if nocache {
- return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s build --pull --no-cache && docker-compose -p %s up -d --force-recreate --timeout 60", workdir, network, network))
- }
- return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s up -d --build --force-recreate --timeout 60", workdir, network))
-}
-
-// faucetInfos is returned from a faucet status check to allow reporting various
-// configuration parameters.
-type faucetInfos struct {
- node *nodeInfos
- host string
- port int
- amount int
- minutes int
- tiers int
- noauth bool
- captchaToken string
- captchaSecret string
- twitterToken string
-}
-
-// Report converts the typed struct into a plain string->string map, containing
-// most - but not all - fields for reporting to the user.
-func (info *faucetInfos) Report() map[string]string {
- report := map[string]string{
- "Website address": info.host,
- "Website listener port": strconv.Itoa(info.port),
- "Ethereum listener port": strconv.Itoa(info.node.port),
- "Funding amount (base tier)": fmt.Sprintf("%d Ethers", info.amount),
- "Funding cooldown (base tier)": fmt.Sprintf("%d mins", info.minutes),
- "Funding tiers": strconv.Itoa(info.tiers),
- "Captha protection": fmt.Sprintf("%v", info.captchaToken != ""),
- "Using Twitter API": fmt.Sprintf("%v", info.twitterToken != ""),
- "Ethstats username": info.node.ethstats,
- }
- if info.noauth {
- report["Debug mode (no auth)"] = "enabled"
- }
- if info.node.keyJSON != "" {
- var key struct {
- Address string `json:"address"`
- }
- if err := json.Unmarshal([]byte(info.node.keyJSON), &key); err == nil {
- report["Funding account"] = common.HexToAddress(key.Address).Hex()
- } else {
- log.Error("Failed to retrieve signer address", "err", err)
- }
- }
- return report
-}
-
-// checkFaucet does a health-check against a faucet server to verify whether
-// it's running, and if yes, gathering a collection of useful infos about it.
-func checkFaucet(client *sshClient, network string) (*faucetInfos, error) {
- // Inspect a possible faucet container on the host
- infos, err := inspectContainer(client, fmt.Sprintf("%s_faucet_1", network))
- if err != nil {
- return nil, err
- }
- if !infos.running {
- return nil, ErrServiceOffline
- }
- // Resolve the port from the host, or the reverse proxy
- port := infos.portmap["8080/tcp"]
- if port == 0 {
- if proxy, _ := checkNginx(client, network); proxy != nil {
- port = proxy.port
- }
- }
- if port == 0 {
- return nil, ErrNotExposed
- }
- // Resolve the host from the reverse-proxy and the config values
- host := infos.envvars["VIRTUAL_HOST"]
- if host == "" {
- host = client.server
- }
- amount, _ := strconv.Atoi(infos.envvars["FAUCET_AMOUNT"])
- minutes, _ := strconv.Atoi(infos.envvars["FAUCET_MINUTES"])
- tiers, _ := strconv.Atoi(infos.envvars["FAUCET_TIERS"])
-
- // Retrieve the funding account information
- var out []byte
- keyJSON, keyPass := "", ""
- if out, err = client.Run(fmt.Sprintf("docker exec %s_faucet_1 cat /account.json", network)); err == nil {
- keyJSON = string(bytes.TrimSpace(out))
- }
- if out, err = client.Run(fmt.Sprintf("docker exec %s_faucet_1 cat /account.pass", network)); err == nil {
- keyPass = string(bytes.TrimSpace(out))
- }
- // Run a sanity check to see if the port is reachable
- if err = checkPort(host, port); err != nil {
- log.Warn("Faucet service seems unreachable", "server", host, "port", port, "err", err)
- }
- // Container available, assemble and return the useful infos
- return &faucetInfos{
- node: &nodeInfos{
- datadir: infos.volumes["/root/.faucet"],
- port: infos.portmap[infos.envvars["ETH_PORT"]+"/tcp"],
- ethstats: infos.envvars["ETH_NAME"],
- keyJSON: keyJSON,
- keyPass: keyPass,
- },
- host: host,
- port: port,
- amount: amount,
- minutes: minutes,
- tiers: tiers,
- captchaToken: infos.envvars["CAPTCHA_TOKEN"],
- captchaSecret: infos.envvars["CAPTCHA_SECRET"],
- noauth: infos.envvars["NO_AUTH"] == "true",
- twitterToken: infos.envvars["TWITTER_TOKEN"],
- }, nil
-}
diff --git a/cmd/puppeth/module_nginx.go b/cmd/puppeth/module_nginx.go
deleted file mode 100644
index 1b1ae61ff5..0000000000
--- a/cmd/puppeth/module_nginx.go
+++ /dev/null
@@ -1,119 +0,0 @@
-// Copyright 2017 The go-ethereum Authors
-// This file is part of go-ethereum.
-//
-// go-ethereum is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// go-ethereum is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with go-ethereum. If not, see .
-
-package main
-
-import (
- "bytes"
- "fmt"
- "html/template"
- "math/rand"
- "path/filepath"
- "strconv"
-
- "github.com/ethereum/go-ethereum/log"
-)
-
-// nginxDockerfile is theis the Dockerfile required to build an nginx reverse-
-// proxy.
-var nginxDockerfile = `FROM jwilder/nginx-proxy`
-
-// nginxComposefile is the docker-compose.yml file required to deploy and maintain
-// an nginx reverse-proxy. The proxy is responsible for exposing one or more HTTP
-// services running on a single host.
-var nginxComposefile = `
-version: '2'
-services:
- nginx:
- build: .
- image: {{.Network}}/nginx
- container_name: {{.Network}}_nginx_1
- ports:
- - "{{.Port}}:80"
- volumes:
- - /var/run/docker.sock:/tmp/docker.sock:ro
- logging:
- driver: "json-file"
- options:
- max-size: "1m"
- max-file: "10"
- restart: always
-`
-
-// deployNginx deploys a new nginx reverse-proxy container to expose one or more
-// HTTP services running on a single host. If an instance with the specified
-// network name already exists there, it will be overwritten!
-func deployNginx(client *sshClient, network string, port int, nocache bool) ([]byte, error) {
- log.Info("Deploying nginx reverse-proxy", "server", client.server, "port", port)
-
- // Generate the content to upload to the server
- workdir := fmt.Sprintf("%d", rand.Int63())
- files := make(map[string][]byte)
-
- dockerfile := new(bytes.Buffer)
- template.Must(template.New("").Parse(nginxDockerfile)).Execute(dockerfile, nil)
- files[filepath.Join(workdir, "Dockerfile")] = dockerfile.Bytes()
-
- composefile := new(bytes.Buffer)
- template.Must(template.New("").Parse(nginxComposefile)).Execute(composefile, map[string]interface{}{
- "Network": network,
- "Port": port,
- })
- files[filepath.Join(workdir, "docker-compose.yaml")] = composefile.Bytes()
-
- // Upload the deployment files to the remote server (and clean up afterwards)
- if out, err := client.Upload(files); err != nil {
- return out, err
- }
- defer client.Run("rm -rf " + workdir)
-
- // Build and deploy the reverse-proxy service
- if nocache {
- return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s build --pull --no-cache && docker-compose -p %s up -d --force-recreate --timeout 60", workdir, network, network))
- }
- return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s up -d --build --force-recreate --timeout 60", workdir, network))
-}
-
-// nginxInfos is returned from an nginx reverse-proxy status check to allow
-// reporting various configuration parameters.
-type nginxInfos struct {
- port int
-}
-
-// Report converts the typed struct into a plain string->string map, containing
-// most - but not all - fields for reporting to the user.
-func (info *nginxInfos) Report() map[string]string {
- return map[string]string{
- "Shared listener port": strconv.Itoa(info.port),
- }
-}
-
-// checkNginx does a health-check against an nginx reverse-proxy to verify whether
-// it's running, and if yes, gathering a collection of useful infos about it.
-func checkNginx(client *sshClient, network string) (*nginxInfos, error) {
- // Inspect a possible nginx container on the host
- infos, err := inspectContainer(client, fmt.Sprintf("%s_nginx_1", network))
- if err != nil {
- return nil, err
- }
- if !infos.running {
- return nil, ErrServiceOffline
- }
- // Container available, assemble and return the useful infos
- return &nginxInfos{
- port: infos.portmap["80/tcp"],
- }, nil
-}
diff --git a/cmd/puppeth/module_node.go b/cmd/puppeth/module_node.go
deleted file mode 100644
index 734dd04051..0000000000
--- a/cmd/puppeth/module_node.go
+++ /dev/null
@@ -1,266 +0,0 @@
-// Copyright 2017 The go-ethereum Authors
-// This file is part of go-ethereum.
-//
-// go-ethereum is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// go-ethereum is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with go-ethereum. If not, see .
-
-package main
-
-import (
- "bytes"
- "encoding/json"
- "fmt"
- "math/rand"
- "path/filepath"
- "strconv"
- "strings"
- "text/template"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/log"
-)
-
-// nodeDockerfile is the Dockerfile required to run an Ethereum node.
-var nodeDockerfile = `
-FROM ethereum/client-go:latest
-
-ADD genesis.json /genesis.json
-{{if .Unlock}}
- ADD signer.json /signer.json
- ADD signer.pass /signer.pass
-{{end}}
-RUN \
- echo 'geth --cache 512 init /genesis.json' > geth.sh && \{{if .Unlock}}
- echo 'mkdir -p /root/.ethereum/keystore/ && cp /signer.json /root/.ethereum/keystore/' >> geth.sh && \{{end}}
- echo $'exec geth --networkid {{.NetworkID}} --cache 512 --port {{.Port}} --nat extip:{{.IP}} --maxpeers {{.Peers}} {{.LightFlag}} --ethstats \'{{.Ethstats}}\' {{if .Bootnodes}}--bootnodes {{.Bootnodes}}{{end}} {{if .Etherbase}}--miner.etherbase {{.Etherbase}} --mine --miner.threads 1{{end}} {{if .Unlock}}--unlock 0 --password /signer.pass --mine{{end}} --miner.gaslimit {{.GasLimit}} --miner.gasprice {{.GasPrice}}' >> geth.sh
-
-ENTRYPOINT ["/bin/sh", "geth.sh"]
-`
-
-// nodeComposefile is the docker-compose.yml file required to deploy and maintain
-// an Ethereum node (bootnode or miner for now).
-var nodeComposefile = `
-version: '2'
-services:
- {{.Type}}:
- build: .
- image: {{.Network}}/{{.Type}}
- container_name: {{.Network}}_{{.Type}}_1
- ports:
- - "{{.Port}}:{{.Port}}"
- - "{{.Port}}:{{.Port}}/udp"
- volumes:
- - {{.Datadir}}:/root/.ethereum{{if .Ethashdir}}
- - {{.Ethashdir}}:/root/.ethash{{end}}
- environment:
- - PORT={{.Port}}/tcp
- - TOTAL_PEERS={{.TotalPeers}}
- - LIGHT_PEERS={{.LightPeers}}
- - STATS_NAME={{.Ethstats}}
- - MINER_NAME={{.Etherbase}}
- - GAS_LIMIT={{.GasLimit}}
- - GAS_PRICE={{.GasPrice}}
- logging:
- driver: "json-file"
- options:
- max-size: "1m"
- max-file: "10"
- restart: always
-`
-
-// deployNode deploys a new Ethereum node container to a remote machine via SSH,
-// docker and docker-compose. If an instance with the specified network name
-// already exists there, it will be overwritten!
-func deployNode(client *sshClient, network string, bootnodes []string, config *nodeInfos, nocache bool) ([]byte, error) {
- kind := "sealnode"
- if config.keyJSON == "" && config.etherbase == "" {
- kind = "bootnode"
- bootnodes = make([]string, 0)
- }
- // Generate the content to upload to the server
- workdir := fmt.Sprintf("%d", rand.Int63())
- files := make(map[string][]byte)
-
- lightFlag := ""
- if config.peersLight > 0 {
- lightFlag = fmt.Sprintf("--light.maxpeers=%d --light.serve=50", config.peersLight)
- }
- dockerfile := new(bytes.Buffer)
- template.Must(template.New("").Parse(nodeDockerfile)).Execute(dockerfile, map[string]interface{}{
- "NetworkID": config.network,
- "Port": config.port,
- "IP": client.address,
- "Peers": config.peersTotal,
- "LightFlag": lightFlag,
- "Bootnodes": strings.Join(bootnodes, ","),
- "Ethstats": config.ethstats,
- "Etherbase": config.etherbase,
- "GasLimit": uint64(1000000 * config.gasLimit),
- "GasPrice": uint64(1000000000 * config.gasPrice),
- "Unlock": config.keyJSON != "",
- })
- files[filepath.Join(workdir, "Dockerfile")] = dockerfile.Bytes()
-
- composefile := new(bytes.Buffer)
- template.Must(template.New("").Parse(nodeComposefile)).Execute(composefile, map[string]interface{}{
- "Type": kind,
- "Datadir": config.datadir,
- "Ethashdir": config.ethashdir,
- "Network": network,
- "Port": config.port,
- "TotalPeers": config.peersTotal,
- "Light": config.peersLight > 0,
- "LightPeers": config.peersLight,
- "Ethstats": getEthName(config.ethstats),
- "Etherbase": config.etherbase,
- "GasLimit": config.gasLimit,
- "GasPrice": config.gasPrice,
- })
- files[filepath.Join(workdir, "docker-compose.yaml")] = composefile.Bytes()
-
- files[filepath.Join(workdir, "genesis.json")] = config.genesis
- if config.keyJSON != "" {
- files[filepath.Join(workdir, "signer.json")] = []byte(config.keyJSON)
- files[filepath.Join(workdir, "signer.pass")] = []byte(config.keyPass)
- }
- // Upload the deployment files to the remote server (and clean up afterwards)
- if out, err := client.Upload(files); err != nil {
- return out, err
- }
- defer client.Run("rm -rf " + workdir)
-
- // Build and deploy the boot or seal node service
- if nocache {
- return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s build --pull --no-cache && docker-compose -p %s up -d --force-recreate --timeout 60", workdir, network, network))
- }
- return nil, client.Stream(fmt.Sprintf("cd %s && docker-compose -p %s up -d --build --force-recreate --timeout 60", workdir, network))
-}
-
-// nodeInfos is returned from a boot or seal node status check to allow reporting
-// various configuration parameters.
-type nodeInfos struct {
- genesis []byte
- network int64
- datadir string
- ethashdir string
- ethstats string
- port int
- enode string
- peersTotal int
- peersLight int
- etherbase string
- keyJSON string
- keyPass string
- gasLimit float64
- gasPrice float64
-}
-
-// Report converts the typed struct into a plain string->string map, containing
-// most - but not all - fields for reporting to the user.
-func (info *nodeInfos) Report() map[string]string {
- report := map[string]string{
- "Data directory": info.datadir,
- "Listener port": strconv.Itoa(info.port),
- "Peer count (all total)": strconv.Itoa(info.peersTotal),
- "Peer count (light nodes)": strconv.Itoa(info.peersLight),
- "Ethstats username": info.ethstats,
- }
- if info.gasLimit > 0 {
- // Miner or signer node
- report["Gas price (minimum accepted)"] = fmt.Sprintf("%0.3f GWei", info.gasPrice)
- report["Gas ceil (target maximum)"] = fmt.Sprintf("%0.3f MGas", info.gasLimit)
-
- if info.etherbase != "" {
- // Ethash proof-of-work miner
- report["Ethash directory"] = info.ethashdir
- report["Miner account"] = info.etherbase
- }
- if info.keyJSON != "" {
- // Clique proof-of-authority signer
- var key struct {
- Address string `json:"address"`
- }
- if err := json.Unmarshal([]byte(info.keyJSON), &key); err == nil {
- report["Signer account"] = common.HexToAddress(key.Address).Hex()
- } else {
- log.Error("Failed to retrieve signer address", "err", err)
- }
- }
- }
- return report
-}
-
-// checkNode does a health-check against a boot or seal node server to verify
-// whether it's running, and if yes, whether it's responsive.
-func checkNode(client *sshClient, network string, boot bool) (*nodeInfos, error) {
- kind := "bootnode"
- if !boot {
- kind = "sealnode"
- }
- // Inspect a possible bootnode container on the host
- infos, err := inspectContainer(client, fmt.Sprintf("%s_%s_1", network, kind))
- if err != nil {
- return nil, err
- }
- if !infos.running {
- return nil, ErrServiceOffline
- }
- // Resolve a few types from the environmental variables
- totalPeers, _ := strconv.Atoi(infos.envvars["TOTAL_PEERS"])
- lightPeers, _ := strconv.Atoi(infos.envvars["LIGHT_PEERS"])
- gasLimit, _ := strconv.ParseFloat(infos.envvars["GAS_LIMIT"], 64)
- gasPrice, _ := strconv.ParseFloat(infos.envvars["GAS_PRICE"], 64)
-
- // Container available, retrieve its node ID and its genesis json
- var out []byte
- if out, err = client.Run(fmt.Sprintf("docker exec %s_%s_1 geth --exec admin.nodeInfo.enode --cache=16 attach", network, kind)); err != nil {
- return nil, ErrServiceUnreachable
- }
- enode := bytes.Trim(bytes.TrimSpace(out), "\"")
-
- if out, err = client.Run(fmt.Sprintf("docker exec %s_%s_1 cat /genesis.json", network, kind)); err != nil {
- return nil, ErrServiceUnreachable
- }
- genesis := bytes.TrimSpace(out)
-
- keyJSON, keyPass := "", ""
- if out, err = client.Run(fmt.Sprintf("docker exec %s_%s_1 cat /signer.json", network, kind)); err == nil {
- keyJSON = string(bytes.TrimSpace(out))
- }
- if out, err = client.Run(fmt.Sprintf("docker exec %s_%s_1 cat /signer.pass", network, kind)); err == nil {
- keyPass = string(bytes.TrimSpace(out))
- }
- // Run a sanity check to see if the devp2p is reachable
- port := infos.portmap[infos.envvars["PORT"]]
- if err = checkPort(client.server, port); err != nil {
- log.Warn(fmt.Sprintf("%s devp2p port seems unreachable", strings.Title(kind)), "server", client.server, "port", port, "err", err)
- }
- // Assemble and return the useful infos
- stats := &nodeInfos{
- genesis: genesis,
- datadir: infos.volumes["/root/.ethereum"],
- ethashdir: infos.volumes["/root/.ethash"],
- port: port,
- peersTotal: totalPeers,
- peersLight: lightPeers,
- ethstats: infos.envvars["STATS_NAME"],
- etherbase: infos.envvars["MINER_NAME"],
- keyJSON: keyJSON,
- keyPass: keyPass,
- gasLimit: gasLimit,
- gasPrice: gasPrice,
- }
- stats.enode = string(enode)
-
- return stats, nil
-}
diff --git a/cmd/puppeth/puppeth.go b/cmd/puppeth/puppeth.go
deleted file mode 100644
index 415542b60c..0000000000
--- a/cmd/puppeth/puppeth.go
+++ /dev/null
@@ -1,65 +0,0 @@
-// Copyright 2017 The go-ethereum Authors
-// This file is part of go-ethereum.
-//
-// go-ethereum is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// go-ethereum is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with go-ethereum. If not, see .
-
-// puppeth is a command to assemble and maintain private networks.
-package main
-
-import (
- "math/rand"
- "os"
- "strings"
- "time"
-
- "github.com/ethereum/go-ethereum/log"
- "github.com/urfave/cli/v2"
-)
-
-// main is just a boring entry point to set up the CLI app.
-func main() {
- app := cli.NewApp()
- app.Name = "puppeth"
- app.Usage = "assemble and maintain private Ethereum networks"
- app.Flags = []cli.Flag{
- &cli.StringFlag{
- Name: "network",
- Usage: "name of the network to administer (no spaces or hyphens, please)",
- },
- &cli.IntFlag{
- Name: "loglevel",
- Value: 3,
- Usage: "log level to emit to the screen",
- },
- }
- app.Before = func(c *cli.Context) error {
- // Set up the logger to print everything and the random generator
- log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(c.Int("loglevel")), log.StreamHandler(os.Stdout, log.TerminalFormat(true))))
- rand.Seed(time.Now().UnixNano())
-
- return nil
- }
- app.Action = runWizard
- app.Run(os.Args)
-}
-
-// runWizard start the wizard and relinquish control to it.
-func runWizard(c *cli.Context) error {
- network := c.String("network")
- if strings.Contains(network, " ") || strings.Contains(network, "-") || strings.ToLower(network) != network {
- log.Crit("No spaces, hyphens or capital letters allowed in network name")
- }
- makeWizard(c.String("network")).run()
- return nil
-}
diff --git a/cmd/puppeth/ssh.go b/cmd/puppeth/ssh.go
deleted file mode 100644
index a20b3bfda2..0000000000
--- a/cmd/puppeth/ssh.go
+++ /dev/null
@@ -1,271 +0,0 @@
-// Copyright 2017 The go-ethereum Authors
-// This file is part of go-ethereum.
-//
-// go-ethereum is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// go-ethereum is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with go-ethereum. If not, see .
-
-package main
-
-import (
- "bufio"
- "bytes"
- "errors"
- "fmt"
- "net"
- "os"
- "os/user"
- "path/filepath"
- "strings"
-
- "github.com/ethereum/go-ethereum/log"
- "golang.org/x/crypto/ssh"
- "golang.org/x/crypto/ssh/agent"
- "golang.org/x/term"
-)
-
-// sshClient is a small wrapper around Go's SSH client with a few utility methods
-// implemented on top.
-type sshClient struct {
- server string // Server name or IP without port number
- address string // IP address of the remote server
- pubkey []byte // RSA public key to authenticate the server
- client *ssh.Client
- logger log.Logger
-}
-
-const EnvSSHAuthSock = "SSH_AUTH_SOCK"
-
-// dial establishes an SSH connection to a remote node using the current user and
-// the user's configured private RSA key. If that fails, password authentication
-// is fallen back to. server can be a string like user:identity@server:port.
-func dial(server string, pubkey []byte) (*sshClient, error) {
- // Figure out username, identity, hostname and port
- hostname := ""
- hostport := server
- username := ""
- identity := "id_rsa" // default
-
- if strings.Contains(server, "@") {
- prefix := server[:strings.Index(server, "@")]
- if strings.Contains(prefix, ":") {
- username = prefix[:strings.Index(prefix, ":")]
- identity = prefix[strings.Index(prefix, ":")+1:]
- } else {
- username = prefix
- }
- hostport = server[strings.Index(server, "@")+1:]
- }
- if strings.Contains(hostport, ":") {
- hostname = hostport[:strings.Index(hostport, ":")]
- } else {
- hostname = hostport
- hostport += ":22"
- }
- logger := log.New("server", server)
- logger.Debug("Attempting to establish SSH connection")
-
- user, err := user.Current()
- if err != nil {
- return nil, err
- }
- if username == "" {
- username = user.Username
- }
-
- // Configure the supported authentication methods (ssh agent, private key and password)
- var (
- auths []ssh.AuthMethod
- conn net.Conn
- )
- if conn, err = net.Dial("unix", os.Getenv(EnvSSHAuthSock)); err != nil {
- log.Warn("Unable to dial SSH agent, falling back to private keys", "err", err)
- } else {
- client := agent.NewClient(conn)
- auths = append(auths, ssh.PublicKeysCallback(client.Signers))
- }
- if err != nil {
- path := filepath.Join(user.HomeDir, ".ssh", identity)
- if buf, err := os.ReadFile(path); err != nil {
- log.Warn("No SSH key, falling back to passwords", "path", path, "err", err)
- } else {
- key, err := ssh.ParsePrivateKey(buf)
- if err != nil {
- fmt.Printf("What's the decryption password for %s? (won't be echoed)\n>", path)
- blob, err := term.ReadPassword(int(os.Stdin.Fd()))
- fmt.Println()
- if err != nil {
- log.Warn("Couldn't read password", "err", err)
- }
- key, err := ssh.ParsePrivateKeyWithPassphrase(buf, blob)
- if err != nil {
- log.Warn("Failed to decrypt SSH key, falling back to passwords", "path", path, "err", err)
- } else {
- auths = append(auths, ssh.PublicKeys(key))
- }
- } else {
- auths = append(auths, ssh.PublicKeys(key))
- }
- }
- auths = append(auths, ssh.PasswordCallback(func() (string, error) {
- fmt.Printf("What's the login password for %s at %s? (won't be echoed)\n> ", username, server)
- blob, err := term.ReadPassword(int(os.Stdin.Fd()))
-
- fmt.Println()
- return string(blob), err
- }))
- }
- // Resolve the IP address of the remote server
- addr, err := net.LookupHost(hostname)
- if err != nil {
- return nil, err
- }
- if len(addr) == 0 {
- return nil, errors.New("no IPs associated with domain")
- }
- // Try to dial in to the remote server
- logger.Trace("Dialing remote SSH server", "user", username)
- keycheck := func(hostname string, remote net.Addr, key ssh.PublicKey) error {
- // If no public key is known for SSH, ask the user to confirm
- if pubkey == nil {
- fmt.Println()
- fmt.Printf("The authenticity of host '%s (%s)' can't be established.\n", hostname, remote)
- fmt.Printf("SSH key fingerprint is %s [MD5]\n", ssh.FingerprintLegacyMD5(key))
- fmt.Printf("Are you sure you want to continue connecting (yes/no)? ")
-
- for {
- text, err := bufio.NewReader(os.Stdin).ReadString('\n')
- switch {
- case err != nil:
- return err
- case strings.TrimSpace(text) == "yes":
- pubkey = key.Marshal()
- return nil
- case strings.TrimSpace(text) == "no":
- return errors.New("users says no")
- default:
- fmt.Println("Please answer 'yes' or 'no'")
- continue
- }
- }
- }
- // If a public key exists for this SSH server, check that it matches
- if bytes.Equal(pubkey, key.Marshal()) {
- return nil
- }
- // We have a mismatch, forbid connecting
- return errors.New("ssh key mismatch, re-add the machine to update")
- }
- client, err := ssh.Dial("tcp", hostport, &ssh.ClientConfig{User: username, Auth: auths, HostKeyCallback: keycheck})
- if err != nil {
- return nil, err
- }
- // Connection established, return our utility wrapper
- c := &sshClient{
- server: hostname,
- address: addr[0],
- pubkey: pubkey,
- client: client,
- logger: logger,
- }
- if err := c.init(); err != nil {
- client.Close()
- return nil, err
- }
- return c, nil
-}
-
-// init runs some initialization commands on the remote server to ensure it's
-// capable of acting as puppeth target.
-func (client *sshClient) init() error {
- client.logger.Debug("Verifying if docker is available")
- if out, err := client.Run("docker version"); err != nil {
- if len(out) == 0 {
- return err
- }
- return fmt.Errorf("docker configured incorrectly: %s", out)
- }
- client.logger.Debug("Verifying if docker-compose is available")
- if out, err := client.Run("docker-compose version"); err != nil {
- if len(out) == 0 {
- return err
- }
- return fmt.Errorf("docker-compose configured incorrectly: %s", out)
- }
- return nil
-}
-
-// Close terminates the connection to an SSH server.
-func (client *sshClient) Close() error {
- return client.client.Close()
-}
-
-// Run executes a command on the remote server and returns the combined output
-// along with any error status.
-func (client *sshClient) Run(cmd string) ([]byte, error) {
- // Establish a single command session
- session, err := client.client.NewSession()
- if err != nil {
- return nil, err
- }
- defer session.Close()
-
- // Execute the command and return any output
- client.logger.Trace("Running command on remote server", "cmd", cmd)
- return session.CombinedOutput(cmd)
-}
-
-// Stream executes a command on the remote server and streams all outputs into
-// the local stdout and stderr streams.
-func (client *sshClient) Stream(cmd string) error {
- // Establish a single command session
- session, err := client.client.NewSession()
- if err != nil {
- return err
- }
- defer session.Close()
-
- session.Stdout = os.Stdout
- session.Stderr = os.Stderr
-
- // Execute the command and return any output
- client.logger.Trace("Streaming command on remote server", "cmd", cmd)
- return session.Run(cmd)
-}
-
-// Upload copies the set of files to a remote server via SCP, creating any non-
-// existing folders in the mean time.
-func (client *sshClient) Upload(files map[string][]byte) ([]byte, error) {
- // Establish a single command session
- session, err := client.client.NewSession()
- if err != nil {
- return nil, err
- }
- defer session.Close()
-
- // Create a goroutine that streams the SCP content
- go func() {
- out, _ := session.StdinPipe()
- defer out.Close()
-
- for file, content := range files {
- client.logger.Trace("Uploading file to server", "file", file, "bytes", len(content))
-
- fmt.Fprintln(out, "D0755", 0, filepath.Dir(file)) // Ensure the folder exists
- fmt.Fprintln(out, "C0644", len(content), filepath.Base(file)) // Create the actual file
- out.Write(content) // Stream the data content
- fmt.Fprint(out, "\x00") // Transfer end with \x00
- fmt.Fprintln(out, "E") // Leave directory (simpler)
- }
- }()
- return session.CombinedOutput("/usr/bin/scp -v -tr ./")
-}
diff --git a/cmd/puppeth/testdata/stureby_aleth.json b/cmd/puppeth/testdata/stureby_aleth.json
deleted file mode 100644
index d18ba3854a..0000000000
--- a/cmd/puppeth/testdata/stureby_aleth.json
+++ /dev/null
@@ -1,113 +0,0 @@
-{
- "sealEngine": "Ethash",
- "params": {
- "accountStartNonce": "0x0",
- "maximumExtraDataSize": "0x20",
- "homesteadForkBlock": "0x2710",
- "daoHardforkBlock": "0x0",
- "EIP150ForkBlock": "0x3a98",
- "EIP158ForkBlock": "0x59d8",
- "byzantiumForkBlock": "0x7530",
- "constantinopleForkBlock": "0x9c40",
- "constantinopleFixForkBlock": "0x9c40",
- "istanbulForkBlock": "0xc350",
- "minGasLimit": "0x1388",
- "maxGasLimit": "0x7fffffffffffffff",
- "tieBreakingGas": false,
- "gasLimitBoundDivisor": "0x400",
- "minimumDifficulty": "0x20000",
- "difficultyBoundDivisor": "0x800",
- "durationLimit": "0xd",
- "blockReward": "0x4563918244f40000",
- "networkID": "0x4cb2e",
- "chainID": "0x4cb2e",
- "allowFutureBlocks": false
- },
- "genesis": {
- "nonce": "0x0000000000000000",
- "difficulty": "0x20000",
- "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
- "author": "0x0000000000000000000000000000000000000000",
- "timestamp": "0x59a4e76d",
- "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
- "extraData": "0x0000000000000000000000000000000000000000000000000000000b4dc0ffee",
- "gasLimit": "0x47b760"
- },
- "accounts": {
- "0000000000000000000000000000000000000001": {
- "balance": "0x1",
- "precompiled": {
- "name": "ecrecover",
- "linear": {
- "base": 3000,
- "word": 0
- }
- }
- },
- "0000000000000000000000000000000000000002": {
- "balance": "0x1",
- "precompiled": {
- "name": "sha256",
- "linear": {
- "base": 60,
- "word": 12
- }
- }
- },
- "0000000000000000000000000000000000000003": {
- "balance": "0x1",
- "precompiled": {
- "name": "ripemd160",
- "linear": {
- "base": 600,
- "word": 120
- }
- }
- },
- "0000000000000000000000000000000000000004": {
- "balance": "0x1",
- "precompiled": {
- "name": "identity",
- "linear": {
- "base": 15,
- "word": 3
- }
- }
- },
- "0000000000000000000000000000000000000005": {
- "balance": "0x1",
- "precompiled": {
- "name": "modexp",
- "startingBlock": "0x7530"
- }
- },
- "0000000000000000000000000000000000000006": {
- "balance": "0x1",
- "precompiled": {
- "name": "alt_bn128_G1_add",
- "startingBlock": "0x7530"
- }
- },
- "0000000000000000000000000000000000000007": {
- "balance": "0x1",
- "precompiled": {
- "name": "alt_bn128_G1_mul",
- "startingBlock": "0x7530"
- }
- },
- "0000000000000000000000000000000000000008": {
- "balance": "0x1",
- "precompiled": {
- "name": "alt_bn128_pairing_product",
- "startingBlock": "0x7530"
- }
- },
- "0000000000000000000000000000000000000009": {
- "balance": "0x1",
- "precompiled": {
- "name": "blake2_compression",
- "startingBlock": "0xc350"
- }
- }
- }
-}
\ No newline at end of file
diff --git a/cmd/puppeth/testdata/stureby_geth.json b/cmd/puppeth/testdata/stureby_geth.json
deleted file mode 100644
index 79f03469af..0000000000
--- a/cmd/puppeth/testdata/stureby_geth.json
+++ /dev/null
@@ -1,54 +0,0 @@
-{
- "config": {
- "chainId": 314158,
- "homesteadBlock": 10000,
- "eip150Block": 15000,
- "eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
- "eip155Block": 23000,
- "eip158Block": 23000,
- "byzantiumBlock": 30000,
- "constantinopleBlock": 40000,
- "petersburgBlock": 40000,
- "istanbulBlock": 50000,
- "ethash": {}
- },
- "nonce": "0x0",
- "timestamp": "0x59a4e76d",
- "extraData": "0x0000000000000000000000000000000000000000000000000000000b4dc0ffee",
- "gasLimit": "0x47b760",
- "difficulty": "0x20000",
- "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
- "coinbase": "0x0000000000000000000000000000000000000000",
- "alloc": {
- "0000000000000000000000000000000000000001": {
- "balance": "0x1"
- },
- "0000000000000000000000000000000000000002": {
- "balance": "0x1"
- },
- "0000000000000000000000000000000000000003": {
- "balance": "0x1"
- },
- "0000000000000000000000000000000000000004": {
- "balance": "0x1"
- },
- "0000000000000000000000000000000000000005": {
- "balance": "0x1"
- },
- "0000000000000000000000000000000000000006": {
- "balance": "0x1"
- },
- "0000000000000000000000000000000000000007": {
- "balance": "0x1"
- },
- "0000000000000000000000000000000000000008": {
- "balance": "0x1"
- },
- "0000000000000000000000000000000000000009": {
- "balance": "0x1"
- }
- },
- "number": "0x0",
- "gasUsed": "0x0",
- "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
-}
\ No newline at end of file
diff --git a/cmd/puppeth/testdata/stureby_parity.json b/cmd/puppeth/testdata/stureby_parity.json
deleted file mode 100644
index e9229f99b7..0000000000
--- a/cmd/puppeth/testdata/stureby_parity.json
+++ /dev/null
@@ -1,213 +0,0 @@
-{
- "name": "stureby",
- "dataDir": "stureby",
- "engine": {
- "Ethash": {
- "params": {
- "minimumDifficulty": "0x20000",
- "difficultyBoundDivisor": "0x800",
- "durationLimit": "0xd",
- "blockReward": {
- "0x0": "0x4563918244f40000",
- "0x7530": "0x29a2241af62c0000",
- "0x9c40": "0x1bc16d674ec80000"
- },
- "difficultyBombDelays": {
- "0x7530": "0x2dc6c0",
- "0x9c40": "0x1e8480"
- },
- "homesteadTransition": "0x2710",
- "eip100bTransition": "0x7530"
- }
- }
- },
- "params": {
- "accountStartNonce": "0x0",
- "maximumExtraDataSize": "0x20",
- "minGasLimit": "0x1388",
- "gasLimitBoundDivisor": "0x400",
- "networkID": "0x4cb2e",
- "chainID": "0x4cb2e",
- "maxCodeSize": "0x6000",
- "maxCodeSizeTransition": "0x0",
- "eip98Transition": "0x7fffffffffffffff",
- "eip150Transition": "0x3a98",
- "eip160Transition": "0x59d8",
- "eip161abcTransition": "0x59d8",
- "eip161dTransition": "0x59d8",
- "eip155Transition": "0x59d8",
- "eip140Transition": "0x7530",
- "eip211Transition": "0x7530",
- "eip214Transition": "0x7530",
- "eip658Transition": "0x7530",
- "eip145Transition": "0x9c40",
- "eip1014Transition": "0x9c40",
- "eip1052Transition": "0x9c40",
- "eip1283Transition": "0x9c40",
- "eip1283DisableTransition": "0x9c40",
- "eip1283ReenableTransition": "0xc350",
- "eip1344Transition": "0xc350",
- "eip1884Transition": "0xc350",
- "eip2028Transition": "0xc350"
- },
- "genesis": {
- "seal": {
- "ethereum": {
- "nonce": "0x0000000000000000",
- "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
- }
- },
- "difficulty": "0x20000",
- "author": "0x0000000000000000000000000000000000000000",
- "timestamp": "0x59a4e76d",
- "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
- "extraData": "0x0000000000000000000000000000000000000000000000000000000b4dc0ffee",
- "gasLimit": "0x47b760"
- },
- "nodes": [],
- "accounts": {
- "0000000000000000000000000000000000000001": {
- "balance": "0x1",
- "builtin": {
- "name": "ecrecover",
- "pricing": {
- "linear": {
- "base": 3000,
- "word": 0
- }
- }
- }
- },
- "0000000000000000000000000000000000000002": {
- "balance": "0x1",
- "builtin": {
- "name": "sha256",
- "pricing": {
- "linear": {
- "base": 60,
- "word": 12
- }
- }
- }
- },
- "0000000000000000000000000000000000000003": {
- "balance": "0x1",
- "builtin": {
- "name": "ripemd160",
- "pricing": {
- "linear": {
- "base": 600,
- "word": 120
- }
- }
- }
- },
- "0000000000000000000000000000000000000004": {
- "balance": "0x1",
- "builtin": {
- "name": "identity",
- "pricing": {
- "linear": {
- "base": 15,
- "word": 3
- }
- }
- }
- },
- "0000000000000000000000000000000000000005": {
- "balance": "0x1",
- "builtin": {
- "name": "modexp",
- "pricing": {
- "modexp": {
- "divisor": 20
- }
- },
- "activate_at": "0x7530"
- }
- },
- "0000000000000000000000000000000000000006": {
- "balance": "0x1",
- "builtin": {
- "name": "alt_bn128_add",
- "pricing": {
- "0x0": {
- "price": {
- "alt_bn128_const_operations": {
- "price": 500
- }
- }
- },
- "0xc350": {
- "price": {
- "alt_bn128_const_operations": {
- "price": 150
- }
- }
- }
- },
- "activate_at": "0x7530"
- }
- },
- "0000000000000000000000000000000000000007": {
- "balance": "0x1",
- "builtin": {
- "name": "alt_bn128_mul",
- "pricing": {
- "0x0": {
- "price": {
- "alt_bn128_const_operations": {
- "price": 40000
- }
- }
- },
- "0xc350": {
- "price": {
- "alt_bn128_const_operations": {
- "price": 6000
- }
- }
- }
- },
- "activate_at": "0x7530"
- }
- },
- "0000000000000000000000000000000000000008": {
- "balance": "0x1",
- "builtin": {
- "name": "alt_bn128_pairing",
- "pricing": {
- "0x0": {
- "price": {
- "alt_bn128_pairing": {
- "base": 100000,
- "pair": 80000
- }
- }
- },
- "0xc350": {
- "price": {
- "alt_bn128_pairing": {
- "base": 45000,
- "pair": 34000
- }
- }
- }
- },
- "activate_at": "0x7530"
- }
- },
- "0000000000000000000000000000000000000009": {
- "balance": "0x1",
- "builtin": {
- "name": "blake2_f",
- "pricing": {
- "blake2_f": {
- "gas_per_round": 1
- }
- },
- "activate_at": "0xc350"
- }
- }
- }
-}
\ No newline at end of file
diff --git a/cmd/puppeth/wizard.go b/cmd/puppeth/wizard.go
deleted file mode 100644
index 6e5ca41d68..0000000000
--- a/cmd/puppeth/wizard.go
+++ /dev/null
@@ -1,312 +0,0 @@
-// Copyright 2017 The go-ethereum Authors
-// This file is part of go-ethereum.
-//
-// go-ethereum is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// go-ethereum is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with go-ethereum. If not, see .
-
-package main
-
-import (
- "encoding/json"
- "fmt"
- "math/big"
- "net"
- "net/url"
- "os"
- "path/filepath"
- "sort"
- "strconv"
- "strings"
- "sync"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/console/prompt"
- "github.com/ethereum/go-ethereum/core"
- "github.com/ethereum/go-ethereum/log"
- "github.com/peterh/liner"
- "golang.org/x/term"
-)
-
-// config contains all the configurations needed by puppeth that should be saved
-// between sessions.
-type config struct {
- path string // File containing the configuration values
- bootnodes []string // Bootnodes to always connect to by all nodes
- ethstats string // Ethstats settings to cache for node deploys
-
- Genesis *core.Genesis `json:"genesis,omitempty"` // Genesis block to cache for node deploys
- Servers map[string][]byte `json:"servers,omitempty"`
-}
-
-// servers retrieves an alphabetically sorted list of servers.
-func (c config) servers() []string {
- servers := make([]string, 0, len(c.Servers))
- for server := range c.Servers {
- servers = append(servers, server)
- }
- sort.Strings(servers)
-
- return servers
-}
-
-// flush dumps the contents of config to disk.
-func (c config) flush() {
- os.MkdirAll(filepath.Dir(c.path), 0755)
-
- out, _ := json.MarshalIndent(c, "", " ")
- if err := os.WriteFile(c.path, out, 0644); err != nil {
- log.Warn("Failed to save puppeth configs", "file", c.path, "err", err)
- }
-}
-
-type wizard struct {
- network string // Network name to manage
- conf config // Configurations from previous runs
-
- servers map[string]*sshClient // SSH connections to servers to administer
- services map[string][]string // Ethereum services known to be running on servers
-
- lock sync.Mutex // Lock to protect configs during concurrent service discovery
-}
-
-// prompts the user for input with the given prompt string. Returns when a value is entered.
-// Causes the wizard to exit if ctrl-d is pressed
-func promptInput(p string) string {
- for {
- text, err := prompt.Stdin.PromptInput(p)
- if err != nil {
- if err != liner.ErrPromptAborted {
- log.Crit("Failed to read user input", "err", err)
- }
- } else {
- return text
- }
- }
-}
-
-// read reads a single line from stdin, trimming if from spaces.
-func (w *wizard) read() string {
- text := promptInput("> ")
- return strings.TrimSpace(text)
-}
-
-// readString reads a single line from stdin, trimming if from spaces, enforcing
-// non-emptyness.
-func (w *wizard) readString() string {
- for {
- text := promptInput("> ")
- if text = strings.TrimSpace(text); text != "" {
- return text
- }
- }
-}
-
-// readDefaultString reads a single line from stdin, trimming if from spaces. If
-// an empty line is entered, the default value is returned.
-func (w *wizard) readDefaultString(def string) string {
- text := promptInput("> ")
- if text = strings.TrimSpace(text); text != "" {
- return text
- }
- return def
-}
-
-// readDefaultYesNo reads a single line from stdin, trimming if from spaces and
-// interpreting it as a 'yes' or a 'no'. If an empty line is entered, the default
-// value is returned.
-func (w *wizard) readDefaultYesNo(def bool) bool {
- for {
- text := promptInput("> ")
- if text = strings.ToLower(strings.TrimSpace(text)); text == "" {
- return def
- }
- if text == "y" || text == "yes" {
- return true
- }
- if text == "n" || text == "no" {
- return false
- }
- log.Error("Invalid input, expected 'y', 'yes', 'n', 'no' or empty")
- }
-}
-
-// readURL reads a single line from stdin, trimming if from spaces and trying to
-// interpret it as a URL (http, https or file).
-func (w *wizard) readURL() *url.URL {
- for {
- text := promptInput("> ")
- uri, err := url.Parse(strings.TrimSpace(text))
- if err != nil {
- log.Error("Invalid input, expected URL", "err", err)
- continue
- }
- return uri
- }
-}
-
-// readInt reads a single line from stdin, trimming if from spaces, enforcing it
-// to parse into an integer.
-func (w *wizard) readInt() int {
- for {
- text := promptInput("> ")
- if text = strings.TrimSpace(text); text == "" {
- continue
- }
- val, err := strconv.Atoi(strings.TrimSpace(text))
- if err != nil {
- log.Error("Invalid input, expected integer", "err", err)
- continue
- }
- return val
- }
-}
-
-// readDefaultInt reads a single line from stdin, trimming if from spaces, enforcing
-// it to parse into an integer. If an empty line is entered, the default value is
-// returned.
-func (w *wizard) readDefaultInt(def int) int {
- for {
- text := promptInput("> ")
- if text = strings.TrimSpace(text); text == "" {
- return def
- }
- val, err := strconv.Atoi(strings.TrimSpace(text))
- if err != nil {
- log.Error("Invalid input, expected integer", "err", err)
- continue
- }
- return val
- }
-}
-
-// readDefaultBigInt reads a single line from stdin, trimming if from spaces,
-// enforcing it to parse into a big integer. If an empty line is entered, the
-// default value is returned.
-func (w *wizard) readDefaultBigInt(def *big.Int) *big.Int {
- for {
- text := promptInput("> ")
- if text = strings.TrimSpace(text); text == "" {
- return def
- }
- val, ok := new(big.Int).SetString(text, 0)
- if !ok {
- log.Error("Invalid input, expected big integer")
- continue
- }
- return val
- }
-}
-
-// readDefaultFloat reads a single line from stdin, trimming if from spaces, enforcing
-// it to parse into a float. If an empty line is entered, the default value is returned.
-func (w *wizard) readDefaultFloat(def float64) float64 {
- for {
- text := promptInput("> ")
- if text = strings.TrimSpace(text); text == "" {
- return def
- }
- val, err := strconv.ParseFloat(strings.TrimSpace(text), 64)
- if err != nil {
- log.Error("Invalid input, expected float", "err", err)
- continue
- }
- return val
- }
-}
-
-// readPassword reads a single line from stdin, trimming it from the trailing new
-// line and returns it. The input will not be echoed.
-func (w *wizard) readPassword() string {
- fmt.Printf("> ")
- text, err := term.ReadPassword(int(os.Stdin.Fd()))
- if err != nil {
- log.Crit("Failed to read password", "err", err)
- }
- fmt.Println()
- return string(text)
-}
-
-// readAddress reads a single line from stdin, trimming if from spaces and converts
-// it to an Ethereum address.
-func (w *wizard) readAddress() *common.Address {
- for {
- text := promptInput("> 0x")
- if text = strings.TrimSpace(text); text == "" {
- return nil
- }
- // Make sure it looks ok and return it if so
- if len(text) != 40 {
- log.Error("Invalid address length, please retry")
- continue
- }
- bigaddr, _ := new(big.Int).SetString(text, 16)
- address := common.BigToAddress(bigaddr)
- return &address
- }
-}
-
-// readDefaultAddress reads a single line from stdin, trimming if from spaces and
-// converts it to an Ethereum address. If an empty line is entered, the default
-// value is returned.
-func (w *wizard) readDefaultAddress(def common.Address) common.Address {
- for {
- // Read the address from the user
- text := promptInput("> 0x")
- if text = strings.TrimSpace(text); text == "" {
- return def
- }
- // Make sure it looks ok and return it if so
- if len(text) != 40 {
- log.Error("Invalid address length, please retry")
- continue
- }
- bigaddr, _ := new(big.Int).SetString(text, 16)
- return common.BigToAddress(bigaddr)
- }
-}
-
-// readJSON reads a raw JSON message and returns it.
-func (w *wizard) readJSON() string {
- var blob json.RawMessage
-
- for {
- text := promptInput("> ")
- reader := strings.NewReader(text)
- if err := json.NewDecoder(reader).Decode(&blob); err != nil {
- log.Error("Invalid JSON, please try again", "err", err)
- continue
- }
- return string(blob)
- }
-}
-
-// readIPAddress reads a single line from stdin, trimming if from spaces and
-// returning it if it's convertible to an IP address. The reason for keeping
-// the user input format instead of returning a Go net.IP is to match with
-// weird formats used by ethstats, which compares IPs textually, not by value.
-func (w *wizard) readIPAddress() string {
- for {
- // Read the IP address from the user
- fmt.Printf("> ")
- text := promptInput("> ")
- if text = strings.TrimSpace(text); text == "" {
- return ""
- }
- // Make sure it looks ok and return it if so
- if ip := net.ParseIP(text); ip == nil {
- log.Error("Invalid IP address, please retry")
- continue
- }
- return text
- }
-}
diff --git a/cmd/puppeth/wizard_dashboard.go b/cmd/puppeth/wizard_dashboard.go
deleted file mode 100644
index b64bdca0b9..0000000000
--- a/cmd/puppeth/wizard_dashboard.go
+++ /dev/null
@@ -1,152 +0,0 @@
-// Copyright 2017 The go-ethereum Authors
-// This file is part of go-ethereum.
-//
-// go-ethereum is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// go-ethereum is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with go-ethereum. If not, see .
-
-package main
-
-import (
- "fmt"
-
- "github.com/ethereum/go-ethereum/log"
-)
-
-// deployDashboard queries the user for various input on deploying a web-service
-// dashboard, after which is pushes the container.
-func (w *wizard) deployDashboard() {
- // Select the server to interact with
- server := w.selectServer()
- if server == "" {
- return
- }
- client := w.servers[server]
-
- // Retrieve any active dashboard configurations from the server
- infos, err := checkDashboard(client, w.network)
- if err != nil {
- infos = &dashboardInfos{
- port: 80,
- host: client.server,
- }
- }
- existed := err == nil
-
- // Figure out which port to listen on
- fmt.Println()
- fmt.Printf("Which port should the dashboard listen on? (default = %d)\n", infos.port)
- infos.port = w.readDefaultInt(infos.port)
-
- // Figure which virtual-host to deploy the dashboard on
- infos.host, err = w.ensureVirtualHost(client, infos.port, infos.host)
- if err != nil {
- log.Error("Failed to decide on dashboard host", "err", err)
- return
- }
- // Port and proxy settings retrieved, figure out which services are available
- available := make(map[string][]string)
- for server, services := range w.services {
- for _, service := range services {
- available[service] = append(available[service], server)
- }
- }
- for _, service := range []string{"ethstats", "explorer", "faucet"} {
- // Gather all the locally hosted pages of this type
- var pages []string
- for _, server := range available[service] {
- client := w.servers[server]
- if client == nil {
- continue
- }
- // If there's a service running on the machine, retrieve it's port number
- var port int
- switch service {
- case "ethstats":
- if infos, err := checkEthstats(client, w.network); err == nil {
- port = infos.port
- }
- case "explorer":
- if infos, err := checkExplorer(client, w.network); err == nil {
- port = infos.port
- }
- case "faucet":
- if infos, err := checkFaucet(client, w.network); err == nil {
- port = infos.port
- }
- }
- if page, err := resolve(client, w.network, service, port); err == nil && page != "" {
- pages = append(pages, page)
- }
- }
- // Prompt the user to chose one, enter manually or simply not list this service
- defLabel, defChoice := "don't list", len(pages)+2
- if len(pages) > 0 {
- defLabel, defChoice = pages[0], 1
- }
- fmt.Println()
- fmt.Printf("Which %s service to list? (default = %s)\n", service, defLabel)
- for i, page := range pages {
- fmt.Printf(" %d. %s\n", i+1, page)
- }
- fmt.Printf(" %d. List external %s service\n", len(pages)+1, service)
- fmt.Printf(" %d. Don't list any %s service\n", len(pages)+2, service)
-
- choice := w.readDefaultInt(defChoice)
- if choice < 0 || choice > len(pages)+2 {
- log.Error("Invalid listing choice, aborting")
- return
- }
- var page string
- switch {
- case choice <= len(pages):
- page = pages[choice-1]
- case choice == len(pages)+1:
- fmt.Println()
- fmt.Printf("Which address is the external %s service at?\n", service)
- page = w.readString()
- default:
- // No service hosting for this
- }
- // Save the users choice
- switch service {
- case "ethstats":
- infos.ethstats = page
- case "explorer":
- infos.explorer = page
- case "faucet":
- infos.faucet = page
- }
- }
- // If we have ethstats running, ask whether to make the secret public or not
- if w.conf.ethstats != "" {
- fmt.Println()
- fmt.Println("Include ethstats secret on dashboard (y/n)? (default = yes)")
- infos.trusted = w.readDefaultYesNo(true)
- }
- // Try to deploy the dashboard container on the host
- nocache := false
- if existed {
- fmt.Println()
- fmt.Printf("Should the dashboard be built from scratch (y/n)? (default = no)\n")
- nocache = w.readDefaultYesNo(false)
- }
- if out, err := deployDashboard(client, w.network, &w.conf, infos, nocache); err != nil {
- log.Error("Failed to deploy dashboard container", "err", err)
- if len(out) > 0 {
- fmt.Printf("%s\n", out)
- }
- return
- }
- // All ok, run a network scan to pick any changes up
- w.networkStats()
-}
diff --git a/cmd/puppeth/wizard_ethstats.go b/cmd/puppeth/wizard_ethstats.go
deleted file mode 100644
index 95cab9da46..0000000000
--- a/cmd/puppeth/wizard_ethstats.go
+++ /dev/null
@@ -1,126 +0,0 @@
-// Copyright 2017 The go-ethereum Authors
-// This file is part of go-ethereum.
-//
-// go-ethereum is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// go-ethereum is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with go-ethereum. If not, see .
-
-package main
-
-import (
- "fmt"
- "sort"
-
- "github.com/ethereum/go-ethereum/log"
-)
-
-// deployEthstats queries the user for various input on deploying an ethstats
-// monitoring server, after which it executes it.
-func (w *wizard) deployEthstats() {
- // Select the server to interact with
- server := w.selectServer()
- if server == "" {
- return
- }
- client := w.servers[server]
-
- // Retrieve any active ethstats configurations from the server
- infos, err := checkEthstats(client, w.network)
- if err != nil {
- infos = ðstatsInfos{
- port: 80,
- host: client.server,
- secret: "",
- }
- }
- existed := err == nil
-
- // Figure out which port to listen on
- fmt.Println()
- fmt.Printf("Which port should ethstats listen on? (default = %d)\n", infos.port)
- infos.port = w.readDefaultInt(infos.port)
-
- // Figure which virtual-host to deploy ethstats on
- if infos.host, err = w.ensureVirtualHost(client, infos.port, infos.host); err != nil {
- log.Error("Failed to decide on ethstats host", "err", err)
- return
- }
- // Port and proxy settings retrieved, figure out the secret and boot ethstats
- fmt.Println()
- if infos.secret == "" {
- fmt.Printf("What should be the secret password for the API? (must not be empty)\n")
- infos.secret = w.readString()
- } else {
- fmt.Printf("What should be the secret password for the API? (default = %s)\n", infos.secret)
- infos.secret = w.readDefaultString(infos.secret)
- }
- // Gather any banned lists to ban from reporting
- if existed {
- fmt.Println()
- fmt.Printf("Keep existing IP %v in the banned list (y/n)? (default = yes)\n", infos.banned)
- if !w.readDefaultYesNo(true) {
- // The user might want to clear the entire list, although generally probably not
- fmt.Println()
- fmt.Printf("Clear out the banned list and start over (y/n)? (default = no)\n")
- if w.readDefaultYesNo(false) {
- infos.banned = nil
- }
- // Offer the user to explicitly add/remove certain IP addresses
- fmt.Println()
- fmt.Println("Which additional IP addresses should be in the banned list?")
- for {
- if ip := w.readIPAddress(); ip != "" {
- infos.banned = append(infos.banned, ip)
- continue
- }
- break
- }
- fmt.Println()
- fmt.Println("Which IP addresses should not be in the banned list?")
- for {
- if ip := w.readIPAddress(); ip != "" {
- for i, addr := range infos.banned {
- if ip == addr {
- infos.banned = append(infos.banned[:i], infos.banned[i+1:]...)
- break
- }
- }
- continue
- }
- break
- }
- sort.Strings(infos.banned)
- }
- }
- // Try to deploy the ethstats server on the host
- nocache := false
- if existed {
- fmt.Println()
- fmt.Printf("Should the ethstats be built from scratch (y/n)? (default = no)\n")
- nocache = w.readDefaultYesNo(false)
- }
- trusted := make([]string, 0, len(w.servers))
- for _, client := range w.servers {
- if client != nil {
- trusted = append(trusted, client.address)
- }
- }
- if out, err := deployEthstats(client, w.network, infos.port, infos.secret, infos.host, trusted, infos.banned, nocache); err != nil {
- log.Error("Failed to deploy ethstats container", "err", err)
- if len(out) > 0 {
- fmt.Printf("%s\n", out)
- }
- return
- }
- // All ok, run a network scan to pick any changes up
- w.networkStats()
-}
diff --git a/cmd/puppeth/wizard_explorer.go b/cmd/puppeth/wizard_explorer.go
deleted file mode 100644
index 1df9cbc0f3..0000000000
--- a/cmd/puppeth/wizard_explorer.go
+++ /dev/null
@@ -1,120 +0,0 @@
-// Copyright 2017 The go-ethereum Authors
-// This file is part of go-ethereum.
-//
-// go-ethereum is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// go-ethereum is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with go-ethereum. If not, see .
-
-package main
-
-import (
- "encoding/json"
- "fmt"
- "time"
-
- "github.com/ethereum/go-ethereum/log"
-)
-
-// deployExplorer creates a new block explorer based on some user input.
-func (w *wizard) deployExplorer() {
- // Do some sanity check before the user wastes time on input
- if w.conf.Genesis == nil {
- log.Error("No genesis block configured")
- return
- }
- if w.conf.ethstats == "" {
- log.Error("No ethstats server configured")
- return
- }
- // Select the server to interact with
- server := w.selectServer()
- if server == "" {
- return
- }
- client := w.servers[server]
-
- // Retrieve any active node configurations from the server
- infos, err := checkExplorer(client, w.network)
- if err != nil {
- infos = &explorerInfos{
- node: &nodeInfos{port: 30303},
- port: 80,
- host: client.server,
- }
- }
- existed := err == nil
-
- infos.node.genesis, _ = json.MarshalIndent(w.conf.Genesis, "", " ")
- infos.node.network = w.conf.Genesis.Config.ChainID.Int64()
-
- // Figure out which port to listen on
- fmt.Println()
- fmt.Printf("Which port should the explorer listen on? (default = %d)\n", infos.port)
- infos.port = w.readDefaultInt(infos.port)
-
- // Figure which virtual-host to deploy ethstats on
- if infos.host, err = w.ensureVirtualHost(client, infos.port, infos.host); err != nil {
- log.Error("Failed to decide on explorer host", "err", err)
- return
- }
- // Figure out where the user wants to store the persistent data
- fmt.Println()
- if infos.node.datadir == "" {
- fmt.Printf("Where should node data be stored on the remote machine?\n")
- infos.node.datadir = w.readString()
- } else {
- fmt.Printf("Where should node data be stored on the remote machine? (default = %s)\n", infos.node.datadir)
- infos.node.datadir = w.readDefaultString(infos.node.datadir)
- }
- // Figure out where the user wants to store the persistent data for backend database
- fmt.Println()
- if infos.dbdir == "" {
- fmt.Printf("Where should postgres data be stored on the remote machine?\n")
- infos.dbdir = w.readString()
- } else {
- fmt.Printf("Where should postgres data be stored on the remote machine? (default = %s)\n", infos.dbdir)
- infos.dbdir = w.readDefaultString(infos.dbdir)
- }
- // Figure out which port to listen on
- fmt.Println()
- fmt.Printf("Which TCP/UDP port should the archive node listen on? (default = %d)\n", infos.node.port)
- infos.node.port = w.readDefaultInt(infos.node.port)
-
- // Set a proper name to report on the stats page
- fmt.Println()
- if infos.node.ethstats == "" {
- fmt.Printf("What should the explorer be called on the stats page?\n")
- infos.node.ethstats = w.readString() + ":" + w.conf.ethstats
- } else {
- fmt.Printf("What should the explorer be called on the stats page? (default = %s)\n", infos.node.ethstats)
- infos.node.ethstats = w.readDefaultString(infos.node.ethstats) + ":" + w.conf.ethstats
- }
- // Try to deploy the explorer on the host
- nocache := false
- if existed {
- fmt.Println()
- fmt.Printf("Should the explorer be built from scratch (y/n)? (default = no)\n")
- nocache = w.readDefaultYesNo(false)
- }
- if out, err := deployExplorer(client, w.network, w.conf.bootnodes, infos, nocache, w.conf.Genesis.Config.Clique != nil); err != nil {
- log.Error("Failed to deploy explorer container", "err", err)
- if len(out) > 0 {
- fmt.Printf("%s\n", out)
- }
- return
- }
- // All ok, run a network scan to pick any changes up
- log.Info("Waiting for node to finish booting")
- time.Sleep(3 * time.Second)
-
- w.networkStats()
-}
diff --git a/cmd/puppeth/wizard_faucet.go b/cmd/puppeth/wizard_faucet.go
deleted file mode 100644
index 65d4e8b8ed..0000000000
--- a/cmd/puppeth/wizard_faucet.go
+++ /dev/null
@@ -1,195 +0,0 @@
-// Copyright 2017 The go-ethereum Authors
-// This file is part of go-ethereum.
-//
-// go-ethereum is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// go-ethereum is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with go-ethereum. If not, see .
-
-package main
-
-import (
- "encoding/json"
- "fmt"
-
- "github.com/ethereum/go-ethereum/accounts/keystore"
- "github.com/ethereum/go-ethereum/log"
-)
-
-// deployFaucet queries the user for various input on deploying a faucet, after
-// which it executes it.
-func (w *wizard) deployFaucet() {
- // Select the server to interact with
- server := w.selectServer()
- if server == "" {
- return
- }
- client := w.servers[server]
-
- // Retrieve any active faucet configurations from the server
- infos, err := checkFaucet(client, w.network)
- if err != nil {
- infos = &faucetInfos{
- node: &nodeInfos{port: 30303, peersTotal: 25},
- port: 80,
- host: client.server,
- amount: 1,
- minutes: 1440,
- tiers: 3,
- }
- }
- existed := err == nil
-
- infos.node.genesis, _ = json.MarshalIndent(w.conf.Genesis, "", " ")
- infos.node.network = w.conf.Genesis.Config.ChainID.Int64()
-
- // Figure out which port to listen on
- fmt.Println()
- fmt.Printf("Which port should the faucet listen on? (default = %d)\n", infos.port)
- infos.port = w.readDefaultInt(infos.port)
-
- // Figure which virtual-host to deploy ethstats on
- if infos.host, err = w.ensureVirtualHost(client, infos.port, infos.host); err != nil {
- log.Error("Failed to decide on faucet host", "err", err)
- return
- }
- // Port and proxy settings retrieved, figure out the funding amount per period configurations
- fmt.Println()
- fmt.Printf("How many Ethers to release per request? (default = %d)\n", infos.amount)
- infos.amount = w.readDefaultInt(infos.amount)
-
- fmt.Println()
- fmt.Printf("How many minutes to enforce between requests? (default = %d)\n", infos.minutes)
- infos.minutes = w.readDefaultInt(infos.minutes)
-
- fmt.Println()
- fmt.Printf("How many funding tiers to feature (x2.5 amounts, x3 timeout)? (default = %d)\n", infos.tiers)
- infos.tiers = w.readDefaultInt(infos.tiers)
- if infos.tiers == 0 {
- log.Error("At least one funding tier must be set")
- return
- }
- // Accessing the reCaptcha service requires API authorizations, request it
- if infos.captchaToken != "" {
- fmt.Println()
- fmt.Println("Reuse previous reCaptcha API authorization (y/n)? (default = yes)")
- if !w.readDefaultYesNo(true) {
- infos.captchaToken, infos.captchaSecret = "", ""
- }
- }
- if infos.captchaToken == "" {
- // No previous authorization (or old one discarded)
- fmt.Println()
- fmt.Println("Enable reCaptcha protection against robots (y/n)? (default = no)")
- if !w.readDefaultYesNo(false) {
- log.Warn("Users will be able to requests funds via automated scripts")
- } else {
- // Captcha protection explicitly requested, read the site and secret keys
- fmt.Println()
- fmt.Printf("What is the reCaptcha site key to authenticate human users?\n")
- infos.captchaToken = w.readString()
-
- fmt.Println()
- fmt.Printf("What is the reCaptcha secret key to verify authentications? (won't be echoed)\n")
- infos.captchaSecret = w.readPassword()
- }
- }
- // Accessing the Twitter API requires a bearer token, request it
- if infos.twitterToken != "" {
- fmt.Println()
- fmt.Println("Reuse previous Twitter API token (y/n)? (default = yes)")
- if !w.readDefaultYesNo(true) {
- infos.twitterToken = ""
- }
- }
- if infos.twitterToken == "" {
- // No previous twitter token (or old one discarded)
- fmt.Println()
- fmt.Println()
- fmt.Printf("What is the Twitter API app Bearer token?\n")
- infos.twitterToken = w.readString()
- }
- // Figure out where the user wants to store the persistent data
- fmt.Println()
- if infos.node.datadir == "" {
- fmt.Printf("Where should data be stored on the remote machine?\n")
- infos.node.datadir = w.readString()
- } else {
- fmt.Printf("Where should data be stored on the remote machine? (default = %s)\n", infos.node.datadir)
- infos.node.datadir = w.readDefaultString(infos.node.datadir)
- }
- // Figure out which port to listen on
- fmt.Println()
- fmt.Printf("Which TCP/UDP port should the light client listen on? (default = %d)\n", infos.node.port)
- infos.node.port = w.readDefaultInt(infos.node.port)
-
- // Set a proper name to report on the stats page
- fmt.Println()
- if infos.node.ethstats == "" {
- fmt.Printf("What should the node be called on the stats page?\n")
- infos.node.ethstats = w.readString() + ":" + w.conf.ethstats
- } else {
- fmt.Printf("What should the node be called on the stats page? (default = %s)\n", infos.node.ethstats)
- infos.node.ethstats = w.readDefaultString(infos.node.ethstats) + ":" + w.conf.ethstats
- }
- // Load up the credential needed to release funds
- if infos.node.keyJSON != "" {
- if key, err := keystore.DecryptKey([]byte(infos.node.keyJSON), infos.node.keyPass); err != nil {
- infos.node.keyJSON, infos.node.keyPass = "", ""
- } else {
- fmt.Println()
- fmt.Printf("Reuse previous (%s) funding account (y/n)? (default = yes)\n", key.Address.Hex())
- if !w.readDefaultYesNo(true) {
- infos.node.keyJSON, infos.node.keyPass = "", ""
- }
- }
- }
- for i := 0; i < 3 && infos.node.keyJSON == ""; i++ {
- fmt.Println()
- fmt.Println("Please paste the faucet's funding account key JSON:")
- infos.node.keyJSON = w.readJSON()
-
- fmt.Println()
- fmt.Println("What's the unlock password for the account? (won't be echoed)")
- infos.node.keyPass = w.readPassword()
-
- if _, err := keystore.DecryptKey([]byte(infos.node.keyJSON), infos.node.keyPass); err != nil {
- log.Error("Failed to decrypt key with given password")
- infos.node.keyJSON = ""
- infos.node.keyPass = ""
- }
- }
- // Check if the user wants to run the faucet in debug mode (noauth)
- noauth := "n"
- if infos.noauth {
- noauth = "y"
- }
- fmt.Println()
- fmt.Printf("Permit non-authenticated funding requests (y/n)? (default = %v)\n", infos.noauth)
- infos.noauth = w.readDefaultString(noauth) != "n"
-
- // Try to deploy the faucet server on the host
- nocache := false
- if existed {
- fmt.Println()
- fmt.Printf("Should the faucet be built from scratch (y/n)? (default = no)\n")
- nocache = w.readDefaultYesNo(false)
- }
- if out, err := deployFaucet(client, w.network, w.conf.bootnodes, infos, nocache); err != nil {
- log.Error("Failed to deploy faucet container", "err", err)
- if len(out) > 0 {
- fmt.Printf("%s\n", out)
- }
- return
- }
- // All ok, run a network scan to pick any changes up
- w.networkStats()
-}
diff --git a/cmd/puppeth/wizard_genesis.go b/cmd/puppeth/wizard_genesis.go
deleted file mode 100644
index ac17bc7b27..0000000000
--- a/cmd/puppeth/wizard_genesis.go
+++ /dev/null
@@ -1,285 +0,0 @@
-// Copyright 2017 The go-ethereum Authors
-// This file is part of go-ethereum.
-//
-// go-ethereum is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// go-ethereum is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with go-ethereum. If not, see .
-
-package main
-
-import (
- "bytes"
- "encoding/json"
- "fmt"
- "io"
- "math/big"
- "math/rand"
- "net/http"
- "os"
- "path/filepath"
- "time"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/params"
-)
-
-// makeGenesis creates a new genesis struct based on some user input.
-func (w *wizard) makeGenesis() {
- // Construct a default genesis block
- genesis := &core.Genesis{
- Timestamp: uint64(time.Now().Unix()),
- GasLimit: 4700000,
- Difficulty: big.NewInt(524288),
- Alloc: make(core.GenesisAlloc),
- Config: ¶ms.ChainConfig{
- HomesteadBlock: big.NewInt(0),
- EIP150Block: big.NewInt(0),
- EIP155Block: big.NewInt(0),
- EIP158Block: big.NewInt(0),
- ByzantiumBlock: big.NewInt(0),
- ConstantinopleBlock: big.NewInt(0),
- PetersburgBlock: big.NewInt(0),
- IstanbulBlock: big.NewInt(0),
- },
- }
- // Figure out which consensus engine to choose
- fmt.Println()
- fmt.Println("Which consensus engine to use? (default = clique)")
- fmt.Println(" 1. Ethash - proof-of-work")
- fmt.Println(" 2. Clique - proof-of-authority")
-
- choice := w.read()
- switch {
- case choice == "1":
- // In case of ethash, we're pretty much done
- genesis.Config.Ethash = new(params.EthashConfig)
- genesis.ExtraData = make([]byte, 32)
-
- case choice == "" || choice == "2":
- // In the case of clique, configure the consensus parameters
- genesis.Difficulty = big.NewInt(1)
- genesis.Config.Clique = ¶ms.CliqueConfig{
- Period: 15,
- Epoch: 30000,
- }
- fmt.Println()
- fmt.Println("How many seconds should blocks take? (default = 15)")
- genesis.Config.Clique.Period = uint64(w.readDefaultInt(15))
-
- // We also need the initial list of signers
- fmt.Println()
- fmt.Println("Which accounts are allowed to seal? (mandatory at least one)")
-
- var signers []common.Address
- for {
- if address := w.readAddress(); address != nil {
- signers = append(signers, *address)
- continue
- }
- if len(signers) > 0 {
- break
- }
- }
- // Sort the signers and embed into the extra-data section
- for i := 0; i < len(signers); i++ {
- for j := i + 1; j < len(signers); j++ {
- if bytes.Compare(signers[i][:], signers[j][:]) > 0 {
- signers[i], signers[j] = signers[j], signers[i]
- }
- }
- }
- genesis.ExtraData = make([]byte, 32+len(signers)*common.AddressLength+65)
- for i, signer := range signers {
- copy(genesis.ExtraData[32+i*common.AddressLength:], signer[:])
- }
-
- default:
- log.Crit("Invalid consensus engine choice", "choice", choice)
- }
- // Consensus all set, just ask for initial funds and go
- fmt.Println()
- fmt.Println("Which accounts should be pre-funded? (advisable at least one)")
- for {
- // Read the address of the account to fund
- if address := w.readAddress(); address != nil {
- genesis.Alloc[*address] = core.GenesisAccount{
- Balance: new(big.Int).Lsh(big.NewInt(1), 256-7), // 2^256 / 128 (allow many pre-funds without balance overflows)
- }
- continue
- }
- break
- }
- fmt.Println()
- fmt.Println("Should the precompile-addresses (0x1 .. 0xff) be pre-funded with 1 wei? (advisable yes)")
- if w.readDefaultYesNo(true) {
- // Add a batch of precompile balances to avoid them getting deleted
- for i := int64(0); i < 256; i++ {
- genesis.Alloc[common.BigToAddress(big.NewInt(i))] = core.GenesisAccount{Balance: big.NewInt(1)}
- }
- }
- // Query the user for some custom extras
- fmt.Println()
- fmt.Println("Specify your chain/network ID if you want an explicit one (default = random)")
- genesis.Config.ChainID = new(big.Int).SetUint64(uint64(w.readDefaultInt(rand.Intn(65536))))
-
- // All done, store the genesis and flush to disk
- log.Info("Configured new genesis block")
-
- w.conf.Genesis = genesis
- w.conf.flush()
-}
-
-// importGenesis imports a Geth genesis spec into puppeth.
-func (w *wizard) importGenesis() {
- // Request the genesis JSON spec URL from the user
- fmt.Println()
- fmt.Println("Where's the genesis file? (local file or http/https url)")
- url := w.readURL()
-
- // Convert the various allowed URLs to a reader stream
- var reader io.Reader
-
- switch url.Scheme {
- case "http", "https":
- // Remote web URL, retrieve it via an HTTP client
- res, err := http.Get(url.String())
- if err != nil {
- log.Error("Failed to retrieve remote genesis", "err", err)
- return
- }
- defer res.Body.Close()
- reader = res.Body
-
- case "":
- // Schemaless URL, interpret as a local file
- file, err := os.Open(url.String())
- if err != nil {
- log.Error("Failed to open local genesis", "err", err)
- return
- }
- defer file.Close()
- reader = file
-
- default:
- log.Error("Unsupported genesis URL scheme", "scheme", url.Scheme)
- return
- }
- // Parse the genesis file and inject it successful
- var genesis core.Genesis
- if err := json.NewDecoder(reader).Decode(&genesis); err != nil {
- log.Error("Invalid genesis spec", "err", err)
- return
- }
- log.Info("Imported genesis block")
-
- w.conf.Genesis = &genesis
- w.conf.flush()
-}
-
-// manageGenesis permits the modification of chain configuration parameters in
-// a genesis config and the export of the entire genesis spec.
-func (w *wizard) manageGenesis() {
- // Figure out whether to modify or export the genesis
- fmt.Println()
- fmt.Println(" 1. Modify existing configurations")
- fmt.Println(" 2. Export genesis configurations")
- fmt.Println(" 3. Remove genesis configuration")
-
- choice := w.read()
- switch choice {
- case "1":
- // Fork rule updating requested, iterate over each fork
- fmt.Println()
- fmt.Printf("Which block should Homestead come into effect? (default = %v)\n", w.conf.Genesis.Config.HomesteadBlock)
- w.conf.Genesis.Config.HomesteadBlock = w.readDefaultBigInt(w.conf.Genesis.Config.HomesteadBlock)
-
- fmt.Println()
- fmt.Printf("Which block should EIP150 (Tangerine Whistle) come into effect? (default = %v)\n", w.conf.Genesis.Config.EIP150Block)
- w.conf.Genesis.Config.EIP150Block = w.readDefaultBigInt(w.conf.Genesis.Config.EIP150Block)
-
- fmt.Println()
- fmt.Printf("Which block should EIP155 (Spurious Dragon) come into effect? (default = %v)\n", w.conf.Genesis.Config.EIP155Block)
- w.conf.Genesis.Config.EIP155Block = w.readDefaultBigInt(w.conf.Genesis.Config.EIP155Block)
-
- fmt.Println()
- fmt.Printf("Which block should EIP158/161 (also Spurious Dragon) come into effect? (default = %v)\n", w.conf.Genesis.Config.EIP158Block)
- w.conf.Genesis.Config.EIP158Block = w.readDefaultBigInt(w.conf.Genesis.Config.EIP158Block)
-
- fmt.Println()
- fmt.Printf("Which block should Byzantium come into effect? (default = %v)\n", w.conf.Genesis.Config.ByzantiumBlock)
- w.conf.Genesis.Config.ByzantiumBlock = w.readDefaultBigInt(w.conf.Genesis.Config.ByzantiumBlock)
-
- fmt.Println()
- fmt.Printf("Which block should Constantinople come into effect? (default = %v)\n", w.conf.Genesis.Config.ConstantinopleBlock)
- w.conf.Genesis.Config.ConstantinopleBlock = w.readDefaultBigInt(w.conf.Genesis.Config.ConstantinopleBlock)
- if w.conf.Genesis.Config.PetersburgBlock == nil {
- w.conf.Genesis.Config.PetersburgBlock = w.conf.Genesis.Config.ConstantinopleBlock
- }
- fmt.Println()
- fmt.Printf("Which block should Petersburg come into effect? (default = %v)\n", w.conf.Genesis.Config.PetersburgBlock)
- w.conf.Genesis.Config.PetersburgBlock = w.readDefaultBigInt(w.conf.Genesis.Config.PetersburgBlock)
-
- fmt.Println()
- fmt.Printf("Which block should Istanbul come into effect? (default = %v)\n", w.conf.Genesis.Config.IstanbulBlock)
- w.conf.Genesis.Config.IstanbulBlock = w.readDefaultBigInt(w.conf.Genesis.Config.IstanbulBlock)
-
- fmt.Println()
- fmt.Printf("Which block should Berlin come into effect? (default = %v)\n", w.conf.Genesis.Config.BerlinBlock)
- w.conf.Genesis.Config.BerlinBlock = w.readDefaultBigInt(w.conf.Genesis.Config.BerlinBlock)
-
- fmt.Println()
- fmt.Printf("Which block should London come into effect? (default = %v)\n", w.conf.Genesis.Config.LondonBlock)
- w.conf.Genesis.Config.LondonBlock = w.readDefaultBigInt(w.conf.Genesis.Config.LondonBlock)
-
- out, _ := json.MarshalIndent(w.conf.Genesis.Config, "", " ")
- fmt.Printf("Chain configuration updated:\n\n%s\n", out)
-
- w.conf.flush()
-
- case "2":
- // Save whatever genesis configuration we currently have
- fmt.Println()
- fmt.Printf("Which folder to save the genesis spec into? (default = current)\n")
- fmt.Printf(" Will create %s.json\n", w.network)
-
- folder := w.readDefaultString(".")
- if err := os.MkdirAll(folder, 0755); err != nil {
- log.Error("Failed to create spec folder", "folder", folder, "err", err)
- return
- }
- out, _ := json.MarshalIndent(w.conf.Genesis, "", " ")
-
- // Export the native genesis spec used by puppeth and Geth
- gethJson := filepath.Join(folder, fmt.Sprintf("%s.json", w.network))
- if err := os.WriteFile(gethJson, out, 0644); err != nil {
- log.Error("Failed to save genesis file", "err", err)
- return
- }
- log.Info("Saved native genesis chain spec", "path", gethJson)
-
- case "3":
- // Make sure we don't have any services running
- if len(w.conf.servers()) > 0 {
- log.Error("Genesis reset requires all services and servers torn down")
- return
- }
- log.Info("Genesis block destroyed")
-
- w.conf.Genesis = nil
- w.conf.flush()
- default:
- log.Error("That's not something I can do")
- return
- }
-}
diff --git a/cmd/puppeth/wizard_intro.go b/cmd/puppeth/wizard_intro.go
deleted file mode 100644
index adac943cc3..0000000000
--- a/cmd/puppeth/wizard_intro.go
+++ /dev/null
@@ -1,157 +0,0 @@
-// Copyright 2017 The go-ethereum Authors
-// This file is part of go-ethereum.
-//
-// go-ethereum is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// go-ethereum is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with go-ethereum. If not, see .
-
-package main
-
-import (
- "encoding/json"
- "fmt"
- "os"
- "path/filepath"
- "strings"
-
- "github.com/ethereum/go-ethereum/log"
-)
-
-// makeWizard creates and returns a new puppeth wizard.
-func makeWizard(network string) *wizard {
- return &wizard{
- network: network,
- conf: config{
- Servers: make(map[string][]byte),
- },
- servers: make(map[string]*sshClient),
- services: make(map[string][]string),
- }
-}
-
-// run displays some useful infos to the user, starting on the journey of
-// setting up a new or managing an existing Ethereum private network.
-func (w *wizard) run() {
- fmt.Println("+-----------------------------------------------------------+")
- fmt.Println("| Welcome to puppeth, your Ethereum private network manager |")
- fmt.Println("| |")
- fmt.Println("| This tool lets you create a new Ethereum network down to |")
- fmt.Println("| the genesis block, bootnodes, miners and ethstats servers |")
- fmt.Println("| without the hassle that it would normally entail. |")
- fmt.Println("| |")
- fmt.Println("| Puppeth uses SSH to dial in to remote servers, and builds |")
- fmt.Println("| its network components out of Docker containers using the |")
- fmt.Println("| docker-compose toolset. |")
- fmt.Println("+-----------------------------------------------------------+")
- fmt.Println()
-
- // Make sure we have a good network name to work with fmt.Println()
- // Docker accepts hyphens in image names, but doesn't like it for container names
- if w.network == "" {
- fmt.Println("Please specify a network name to administer (no spaces, hyphens or capital letters please)")
- for {
- w.network = w.readString()
- if !strings.Contains(w.network, " ") && !strings.Contains(w.network, "-") && strings.ToLower(w.network) == w.network {
- fmt.Printf("\nSweet, you can set this via --network=%s next time!\n\n", w.network)
- break
- }
- log.Error("I also like to live dangerously, still no spaces, hyphens or capital letters")
- }
- }
- log.Info("Administering Ethereum network", "name", w.network)
-
- // Load initial configurations and connect to all live servers
- w.conf.path = filepath.Join(os.Getenv("HOME"), ".puppeth", w.network)
-
- blob, err := os.ReadFile(w.conf.path)
- if err != nil {
- log.Warn("No previous configurations found", "path", w.conf.path)
- } else if err := json.Unmarshal(blob, &w.conf); err != nil {
- log.Crit("Previous configuration corrupted", "path", w.conf.path, "err", err)
- } else {
- // Dial all previously known servers
- for server, pubkey := range w.conf.Servers {
- log.Info("Dialing previously configured server", "server", server)
- client, err := dial(server, pubkey)
- if err != nil {
- log.Error("Previous server unreachable", "server", server, "err", err)
- }
- w.lock.Lock()
- w.servers[server] = client
- w.lock.Unlock()
- }
- w.networkStats()
- }
- // Basics done, loop ad infinitum about what to do
- for {
- fmt.Println()
- fmt.Println("What would you like to do? (default = stats)")
- fmt.Println(" 1. Show network stats")
- if w.conf.Genesis == nil {
- fmt.Println(" 2. Configure new genesis")
- } else {
- fmt.Println(" 2. Manage existing genesis")
- }
- if len(w.servers) == 0 {
- fmt.Println(" 3. Track new remote server")
- } else {
- fmt.Println(" 3. Manage tracked machines")
- }
- if len(w.services) == 0 {
- fmt.Println(" 4. Deploy network components")
- } else {
- fmt.Println(" 4. Manage network components")
- }
-
- choice := w.read()
- switch {
- case choice == "" || choice == "1":
- w.networkStats()
-
- case choice == "2":
- if w.conf.Genesis == nil {
- fmt.Println()
- fmt.Println("What would you like to do? (default = create)")
- fmt.Println(" 1. Create new genesis from scratch")
- fmt.Println(" 2. Import already existing genesis")
-
- choice := w.read()
- switch {
- case choice == "" || choice == "1":
- w.makeGenesis()
- case choice == "2":
- w.importGenesis()
- default:
- log.Error("That's not something I can do")
- }
- } else {
- w.manageGenesis()
- }
- case choice == "3":
- if len(w.servers) == 0 {
- if w.makeServer() != "" {
- w.networkStats()
- }
- } else {
- w.manageServers()
- }
- case choice == "4":
- if len(w.services) == 0 {
- w.deployComponent()
- } else {
- w.manageComponents()
- }
- default:
- log.Error("That's not something I can do")
- }
- }
-}
diff --git a/cmd/puppeth/wizard_netstats.go b/cmd/puppeth/wizard_netstats.go
deleted file mode 100644
index 7b5671e6df..0000000000
--- a/cmd/puppeth/wizard_netstats.go
+++ /dev/null
@@ -1,284 +0,0 @@
-// Copyright 2017 The go-ethereum Authors
-// This file is part of go-ethereum.
-//
-// go-ethereum is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// go-ethereum is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with go-ethereum. If not, see .
-
-package main
-
-import (
- "encoding/json"
- "os"
- "sort"
- "strings"
- "sync"
-
- "github.com/ethereum/go-ethereum/core"
- "github.com/ethereum/go-ethereum/log"
- "github.com/olekukonko/tablewriter"
-)
-
-// networkStats verifies the status of network components and generates a protip
-// configuration set to give users hints on how to do various tasks.
-func (w *wizard) networkStats() {
- if len(w.servers) == 0 {
- log.Info("No remote machines to gather stats from")
- return
- }
- // Clear out some previous configs to refill from current scan
- w.conf.ethstats = ""
- w.conf.bootnodes = w.conf.bootnodes[:0]
-
- // Iterate over all the specified hosts and check their status
- var pend sync.WaitGroup
-
- stats := make(serverStats)
- for server, pubkey := range w.conf.Servers {
- pend.Add(1)
-
- // Gather the service stats for each server concurrently
- go func(server string, pubkey []byte) {
- defer pend.Done()
-
- stat := w.gatherStats(server, pubkey, w.servers[server])
-
- // All status checks complete, report and check next server
- w.lock.Lock()
- defer w.lock.Unlock()
-
- delete(w.services, server)
- for service := range stat.services {
- w.services[server] = append(w.services[server], service)
- }
- stats[server] = stat
- }(server, pubkey)
- }
- pend.Wait()
-
- // Print any collected stats and return
- stats.render()
-}
-
-// gatherStats gathers service statistics for a particular remote server.
-func (w *wizard) gatherStats(server string, pubkey []byte, client *sshClient) *serverStat {
- // Gather some global stats to feed into the wizard
- var (
- genesis string
- ethstats string
- bootnodes []string
- )
- // Ensure a valid SSH connection to the remote server
- logger := log.New("server", server)
- logger.Info("Starting remote server health-check")
-
- stat := &serverStat{
- services: make(map[string]map[string]string),
- }
- if client == nil {
- conn, err := dial(server, pubkey)
- if err != nil {
- logger.Error("Failed to establish remote connection", "err", err)
- stat.failure = err.Error()
- return stat
- }
- client = conn
- }
- stat.address = client.address
-
- // Client connected one way or another, run health-checks
- logger.Debug("Checking for nginx availability")
- if infos, err := checkNginx(client, w.network); err != nil {
- if err != ErrServiceUnknown {
- stat.services["nginx"] = map[string]string{"offline": err.Error()}
- }
- } else {
- stat.services["nginx"] = infos.Report()
- }
- logger.Debug("Checking for ethstats availability")
- if infos, err := checkEthstats(client, w.network); err != nil {
- if err != ErrServiceUnknown {
- stat.services["ethstats"] = map[string]string{"offline": err.Error()}
- }
- } else {
- stat.services["ethstats"] = infos.Report()
- ethstats = infos.config
- }
- logger.Debug("Checking for bootnode availability")
- if infos, err := checkNode(client, w.network, true); err != nil {
- if err != ErrServiceUnknown {
- stat.services["bootnode"] = map[string]string{"offline": err.Error()}
- }
- } else {
- stat.services["bootnode"] = infos.Report()
-
- genesis = string(infos.genesis)
- bootnodes = append(bootnodes, infos.enode)
- }
- logger.Debug("Checking for sealnode availability")
- if infos, err := checkNode(client, w.network, false); err != nil {
- if err != ErrServiceUnknown {
- stat.services["sealnode"] = map[string]string{"offline": err.Error()}
- }
- } else {
- stat.services["sealnode"] = infos.Report()
- genesis = string(infos.genesis)
- }
- logger.Debug("Checking for explorer availability")
- if infos, err := checkExplorer(client, w.network); err != nil {
- if err != ErrServiceUnknown {
- stat.services["explorer"] = map[string]string{"offline": err.Error()}
- }
- } else {
- stat.services["explorer"] = infos.Report()
- }
- logger.Debug("Checking for faucet availability")
- if infos, err := checkFaucet(client, w.network); err != nil {
- if err != ErrServiceUnknown {
- stat.services["faucet"] = map[string]string{"offline": err.Error()}
- }
- } else {
- stat.services["faucet"] = infos.Report()
- }
- logger.Debug("Checking for dashboard availability")
- if infos, err := checkDashboard(client, w.network); err != nil {
- if err != ErrServiceUnknown {
- stat.services["dashboard"] = map[string]string{"offline": err.Error()}
- }
- } else {
- stat.services["dashboard"] = infos.Report()
- }
- // Feed and newly discovered information into the wizard
- w.lock.Lock()
- defer w.lock.Unlock()
-
- if genesis != "" && w.conf.Genesis == nil {
- g := new(core.Genesis)
- if err := json.Unmarshal([]byte(genesis), g); err != nil {
- log.Error("Failed to parse remote genesis", "err", err)
- } else {
- w.conf.Genesis = g
- }
- }
- if ethstats != "" {
- w.conf.ethstats = ethstats
- }
- w.conf.bootnodes = append(w.conf.bootnodes, bootnodes...)
-
- return stat
-}
-
-// serverStat is a collection of service configuration parameters and health
-// check reports to print to the user.
-type serverStat struct {
- address string
- failure string
- services map[string]map[string]string
-}
-
-// serverStats is a collection of server stats for multiple hosts.
-type serverStats map[string]*serverStat
-
-// render converts the gathered statistics into a user friendly tabular report
-// and prints it to the standard output.
-func (stats serverStats) render() {
- // Start gathering service statistics and config parameters
- table := tablewriter.NewWriter(os.Stdout)
-
- table.SetHeader([]string{"Server", "Address", "Service", "Config", "Value"})
- table.SetAlignment(tablewriter.ALIGN_LEFT)
- table.SetColWidth(40)
-
- // Find the longest lines for all columns for the hacked separator
- separator := make([]string, 5)
- for server, stat := range stats {
- if len(server) > len(separator[0]) {
- separator[0] = strings.Repeat("-", len(server))
- }
- if len(stat.address) > len(separator[1]) {
- separator[1] = strings.Repeat("-", len(stat.address))
- }
- if len(stat.failure) > len(separator[1]) {
- separator[1] = strings.Repeat("-", len(stat.failure))
- }
- for service, configs := range stat.services {
- if len(service) > len(separator[2]) {
- separator[2] = strings.Repeat("-", len(service))
- }
- for config, value := range configs {
- if len(config) > len(separator[3]) {
- separator[3] = strings.Repeat("-", len(config))
- }
- for _, val := range strings.Split(value, "\n") {
- if len(val) > len(separator[4]) {
- separator[4] = strings.Repeat("-", len(val))
- }
- }
- }
- }
- }
- // Fill up the server report in alphabetical order
- servers := make([]string, 0, len(stats))
- for server := range stats {
- servers = append(servers, server)
- }
- sort.Strings(servers)
-
- for i, server := range servers {
- // Add a separator between all servers
- if i > 0 {
- table.Append(separator)
- }
- // Fill up the service report in alphabetical order
- services := make([]string, 0, len(stats[server].services))
- for service := range stats[server].services {
- services = append(services, service)
- }
- sort.Strings(services)
-
- if len(services) == 0 {
- if stats[server].failure != "" {
- table.Append([]string{server, stats[server].failure, "", "", ""})
- } else {
- table.Append([]string{server, stats[server].address, "", "", ""})
- }
- }
- for j, service := range services {
- // Add an empty line between all services
- if j > 0 {
- table.Append([]string{"", "", "", separator[3], separator[4]})
- }
- // Fill up the config report in alphabetical order
- configs := make([]string, 0, len(stats[server].services[service]))
- for service := range stats[server].services[service] {
- configs = append(configs, service)
- }
- sort.Strings(configs)
-
- for k, config := range configs {
- for l, value := range strings.Split(stats[server].services[service][config], "\n") {
- switch {
- case j == 0 && k == 0 && l == 0:
- table.Append([]string{server, stats[server].address, service, config, value})
- case k == 0 && l == 0:
- table.Append([]string{"", "", service, config, value})
- case l == 0:
- table.Append([]string{"", "", "", config, value})
- default:
- table.Append([]string{"", "", "", "", value})
- }
- }
- }
- }
- }
- table.Render()
-}
diff --git a/cmd/puppeth/wizard_network.go b/cmd/puppeth/wizard_network.go
deleted file mode 100644
index d015e06ebb..0000000000
--- a/cmd/puppeth/wizard_network.go
+++ /dev/null
@@ -1,197 +0,0 @@
-// Copyright 2017 The go-ethereum Authors
-// This file is part of go-ethereum.
-//
-// go-ethereum is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// go-ethereum is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with go-ethereum. If not, see .
-
-package main
-
-import (
- "fmt"
- "strings"
-
- "github.com/ethereum/go-ethereum/log"
-)
-
-// manageServers displays a list of servers the user can disconnect from, and an
-// option to connect to new servers.
-func (w *wizard) manageServers() {
- // List all the servers we can disconnect, along with an entry to connect a new one
- fmt.Println()
-
- servers := w.conf.servers()
- for i, server := range servers {
- fmt.Printf(" %d. Disconnect %s\n", i+1, server)
- }
- fmt.Printf(" %d. Connect another server\n", len(w.conf.Servers)+1)
-
- choice := w.readInt()
- if choice < 0 || choice > len(w.conf.Servers)+1 {
- log.Error("Invalid server choice, aborting")
- return
- }
- // If the user selected an existing server, drop it
- if choice <= len(w.conf.Servers) {
- server := servers[choice-1]
- client := w.servers[server]
-
- delete(w.servers, server)
- if client != nil {
- client.Close()
- }
- delete(w.conf.Servers, server)
- w.conf.flush()
-
- log.Info("Disconnected existing server", "server", server)
- w.networkStats()
- return
- }
- // If the user requested connecting a new server, do it
- if w.makeServer() != "" {
- w.networkStats()
- }
-}
-
-// makeServer reads a single line from stdin and interprets it as
-// username:identity@hostname to connect to. It tries to establish a
-// new SSH session and also executing some baseline validations.
-//
-// If connection succeeds, the server is added to the wizards configs!
-func (w *wizard) makeServer() string {
- fmt.Println()
- fmt.Println("What is the remote server's address ([username[:identity]@]hostname[:port])?")
-
- // Read and dial the server to ensure docker is present
- input := w.readString()
-
- client, err := dial(input, nil)
- if err != nil {
- log.Error("Server not ready for puppeth", "err", err)
- return ""
- }
- // All checks passed, start tracking the server
- w.servers[input] = client
- w.conf.Servers[input] = client.pubkey
- w.conf.flush()
-
- return input
-}
-
-// selectServer lists the user all the currently known servers to choose from,
-// also granting the option to add a new one.
-func (w *wizard) selectServer() string {
- // List the available server to the user and wait for a choice
- fmt.Println()
- fmt.Println("Which server do you want to interact with?")
-
- servers := w.conf.servers()
- for i, server := range servers {
- fmt.Printf(" %d. %s\n", i+1, server)
- }
- fmt.Printf(" %d. Connect another server\n", len(w.conf.Servers)+1)
-
- choice := w.readInt()
- if choice < 0 || choice > len(w.conf.Servers)+1 {
- log.Error("Invalid server choice, aborting")
- return ""
- }
- // If the user requested connecting to a new server, go for it
- if choice <= len(w.conf.Servers) {
- return servers[choice-1]
- }
- return w.makeServer()
-}
-
-// manageComponents displays a list of network components the user can tear down
-// and an option
-func (w *wizard) manageComponents() {
- // List all the components we can tear down, along with an entry to deploy a new one
- fmt.Println()
-
- var serviceHosts, serviceNames []string
- for server, services := range w.services {
- for _, service := range services {
- serviceHosts = append(serviceHosts, server)
- serviceNames = append(serviceNames, service)
-
- fmt.Printf(" %d. Tear down %s on %s\n", len(serviceHosts), strings.Title(service), server)
- }
- }
- fmt.Printf(" %d. Deploy new network component\n", len(serviceHosts)+1)
-
- choice := w.readInt()
- if choice < 0 || choice > len(serviceHosts)+1 {
- log.Error("Invalid component choice, aborting")
- return
- }
- // If the user selected an existing service, destroy it
- if choice <= len(serviceHosts) {
- // Figure out the service to destroy and execute it
- service := serviceNames[choice-1]
- server := serviceHosts[choice-1]
- client := w.servers[server]
-
- if out, err := tearDown(client, w.network, service, true); err != nil {
- log.Error("Failed to tear down component", "err", err)
- if len(out) > 0 {
- fmt.Printf("%s\n", out)
- }
- return
- }
- // Clean up any references to it from out state
- services := w.services[server]
- for i, name := range services {
- if name == service {
- w.services[server] = append(services[:i], services[i+1:]...)
- if len(w.services[server]) == 0 {
- delete(w.services, server)
- }
- }
- }
- log.Info("Torn down existing component", "server", server, "service", service)
- return
- }
- // If the user requested deploying a new component, do it
- w.deployComponent()
-}
-
-// deployComponent displays a list of network components the user can deploy and
-// guides through the process.
-func (w *wizard) deployComponent() {
- // Print all the things we can deploy and wait or user choice
- fmt.Println()
- fmt.Println("What would you like to deploy? (recommended order)")
- fmt.Println(" 1. Ethstats - Network monitoring tool")
- fmt.Println(" 2. Bootnode - Entry point of the network")
- fmt.Println(" 3. Sealer - Full node minting new blocks")
- fmt.Println(" 4. Explorer - Chain analysis webservice")
- fmt.Println(" 5. Faucet - Crypto faucet to give away funds")
- fmt.Println(" 6. Dashboard - Website listing above web-services")
-
- switch w.read() {
- case "1":
- w.deployEthstats()
- case "2":
- w.deployNode(true)
- case "3":
- w.deployNode(false)
- case "4":
- w.deployExplorer()
- case "5":
- w.deployFaucet()
- case "6":
- w.deployDashboard()
- default:
- log.Error("That's not something I can do")
- }
-}
diff --git a/cmd/puppeth/wizard_nginx.go b/cmd/puppeth/wizard_nginx.go
deleted file mode 100644
index 8397b7fd57..0000000000
--- a/cmd/puppeth/wizard_nginx.go
+++ /dev/null
@@ -1,65 +0,0 @@
-// Copyright 2017 The go-ethereum Authors
-// This file is part of go-ethereum.
-//
-// go-ethereum is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// go-ethereum is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with go-ethereum. If not, see .
-
-package main
-
-import (
- "fmt"
-
- "github.com/ethereum/go-ethereum/log"
-)
-
-// ensureVirtualHost checks whether a reverse-proxy is running on the specified
-// host machine, and if yes requests a virtual host from the user to host a
-// specific web service on. If no proxy exists, the method will offer to deploy
-// one.
-//
-// If the user elects not to use a reverse proxy, an empty hostname is returned!
-func (w *wizard) ensureVirtualHost(client *sshClient, port int, def string) (string, error) {
- proxy, _ := checkNginx(client, w.network)
- if proxy != nil {
- // Reverse proxy is running, if ports match, we need a virtual host
- if proxy.port == port {
- fmt.Println()
- fmt.Printf("Shared port, which domain to assign? (default = %s)\n", def)
- return w.readDefaultString(def), nil
- }
- }
- // Reverse proxy is not running, offer to deploy a new one
- fmt.Println()
- fmt.Println("Allow sharing the port with other services (y/n)? (default = yes)")
- if w.readDefaultYesNo(true) {
- nocache := false
- if proxy != nil {
- fmt.Println()
- fmt.Printf("Should the reverse-proxy be rebuilt from scratch (y/n)? (default = no)\n")
- nocache = w.readDefaultYesNo(false)
- }
- if out, err := deployNginx(client, w.network, port, nocache); err != nil {
- log.Error("Failed to deploy reverse-proxy", "err", err)
- if len(out) > 0 {
- fmt.Printf("%s\n", out)
- }
- return "", err
- }
- // Reverse proxy deployed, ask again for the virtual-host
- fmt.Println()
- fmt.Printf("Proxy deployed, which domain to assign? (default = %s)\n", def)
- return w.readDefaultString(def), nil
- }
- // Reverse proxy not requested, deploy as a standalone service
- return "", nil
-}
diff --git a/cmd/puppeth/wizard_node.go b/cmd/puppeth/wizard_node.go
deleted file mode 100644
index c38750875a..0000000000
--- a/cmd/puppeth/wizard_node.go
+++ /dev/null
@@ -1,178 +0,0 @@
-// Copyright 2017 The go-ethereum Authors
-// This file is part of go-ethereum.
-//
-// go-ethereum is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// go-ethereum is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with go-ethereum. If not, see .
-
-package main
-
-import (
- "encoding/json"
- "fmt"
- "time"
-
- "github.com/ethereum/go-ethereum/accounts/keystore"
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/log"
-)
-
-// deployNode creates a new node configuration based on some user input.
-func (w *wizard) deployNode(boot bool) {
- // Do some sanity check before the user wastes time on input
- if w.conf.Genesis == nil {
- log.Error("No genesis block configured")
- return
- }
- if w.conf.ethstats == "" {
- log.Error("No ethstats server configured")
- return
- }
- // Select the server to interact with
- server := w.selectServer()
- if server == "" {
- return
- }
- client := w.servers[server]
-
- // Retrieve any active node configurations from the server
- infos, err := checkNode(client, w.network, boot)
- if err != nil {
- if boot {
- infos = &nodeInfos{port: 30303, peersTotal: 512, peersLight: 256}
- } else {
- infos = &nodeInfos{port: 30303, peersTotal: 50, peersLight: 0, gasLimit: 10, gasPrice: 1}
- }
- }
- existed := err == nil
-
- infos.genesis, _ = json.MarshalIndent(w.conf.Genesis, "", " ")
- infos.network = w.conf.Genesis.Config.ChainID.Int64()
-
- // Figure out where the user wants to store the persistent data
- fmt.Println()
- if infos.datadir == "" {
- fmt.Printf("Where should data be stored on the remote machine?\n")
- infos.datadir = w.readString()
- } else {
- fmt.Printf("Where should data be stored on the remote machine? (default = %s)\n", infos.datadir)
- infos.datadir = w.readDefaultString(infos.datadir)
- }
- if w.conf.Genesis.Config.Ethash != nil && !boot {
- fmt.Println()
- if infos.ethashdir == "" {
- fmt.Printf("Where should the ethash mining DAGs be stored on the remote machine?\n")
- infos.ethashdir = w.readString()
- } else {
- fmt.Printf("Where should the ethash mining DAGs be stored on the remote machine? (default = %s)\n", infos.ethashdir)
- infos.ethashdir = w.readDefaultString(infos.ethashdir)
- }
- }
- // Figure out which port to listen on
- fmt.Println()
- fmt.Printf("Which TCP/UDP port to listen on? (default = %d)\n", infos.port)
- infos.port = w.readDefaultInt(infos.port)
-
- // Figure out how many peers to allow (different based on node type)
- fmt.Println()
- fmt.Printf("How many peers to allow connecting? (default = %d)\n", infos.peersTotal)
- infos.peersTotal = w.readDefaultInt(infos.peersTotal)
-
- // Figure out how many light peers to allow (different based on node type)
- fmt.Println()
- fmt.Printf("How many light peers to allow connecting? (default = %d)\n", infos.peersLight)
- infos.peersLight = w.readDefaultInt(infos.peersLight)
-
- // Set a proper name to report on the stats page
- fmt.Println()
- if infos.ethstats == "" {
- fmt.Printf("What should the node be called on the stats page?\n")
- infos.ethstats = w.readString() + ":" + w.conf.ethstats
- } else {
- fmt.Printf("What should the node be called on the stats page? (default = %s)\n", infos.ethstats)
- infos.ethstats = w.readDefaultString(infos.ethstats) + ":" + w.conf.ethstats
- }
- // If the node is a miner/signer, load up needed credentials
- if !boot {
- if w.conf.Genesis.Config.Ethash != nil {
- // Ethash based miners only need an etherbase to mine against
- fmt.Println()
- if infos.etherbase == "" {
- fmt.Printf("What address should the miner use?\n")
- for {
- if address := w.readAddress(); address != nil {
- infos.etherbase = address.Hex()
- break
- }
- }
- } else {
- fmt.Printf("What address should the miner use? (default = %s)\n", infos.etherbase)
- infos.etherbase = w.readDefaultAddress(common.HexToAddress(infos.etherbase)).Hex()
- }
- } else if w.conf.Genesis.Config.Clique != nil {
- // If a previous signer was already set, offer to reuse it
- if infos.keyJSON != "" {
- if key, err := keystore.DecryptKey([]byte(infos.keyJSON), infos.keyPass); err != nil {
- infos.keyJSON, infos.keyPass = "", ""
- } else {
- fmt.Println()
- fmt.Printf("Reuse previous (%s) signing account (y/n)? (default = yes)\n", key.Address.Hex())
- if !w.readDefaultYesNo(true) {
- infos.keyJSON, infos.keyPass = "", ""
- }
- }
- }
- // Clique based signers need a keyfile and unlock password, ask if unavailable
- if infos.keyJSON == "" {
- fmt.Println()
- fmt.Println("Please paste the signer's key JSON:")
- infos.keyJSON = w.readJSON()
-
- fmt.Println()
- fmt.Println("What's the unlock password for the account? (won't be echoed)")
- infos.keyPass = w.readPassword()
-
- if _, err := keystore.DecryptKey([]byte(infos.keyJSON), infos.keyPass); err != nil {
- log.Error("Failed to decrypt key with given password")
- return
- }
- }
- }
- // Establish the gas dynamics to be enforced by the signer
- fmt.Println()
- fmt.Printf("What gas limit should full blocks target (MGas)? (default = %0.3f)\n", infos.gasLimit)
- infos.gasLimit = w.readDefaultFloat(infos.gasLimit)
-
- fmt.Println()
- fmt.Printf("What gas price should the signer require (GWei)? (default = %0.3f)\n", infos.gasPrice)
- infos.gasPrice = w.readDefaultFloat(infos.gasPrice)
- }
- // Try to deploy the full node on the host
- nocache := false
- if existed {
- fmt.Println()
- fmt.Printf("Should the node be built from scratch (y/n)? (default = no)\n")
- nocache = w.readDefaultYesNo(false)
- }
- if out, err := deployNode(client, w.network, w.conf.bootnodes, infos, nocache); err != nil {
- log.Error("Failed to deploy Ethereum node container", "err", err)
- if len(out) > 0 {
- fmt.Printf("%s\n", out)
- }
- return
- }
- // All ok, run a network scan to pick any changes up
- log.Info("Waiting for node to finish booting")
- time.Sleep(3 * time.Second)
-
- w.networkStats()
-}
diff --git a/go.mod b/go.mod
index a370b0dcfa..cf3d7e2e98 100644
--- a/go.mod
+++ b/go.mod
@@ -61,7 +61,6 @@ require (
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4
golang.org/x/sys v0.2.0
- golang.org/x/term v0.1.0
golang.org/x/text v0.4.0
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba
golang.org/x/tools v0.1.12
diff --git a/go.sum b/go.sum
index 0413347a14..3c7e9202f6 100644
--- a/go.sum
+++ b/go.sum
@@ -550,8 +550,6 @@ golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw=
-golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=