From f58ccfb4357b394fb3d1f4ca8f229791b09b4045 Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Wed, 26 Oct 2016 16:57:31 +0000 Subject: [PATCH 01/47] [ci skip] js-precompiled 20161026-165632 --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index fdb80f602..82edd3623 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1211,7 +1211,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#9f8baa9d0e54056c41a842b351597d0565beda98" +source = "git+https://github.com/ethcore/js-precompiled.git#51b61fdce036bc8c01923fc73206cfa9fc5ec473" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] From 0e14738147643f5dc926c6203a05e222a91997c2 Mon Sep 17 00:00:00 2001 From: "Denis S. Soldatov aka General-Beck" Date: Thu, 27 Oct 2016 02:12:05 +0700 Subject: [PATCH 02/47] Update gitlab-ci enable arm* in master branch --- .gitlab-ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 0cc444846..fcee8333b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -136,6 +136,7 @@ linux-armv7: stage: build image: ethcore/rust-armv7:latest only: + - master - beta - tags - stable @@ -171,6 +172,7 @@ linux-arm: stage: build image: ethcore/rust-arm:latest only: + - master - beta - tags - stable @@ -206,6 +208,7 @@ linux-armv6: stage: build image: ethcore/rust-armv6:latest only: + - master - beta - tags - stable @@ -234,6 +237,7 @@ linux-aarch64: stage: build image: ethcore/rust-aarch64:latest only: + - master - beta - tags - stable From f7259c26d167e782f384edc73addfc2fad51e3eb Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Wed, 26 Oct 2016 21:25:03 +0200 Subject: [PATCH 03/47] Fixes too long desc and Token Balance Value (#2891) (#2902) --- js/src/ui/Balance/balance.css | 18 ++++++++++++------ js/src/ui/Balance/balance.js | 5 ++++- js/src/ui/Container/Title/title.css | 3 +++ js/src/ui/Container/Title/title.js | 2 +- 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/js/src/ui/Balance/balance.css b/js/src/ui/Balance/balance.css index 6f9b7bb21..6fe1b2a51 100644 --- a/js/src/ui/Balance/balance.css +++ b/js/src/ui/Balance/balance.css @@ -32,19 +32,25 @@ border-radius: 16px; margin: 0.75em 0.5em 0 0; max-height: 24px; + max-width: 100%; + display: flex; + align-items: center; } .balance img { - display: inline-block; height: 32px; margin: -4px 1em 0 0; width: 32px; } -.balance div { - display: inline-block; - /*font-family: 'Roboto Mono', monospace;*/ - line-height: 24px; +.balanceValue { margin: 0 1em 0 0; - vertical-align: top; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; +} + +.balanceTag { + font-size: 0.85em; + padding-right: 0.75rem; } diff --git a/js/src/ui/Balance/balance.js b/js/src/ui/Balance/balance.js index b9df3e9fb..b6f786648 100644 --- a/js/src/ui/Balance/balance.js +++ b/js/src/ui/Balance/balance.js @@ -56,7 +56,10 @@ class Balance extends Component { { -
{ value } { token.tag }
+
+ { value } +
+
{ token.tag }
); }); diff --git a/js/src/ui/Container/Title/title.css b/js/src/ui/Container/Title/title.css index c3bf7a3b1..ee5cc58cd 100644 --- a/js/src/ui/Container/Title/title.css +++ b/js/src/ui/Container/Title/title.css @@ -16,6 +16,9 @@ */ .byline { color: #aaa; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; } .title { diff --git a/js/src/ui/Container/Title/title.js b/js/src/ui/Container/Title/title.js index 2acfc30bb..0c0d0e6b3 100644 --- a/js/src/ui/Container/Title/title.js +++ b/js/src/ui/Container/Title/title.js @@ -40,7 +40,7 @@ export default class Title extends Component { { this.props.title }
- { this.props.byline } + { this.props.byline }
); From 58b963b93d20d7528f7aaa8d31a6ec15937add2e Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Wed, 26 Oct 2016 19:44:08 +0000 Subject: [PATCH 04/47] [ci skip] js-precompiled 20161026-194241 --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 82edd3623..768096cd2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1211,7 +1211,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#51b61fdce036bc8c01923fc73206cfa9fc5ec473" +source = "git+https://github.com/ethcore/js-precompiled.git#95fdde8fabd4eb15450b49263124b8659d114167" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] From d72ea19e54b8e12619657a811105fee0cc6af697 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Wed, 26 Oct 2016 22:25:51 +0200 Subject: [PATCH 05/47] iconomi token images (#2906) --- js/assets/images/contracts/iconomi-64x64.png | Bin 0 -> 2305 bytes js/assets/images/contracts/iconomi.png | Bin 0 -> 6501 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 js/assets/images/contracts/iconomi-64x64.png create mode 100644 js/assets/images/contracts/iconomi.png diff --git a/js/assets/images/contracts/iconomi-64x64.png b/js/assets/images/contracts/iconomi-64x64.png new file mode 100644 index 0000000000000000000000000000000000000000..b3ef6b95b9d1554f94d4526b4071e46132e3aa1b GIT binary patch literal 2305 zcmV+c3I6tpP)%uqT_5JV8c$w|)doZtCf-gkfG(&S(geaON2JkR;(_xru? z_x*m}@AZ8T5Jnhbgb_v_9y#{z=#UJ8sqkR;r4ss(Ny9I_NVy^DeemwXcyn=T=CUQSppF#2bTbF0tgX`eeZpf1wJ~wSGnZ%Bm_)D zApjQ$rH`eipUr61-XBiJVcZ8U&^K%Ds487s08Zio&O|E6v3BR2e~f;8?yb!Cr~l+d zZ#i*zc%l1J9|CmOOe4{&Yl6u+A|)9Siv6-?8VBMPN5K>AQg*!c)QF?aM!?T=)^^-| z=J?7BUeA-1REy*QWJ+=IwPlT7!_WS@Mu?DY4}E&BXijOBdw|c-Y3+PB0tbgIh={W} zi@LUKyJSFzE*oCz_UP3+ngtBYlnIDEn0ddcH)=fhYh>>JPafECZ0}5u9vH9c8Ym&S znex?g2n@@@Mb(pX;Z)`FgBQOYB8Ga9>X12g@Mk538v=~kYIeMV z@{CuTI3dIm2nkRSt}_!N6`VdpM=e+6>!J*da}>MFCzeoQT1XSnbJd$ei>t1zHW;&! z>-e@!lr)*3HIP0Vu0SKhJ@?_v}%F0f= zC|My(z|c*ZJr0(gU2HKP9jrVh1ks8!FG5!SFt9_$o_YO-e|cSZ;N1TBx&_@;U3Z+2o`BLSx6QDuxZr{(MsZ;LBTwJ^1e|o2p-R_S zgVe`?SepaiT{Zi*tTm+4AR*NaOE64k;m}X#v3uSEGF)v*jN+i2^9E&axzCILmX*YX zv<0l*v#a$v&lMZvi~|JyJ9qK)NltS&Wsa71r?(I&3vOitmWlcK+q(kT9g-H1wJ&$P zs{40FDz@BcncX3yP!_SEhjM-O&v$~-FvjjNCa@iYr6f+zt_c`gqLSr^bv^ZVdP zBS3osBoQEqF_v2FzSOsgF+gYFUq#6_G^_*(N-Eu>IOjA(W59KrD$Y1-vWvBHM4gbS zc(T6!1VE*(xt`ygIpr=3f=xdKdw;;_!-62}2~GzDKz`||*S8)mNcbizI$e62d|CVz z3E5tty(_UQTCwLiB)KA(KHV}~&is7qmd{E~+)_qJfA!t}YnoI9pNP_dr?6wr{^XYfS%j3sX7g^7ZD)K3Wjp zXU*J)Gv1py@toI_Q|hglU@->9Efjk$zxLr51w+JuY5c(AyiN>f9I`0(y6UZL58!xn zk+9>1sd_eoP0gi4-Q(4JcaHS<)Y#fDY?55n zwQbVQzR3@sznvuHz4XTN^ySl8g|21#O z!?NtE5dZ}Nf-X@w=MbrYc)N4Ic!S_w7k{z}cJ=XS{)M%FZUR6|X6Z#`=idP^`*vHx zv0uzz@%Y-`Rh+53vdpdd;)7e|TE$c&00I(a)vVlLR^2gpaBCeQ$%wN#7j|utn0fsy zP|xwadZ4q#mVol3TYf2I%-{2lH_r>WO9HQL2Q;h4+lu&Sf?OAbftOypy zYR0s%JM+_BZ3aKvvCEuyo*7dr3Y5ZR?CSv>V2l|ivrGW6ONy+UWh7cJZH`@5r0U(^roovJOpZW>##V?NK;d)im%I`wD~rl~-WgXl-(b}Y5L$EPRog$+ zU5nK`WB_DAM4~gYAVzV_j}YZgw~v@oK?IRkFH(}hjzO! literal 0 HcmV?d00001 diff --git a/js/assets/images/contracts/iconomi.png b/js/assets/images/contracts/iconomi.png new file mode 100644 index 0000000000000000000000000000000000000000..cc7b69afec2f35a264da954b875db3d5381c2201 GIT binary patch literal 6501 zcmcgxXEO_kk5j9x7M~H6KMG&kWy#|SBVTs;}-lB!*qAgYl z(R;f`{`d2}&;4@GKIb{-opatZ@65b2^PAZ)byY+$Kl5hVBjc#KL=eq=4vgoMT; z6FMX#R%$yA?Uc-0)IYj-SFsg7#gB#uPg!s5oqINNXi~<(Z2K+oNq9}~uoV`Oe;Ut} zGmI2v+s;4p+40oAxcJb(`%pA^mDnvjK!w~WQ5n{b2YQK*i!tw?)Oi;}@)8V{;5^-s zQ*Y00+6XNMhIE>SQ4!qrMrlpWy{0W?NHN+pC=9u5-Iy|;K|1@vsR}vyv%`NlJBix( zqQjqw86QkRfr|=-!sKp7<}Kv^J(2@M|BZM}Lql<7fzgJvFD2!|ZlT>BK$IO4CDqtU z!*9}ur>0w66T^is#{S~?J7L7I z)p!HTj$LpIufqrh6{T3uqNI| zzPB~w7Dm2mR?#G#Pt9($nI2iN!k4bDLks`O?b zLX*sr_Tat&YiqZqFEhb;t zyA6xIkl^YEb39lodmJq?MmLM$HXY$|{CU)&lzzw9=F^3QUrZk+W;9$1DIcdFiYym} zG-=eiFBH*rt+vcKGbWcA&TBh^flUu8i?TsQW7pR{aoVFmy!D z-7KJoYo2&2jy&P`RU^mG4Tbd!Dl<0>O8K>>rcs#D7}cZ(oTRe;lF6K}CRmZJ5-$OM zND#eaHlh;9dp_=*lI~8+7h?4sFjuwx$|Li^>+krY-_>^1%%f>f4r%M9Ou}vLFqc1u zV!SP-7GB{562j7Cs97^e>Bn%t(p!>zC%N`HH>!Q?Yxx|lf&cPu8CHTT&A^~iR8PX8 zC{3KMFMn`kR`k`|5=y_PjSODZfmcMWmZW{p>dYn$3!D@lk+ndw^UcqC!~{%86LBE& zE66Ssm2`vPTQ#4TPd59E$2&!{Ywy$8OEgMd=gKm)&ABo$%jU9}@&9^yD?0L1;nbJE ztj1}>S^5Rr#vu*_lRQsdDMn8TJY?S;-fKb22pKSTcgc?pvC*p7XAFpK6qdCc6M7vk zVLalm2rCq3sYVM?IFLv8XSoM(S*HVEj|CjZl$qg?Thw8hBdy@q>B?lAEhD&MF-E$K znT_XJpKp^4Js+uhr5d7JYai7!S$gZ^+Yi#!59J1m)$`lC>;mkn8IL^)HBG#T`sRF% zMg>Lia8X~0%ZZ6QVydR<$&!u=?@z_~H$q-N_g!JPq@i{dwS>ZQ#0*HcX-+O>V!a!0 zyVyMlT)~%)2W|mMS)H9@ejo}*wq9dm;jSL73Hkoj9&2E3g*1dV1kZb}>uc3en2Wbt z$iH_A^fgT`J?>^m(YdJw%TNoHIg3m$bWGOhFuyq-Ck+PUqQ1)z2~0TSlopNJznY=Jf438Sle z+$L!_P3^f?V6QA4`Qn8FQpQ9V5&;kX=$ zNG(AlYXcV1(j!Na+tC>9SizHO_hgJ^i@My8siv{(N;a^V>&$ptu%G8;xxwQF)jv*= zwKn9MX9x0cHOq5ABGZY-?t=H>w`Yg&)2n z&It|o0ZdKS;LHt&vlBaJ_!ERgbtW88JXd*20O#E?Yo*w2e{Pip1!*-Fx?qf0pim2J48M{ zpGlBiZ<>O2%k}U>bf=)uZdVpr*}9zGPxSrHrLID6E5AP@X#jI*^!ExOlifmz>9TvC zXOPLZM+B!mgiAuiT0(9ls=GhBx6f&!>&sKTJZ4(&&!3}aCY>ID<%3ABYZNz2;YYf) zY{pbCB8dAOGC(U~V-YO&eqTp(EZIHIe3ZP*4>9SxWG8-^gLZo7(OP8H{hkM?`7R<< z+Z=noELiX3tc6jsZI(dLU>OJbFe zxHzFi-iYgqw=?gCn8;{6Amxhl#OLM7xn<)AU#DET)rTkP4Z@1v1%9sm`1J5>>=(_G zzdgrg@3Ts)>+DK&OINq(3yc$}Y1{|(3#Yz?EHeINYW$_cYPIP}5(A!}mdX||k=t!= zCe)Gvv|<>Jc80r{VY zSen{kWi_#mPlek;aAeJTv0tg~{gDyF%I_BTYzW_xj~3@yZ@OHK%y3bmb#;p*6Z2w! z!o}$WxwBYI(JoI5h7ySZcGb}G;Ip(azFQKmq0|ee!3VFmZXY!TsvkG`?Ch`DdE$fR z8#KMOtqvp?p`1O8uiJ=x4+ITkU3X+sqtc!!^+FShw-S$q6gzcb~-0_Z%? z?KXYGpY#8G5L-?rEBvShjX9iZ*6r7M(-NcFh%6Ej7*mcQ{#b!^^Q;x)HDznts!Sxz z?WBsch7hAlqksHMXpHR0z+rXd$m!D$!OHXqM1ysfZXKI=L>3Qi8X)HA&yDd5D7&+goi#9bSia_n*oW--+5pO`C5Ar3-r=WvTsB<3Q8bAJ z5O_qvPX`9*n5Pq6P66P$R`>*F#D^|LBtG~*kI|;V0M;AfF!dzv@@MjW5fx>@0y7O| zP8beCs3cF40&}zF5JgzE*t>AZKmG~i*_Vv9>XFo}_~76yN$Rb0(+A)1{|Wp2PgrRA zaFFaE^54)}ecSTck|fkSyY1iNav$Y`+VW4G?LO$YjsvS5JgDlh^&{`~e;Zt*1Te}7 z@U`6HgvQ@{l+Q6W2@pOq^|Hr}HxTtGKuATuTUh}6AeyxoS5Qhp;}UMSMsH~jaWygu ze3hUD$0Y2Fi_dy8$Hm12v9GhZ)^v8IybIjv&E22oo?-O4!P9yyT~6ne_%D~)fCwD* z$>&bR@a_0kP<+-O11H1U&9)sjsqH*rH%68 z4}fGk=`n(;h4Q~d{T0sYHGO6ndGGPyCwYz6!4CB34@z`}UWz4l0%pk>bWPxtJ9}5V zvY`=#S9xa6yjDK?-jp|ignrt&!@o5m3n?HqsPRUkyB%iAoR8eK7P&QMC$P9PF?Ncy zRtB;~QrS4ZPO8HT z&?6!fTH^#YNC#S<5PH@-@zZ7idvkB|WIK3SNC|j#>CceH+Y)oCcEIaWGI^OEa{k8g zw^M4#4yK)2yO|HzR&#tnR*=D!#%WY9TCj7wBHXMc##mf*o!}W->|uAD#Zsy3wbEBv!ns#Wt+fU01 zygDidv%bYk@I4^vi2(}r9ZWfw8J{9PIk&)WP4_5H=>HBbCuI9A?tCm8f%qZW6T-{n z%4DI`((yh~WW_ZUSfOF_F$a*3%bL%iswOVCKz}PqyBnR4-M`*M_$2NA!szN%9x2^B zQ+9+lJz%|KTU0-ld?FRbv#uqI z6+E?LFgsLle1kWUt7HHGV=|y*q*d{|<}&~;J-_#u-KT%t#zirYV2iw%yuXar1TWIWoEpd_~`ozl4WWU%V3|Lz=^~+k5@~&f2n-E7vOUrD+ zR0H_>#`;LPhY3xpfpH_NwU~>6*R}zV@$t3alF^GTZH(>818(p7>K~H>O1`_Z0XjLX zk1Dbw>$m)@uG;1a$lFNgt0Nu@k=}q*0QAcJJvi0ymZZ_v3H?On@GNtS(NcMJW@^rd z@CF?(U#@Aa*WiP9#jUn-Im69- zw%5hp!~Y^FrdOhzA$wf%@!a`34~0rbGqBxuT^Fq^oAwE=x72CGMqgLpd38%Ucq~1W z{rr~w;>*Pk(%{+a@Ce@Q@9UJ~4<;chv$=)2O$NE5ep4q=S5REg<|*d+Phm>_dtjg{c9`sYIZDxqNRoCIK3sbf z!$qk3u9SRlqgy^Fb3(MChN#Gp(IqZ<^CV zTb(V|{oeO_CvLWLPLt?vw95-|yw#HJ$~q<^eyoBozqSfZOx=onr2cvOq>eGA`>H1p zdQwiumCbvm?W{G+3?KU=I3?dUU`e95ra$n*XjN(_M?27g@6Gq48OYE>EC!S}IzhNM zkpi_6;5W|>mecbzy%T%5pZt?>{7Br7LD|mGTC7LK>SEX5kBmc=>T_sZTEdgWX>+c6AhLrv} zNVac3d!1dd#UI?r70KG@Tb_<18*6i8{FR+|yp9uHr9`PMop{-V1I1iDVj3v%8R?dM zY_$Hl;g!+lFBsoz@~#|Gc^4mMK23a&jrElOlzIPdAIv(i@y=IB__z)XOYN z)~zhh-|9f)1Mq@aY*Ygrfce@$rIO!$rRgDH2U@TWgecOcw}kOsW3X)m7k^i8@ly9# z5#x6~W>V$DU{4-`TBfl0wLvsA5?rOi9R6_mu0PF(&1IILTl??Zo|u-5f2qduo3fd& zy!^lan?WI=ABhRMT000a(0#Ik)^0ldPd z*WLr=qnVQ=Jqlq-3PgG@YsX5s5P?+PbJ$89jwo$Kss$ zp49$8HP_wGI;*c1O$M4d3zB?!InfJp{cI$u&+ClBt->1!XYzR7HgvUpf6oqNJHN9{ zi&0^tx5VM!d`<&5#D(7NF-;gpVP<5d)Utz`MTqSY{tx^wxgnxm*<=WTqbp3G(5ul!b;My{Py3pp8CnpbV?h6sY{4D zXM?ok^V`2~zAj*SMJE!M?272SeFAssrCC z&mB4Cgux`k#$FOQhF~nr%Mjz5cO~{3N2dtWicFu^jP+y7n=tE_Gr(SBnw93$e2=F9 z8UM`>QRZievszf;ZXyG)AScYqLS^^d+@@Ex1-&6@eM@_`u=iJ)&&CrQ_PvI~cR(6! z%1tG^l(0Us$9)+1Esqu`pVTG%En86M@4~zPo_();;COqbsa?p_Jc?I*UyMnC?{os1 zIrD zxC_IcCthj4n}3z0TGMeeb$wY6tJ@qWtS_{_$$mgwj@|hqoMTTW8J=3OewE8;U`WA_ z#{@HC-bqac(?)!mQj_+=O68{8AacJMe#{_Bakg2HsH!*B`a~uhckZ@cWl_8yr!!OV zRg&RM7gJwpCks^R32`wwHf#i$F0ML2im&uOnBM5Z^!{@7J{NeidDo`9edxsxp6goA z)qcPBwyYW!N!D?{MdWWYDiQktpyv6SV`&+k{vK1;7htlYe6z-Kg-F6Fmq(NfCX`Gt zThSC6X8`9ZZ+n&YNksy&r<&nZU=2cVR7`)tjL=VpBjG@}nG>lMW_YH4oZwQ0gxMZs ztPPqZYS@0di8az(D^wm39hFn2B(;oAH`i%V#lB4w^LcpUqyZe)d;d{O{w@4hGxc?8 zbALhpaQlYf3VjSOKNoJQ4U3cFbSeG3a>LdF;G<>RIV1suD5E;3TYh6eZ@tFT`C#SW?>@I(ArKWqNeIs$@VW!dK;H( z?s;lo1MjWE3$Wtg*q8bO1Vj-RJtLJOiT_J+u9XTDxD2FE?i%ERokItn2S3o`6ETVk z6-*h%>@T>W2|z#p6oyNPa#MsQi2yYQi>M?;lX<^{apM2dH2xnu%Kz7g8f=$R_X3}c VH0|qJ0)L2rl;l Date: Wed, 26 Oct 2016 20:39:01 +0000 Subject: [PATCH 06/47] [ci skip] js-precompiled 20161026-203802 --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 768096cd2..cfd995446 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1211,7 +1211,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#95fdde8fabd4eb15450b49263124b8659d114167" +source = "git+https://github.com/ethcore/js-precompiled.git#f1b734cb0df5796f2d73db6268fa0613c1526e84" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] From e668fc4c55f13c04b189a3fd6b8b17408d6209a9 Mon Sep 17 00:00:00 2001 From: "Denis S. Soldatov aka General-Beck" Date: Thu, 27 Oct 2016 09:53:51 +0700 Subject: [PATCH 07/47] Update gitlab-ci fix arm* build ENV --- .gitlab-ci.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index fcee8333b..17907b49a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -6,6 +6,8 @@ variables: SIMPLECOV: "true" RUST_BACKTRACE: "1" RUSTFLAGS: "-D warnings" + HOST_CC: "gcc" + HOST_CXX: "g++" cache: key: "$CI_BUILD_NAME/$CI_BUILD_REF_NAME" untracked: true @@ -141,6 +143,8 @@ linux-armv7: - tags - stable script: + - export CC=arm-linux-gnueabihf-gcc + - export CXX=arm-linux-gnueabihf-g++ - rm -rf .cargo - mkdir -p .cargo - echo "[target.armv7-unknown-linux-gnueabihf]" >> .cargo/config @@ -177,6 +181,8 @@ linux-arm: - tags - stable script: + - export CC=arm-linux-gnueabihf-gcc + - export CXX=arm-linux-gnueabihf-g++ - rm -rf .cargo - mkdir -p .cargo - echo "[target.arm-unknown-linux-gnueabihf]" >> .cargo/config @@ -213,6 +219,8 @@ linux-armv6: - tags - stable script: + - export CC=arm-linux-gnueabi-gcc + - export CXX=arm-linux-gnueabi-g++ - rm -rf .cargo - mkdir -p .cargo - echo "[target.arm-unknown-linux-gnueabi]" >> .cargo/config @@ -242,6 +250,8 @@ linux-aarch64: - tags - stable script: + - export CC=aarch64-linux-gnu-gcc + - export CXX=aarch64-linux-gnu-g++ - rm -rf .cargo - mkdir -p .cargo - echo "[target.aarch64-unknown-linux-gnu]" >> .cargo/config From 9bfb8094cc8ed58dc28b8c950c1802c5ff7c5c46 Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Thu, 27 Oct 2016 02:56:17 +0000 Subject: [PATCH 08/47] [ci skip] js-precompiled 20161027-025520 --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index cfd995446..ced9524ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1211,7 +1211,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#f1b734cb0df5796f2d73db6268fa0613c1526e84" +source = "git+https://github.com/ethcore/js-precompiled.git#9d77b0117c2fa0b17dd381853b0020b1fdc96855" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] From 88997801d0f5fdafbf583271be077116f99ce8d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Thu, 27 Oct 2016 08:28:12 +0200 Subject: [PATCH 09/47] Clippy bump (#2877) * Bumping clippy * Fixing warnings * Fix the "fix" --- Cargo.lock | 24 +++++++------- Cargo.toml | 2 +- dapps/Cargo.toml | 2 +- db/Cargo.toml | 2 +- ethcore/Cargo.toml | 2 +- ethcore/src/account_provider.rs | 8 ++--- ethcore/src/block.rs | 2 +- ethcore/src/blockchain/blockchain.rs | 3 +- ethcore/src/cache_manager.rs | 2 +- ethcore/src/client/client.rs | 2 +- ethcore/src/db.rs | 6 ++-- ethcore/src/evm/interpreter/mod.rs | 4 +-- ethcore/src/migrations/v10.rs | 2 +- ethcore/src/miner/miner.rs | 8 +++-- ethcore/src/miner/transaction_queue.rs | 1 + ethcore/src/service.rs | 2 +- ethcore/src/snapshot/mod.rs | 2 +- ethcore/src/state/account.rs | 2 +- ethcore/src/state/mod.rs | 46 +++++++++++--------------- ethcore/src/state_db.rs | 15 ++++----- ethcore/src/trace/db.rs | 2 +- ethcore/src/trace/executive_tracer.rs | 6 ++-- json/Cargo.toml | 2 +- rpc/Cargo.toml | 2 +- signer/Cargo.toml | 2 +- signer/src/authcode_store.rs | 1 + sync/Cargo.toml | 2 +- sync/src/blocks.rs | 6 ++-- sync/src/chain.rs | 27 +++++++-------- util/Cargo.toml | 2 +- util/network/Cargo.toml | 2 +- util/src/journaldb/archivedb.rs | 10 +++--- util/src/journaldb/earlymergedb.rs | 10 ++++-- util/src/journaldb/overlayrecentdb.rs | 2 +- util/src/journaldb/refcounteddb.rs | 4 ++- util/src/migration/mod.rs | 2 +- util/src/overlaydb.rs | 4 +-- util/src/trie/journal.rs | 2 +- util/src/trie/triedb.rs | 4 +-- 39 files changed, 117 insertions(+), 112 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ced9524ee..a58a426a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,7 +3,7 @@ name = "parity" version = "1.4.0" dependencies = [ "ansi_term 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", - "clippy 0.0.90 (registry+https://github.com/rust-lang/crates.io-index)", + "clippy 0.0.96 (registry+https://github.com/rust-lang/crates.io-index)", "ctrlc 1.1.1 (git+https://github.com/ethcore/rust-ctrlc.git)", "daemonize 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "docopt 0.6.80 (registry+https://github.com/rust-lang/crates.io-index)", @@ -145,15 +145,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "clippy" -version = "0.0.90" +version = "0.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "clippy_lints 0.0.90 (registry+https://github.com/rust-lang/crates.io-index)", + "clippy_lints 0.0.96 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "clippy_lints" -version = "0.0.90" +version = "0.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "matches 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -279,7 +279,7 @@ dependencies = [ "bit-set 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "bloomchain 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", - "clippy 0.0.90 (registry+https://github.com/rust-lang/crates.io-index)", + "clippy 0.0.96 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "ethash 1.4.0", @@ -330,7 +330,7 @@ dependencies = [ name = "ethcore-dapps" version = "1.4.0" dependencies = [ - "clippy 0.0.90 (registry+https://github.com/rust-lang/crates.io-index)", + "clippy 0.0.96 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "ethabi 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "ethcore-devtools 1.4.0", @@ -473,7 +473,7 @@ dependencies = [ name = "ethcore-rpc" version = "1.4.0" dependencies = [ - "clippy 0.0.90 (registry+https://github.com/rust-lang/crates.io-index)", + "clippy 0.0.96 (registry+https://github.com/rust-lang/crates.io-index)", "ethash 1.4.0", "ethcore 1.4.0", "ethcore-devtools 1.4.0", @@ -503,7 +503,7 @@ dependencies = [ name = "ethcore-signer" version = "1.4.0" dependencies = [ - "clippy 0.0.90 (registry+https://github.com/rust-lang/crates.io-index)", + "clippy 0.0.96 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "ethcore-devtools 1.4.0", "ethcore-io 1.4.0", @@ -542,7 +542,7 @@ version = "1.4.0" dependencies = [ "ansi_term 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", "arrayvec 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", - "clippy 0.0.90 (registry+https://github.com/rust-lang/crates.io-index)", + "clippy 0.0.96 (registry+https://github.com/rust-lang/crates.io-index)", "elastic-array 0.6.0 (git+https://github.com/ethcore/elastic-array)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "eth-secp256k1 0.5.4 (git+https://github.com/ethcore/rust-secp256k1)", @@ -631,7 +631,7 @@ dependencies = [ name = "ethsync" version = "1.4.0" dependencies = [ - "clippy 0.0.90 (registry+https://github.com/rust-lang/crates.io-index)", + "clippy 0.0.96 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "ethcore 1.4.0", "ethcore-io 1.4.0", @@ -1948,8 +1948,8 @@ dependencies = [ "checksum bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c129aff112dcc562970abb69e2508b40850dd24c274761bb50fb8a0067ba6c27" "checksum bytes 0.4.0-dev (git+https://github.com/carllerche/bytes)" = "" "checksum cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de1e760d7b6535af4241fca8bd8adf68e2e7edacc6b29f5d399050c5e48cf88c" -"checksum clippy 0.0.90 (registry+https://github.com/rust-lang/crates.io-index)" = "d19bda68c3db98e3a780342f6101b44312fef20a5f13ce756d1202a35922b01b" -"checksum clippy_lints 0.0.90 (registry+https://github.com/rust-lang/crates.io-index)" = "3d4ed67c69b9bb35169be2538691d290a3aa0cbfd4b9f0bfb7c221fc1d399a96" +"checksum clippy 0.0.96 (registry+https://github.com/rust-lang/crates.io-index)" = "6eacf01b0aad84a0817703498f72d252df7c0faf6a5b86d0be4265f1829e459f" +"checksum clippy_lints 0.0.96 (registry+https://github.com/rust-lang/crates.io-index)" = "a49960c9aab544ce86b004dcb61620e8b898fea5fc0f697a028f460f48221ed6" "checksum cookie 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "90266f45846f14a1e986c77d1e9c2626b8c342ed806fe60241ec38cc8697b245" "checksum crossbeam 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)" = "fb974f835e90390c5f9dfac00f05b06dc117299f5ea4e85fbc7bb443af4911cc" "checksum ctrlc 1.1.1 (git+https://github.com/ethcore/rust-ctrlc.git)" = "" diff --git a/Cargo.toml b/Cargo.toml index 62039696c..dc802a0fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,7 +46,7 @@ ethcore-logger = { path = "logger" } rlp = { path = "util/rlp" } ethcore-stratum = { path = "stratum" } ethcore-dapps = { path = "dapps", optional = true } -clippy = { version = "0.0.90", optional = true} +clippy = { version = "0.0.96", optional = true} [target.'cfg(windows)'.dependencies] winapi = "0.2" diff --git a/dapps/Cargo.toml b/dapps/Cargo.toml index 9c49c7e28..ddc23c87c 100644 --- a/dapps/Cargo.toml +++ b/dapps/Cargo.toml @@ -33,7 +33,7 @@ fetch = { path = "../util/fetch" } parity-ui = { path = "./ui" } mime_guess = { version = "1.6.1" } -clippy = { version = "0.0.90", optional = true} +clippy = { version = "0.0.96", optional = true} [build-dependencies] serde_codegen = { version = "0.8", optional = true } diff --git a/db/Cargo.toml b/db/Cargo.toml index 15ceb9b3b..27eadef4a 100644 --- a/db/Cargo.toml +++ b/db/Cargo.toml @@ -11,7 +11,7 @@ build = "build.rs" ethcore-ipc-codegen = { path = "../ipc/codegen" } [dependencies] -clippy = { version = "0.0.90", optional = true} +clippy = { version = "0.0.96", optional = true} ethcore-devtools = { path = "../devtools" } ethcore-ipc = { path = "../ipc/rpc" } rocksdb = { git = "https://github.com/ethcore/rust-rocksdb" } diff --git a/ethcore/Cargo.toml b/ethcore/Cargo.toml index ad9e545ce..a60eccddd 100644 --- a/ethcore/Cargo.toml +++ b/ethcore/Cargo.toml @@ -25,7 +25,7 @@ semver = "0.2" bit-set = "0.4" time = "0.1" evmjit = { path = "../evmjit", optional = true } -clippy = { version = "0.0.90", optional = true} +clippy = { version = "0.0.96", optional = true} ethash = { path = "../ethash" } ethcore-util = { path = "../util" } ethcore-io = { path = "../util/io" } diff --git a/ethcore/src/account_provider.rs b/ethcore/src/account_provider.rs index 7a4958ddc..fed1a05fc 100644 --- a/ethcore/src/account_provider.rs +++ b/ethcore/src/account_provider.rs @@ -267,17 +267,17 @@ impl AccountProvider { /// Returns `true` if the password for `account` is `password`. `false` if not. pub fn test_password(&self, account: &Address, password: String) -> Result { - match self.sstore.sign(&account, &password, &Default::default()) { + match self.sstore.sign(account, &password, &Default::default()) { Ok(_) => Ok(true), Err(SSError::InvalidPassword) => Ok(false), Err(e) => Err(Error::SStore(e)), } - } + } /// Changes the password of `account` from `password` to `new_password`. Fails if incorrect `password` given. pub fn change_password(&self, account: &Address, password: String, new_password: String) -> Result<(), Error> { - self.sstore.change_password(&account, &password, &new_password).map_err(Error::SStore) - } + self.sstore.change_password(account, &password, &new_password).map_err(Error::SStore) + } /// Helper method used for unlocking accounts. fn unlock_account(&self, account: Address, password: String, unlock: Unlock) -> Result<(), Error> { diff --git a/ethcore/src/block.rs b/ethcore/src/block.rs index 5d7305b91..54c2a7a02 100644 --- a/ethcore/src/block.rs +++ b/ethcore/src/block.rs @@ -542,7 +542,7 @@ pub fn enact( Ok(b.close_and_lock()) } -#[inline(always)] +#[inline] #[cfg(not(feature = "slow-blocks"))] fn push_transactions(block: &mut OpenBlock, transactions: &[SignedTransaction]) -> Result<(), Error> { for t in transactions { diff --git a/ethcore/src/blockchain/blockchain.rs b/ethcore/src/blockchain/blockchain.rs index de0c72a38..32d42fe31 100644 --- a/ethcore/src/blockchain/blockchain.rs +++ b/ethcore/src/blockchain/blockchain.rs @@ -414,6 +414,7 @@ impl<'a> Iterator for AncestryIter<'a> { } impl BlockChain { + #[cfg_attr(feature="dev", allow(useless_let_if_seq))] /// Create new instance of blockchain from given Genesis pub fn new(config: Config, genesis: &[u8], db: Arc) -> BlockChain { // 400 is the avarage size of the key @@ -565,7 +566,7 @@ impl BlockChain { let range = extras.number as bc::Number .. extras.number as bc::Number; let chain = bc::group::BloomGroupChain::new(self.blooms_config, self); let changes = chain.replace(&range, vec![]); - for (k, v) in changes.into_iter() { + for (k, v) in changes { batch.write(db::COL_EXTRA, &LogGroupPosition::from(k), &BloomGroup::from(v)); } batch.put(db::COL_EXTRA, b"best", &hash); diff --git a/ethcore/src/cache_manager.rs b/ethcore/src/cache_manager.rs index 02c8f08a1..6ad01b453 100644 --- a/ethcore/src/cache_manager.rs +++ b/ethcore/src/cache_manager.rs @@ -66,7 +66,7 @@ impl CacheManager where T: Eq + Hash { } fn rotate_cache_if_needed(&mut self) { - if self.cache_usage.len() == 0 { return } + if self.cache_usage.is_empty() { return } if self.cache_usage[0].len() * self.bytes_per_cache_entry > self.pref_cache_size / COLLECTION_QUEUE_SIZE { if let Some(cache) = self.cache_usage.pop_back() { diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 49195d952..cb963dadc 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -314,7 +314,7 @@ impl Client { if let Some(parent) = chain_has_parent { // Enact Verified Block let last_hashes = self.build_last_hashes(header.parent_hash().clone()); - let db = self.state_db.lock().boxed_clone_canon(&header.parent_hash()); + let db = self.state_db.lock().boxed_clone_canon(header.parent_hash()); let enact_result = enact_verified(block, engine, self.tracedb.read().tracing_enabled(), db, &parent, last_hashes, self.factories.clone()); let locked_block = try!(enact_result.map_err(|e| { diff --git a/ethcore/src/db.rs b/ethcore/src/db.rs index 10672d730..92c0f1b39 100644 --- a/ethcore/src/db.rs +++ b/ethcore/src/db.rs @@ -114,7 +114,7 @@ pub trait Writable { R: Deref { match policy { CacheUpdatePolicy::Overwrite => { - for (key, value) in values.into_iter() { + for (key, value) in values { self.write(col, &key, &value); cache.insert(key, value); } @@ -135,7 +135,7 @@ pub trait Writable { R: Deref { match policy { CacheUpdatePolicy::Overwrite => { - for (key, value) in values.into_iter() { + for (key, value) in values { match value { Some(ref v) => self.write(col, &key, v), None => self.delete(col, &key), @@ -144,7 +144,7 @@ pub trait Writable { } }, CacheUpdatePolicy::Remove => { - for (key, value) in values.into_iter() { + for (key, value) in values { match value { Some(v) => self.write(col, &key, &v), None => self.delete(col, &key), diff --git a/ethcore/src/evm/interpreter/mod.rs b/ethcore/src/evm/interpreter/mod.rs index a39e09e79..8ded2e1f1 100644 --- a/ethcore/src/evm/interpreter/mod.rs +++ b/ethcore/src/evm/interpreter/mod.rs @@ -54,14 +54,14 @@ const TWO_POW_248: U256 = U256([0, 0, 0, 0x100000000000000]); //0x1 00000000 000 /// Abstraction over raw vector of Bytes. Easier state management of PC. struct CodeReader<'a> { position: ProgramCounter, - code: &'a Bytes + code: &'a [u8] } #[cfg_attr(feature="dev", allow(len_without_is_empty))] impl<'a> CodeReader<'a> { /// Create new code reader - starting at position 0. - fn new(code: &'a Bytes) -> Self { + fn new(code: &'a [u8]) -> Self { CodeReader { position: 0, code: code, diff --git a/ethcore/src/migrations/v10.rs b/ethcore/src/migrations/v10.rs index 88884fb26..77531eb08 100644 --- a/ethcore/src/migrations/v10.rs +++ b/ethcore/src/migrations/v10.rs @@ -61,7 +61,7 @@ pub fn generate_bloom(source: Arc, dest: &mut Database) -> Result<(), let account_trie = try!(TrieDB::new(state_db.as_hashdb(), &state_root).map_err(|e| Error::Custom(format!("Cannot open trie: {:?}", e)))); for item in try!(account_trie.iter().map_err(|_| Error::MigrationImpossible)) { let (ref account_key, _) = try!(item.map_err(|_| Error::MigrationImpossible)); - let account_key_hash = H256::from_slice(&account_key); + let account_key_hash = H256::from_slice(account_key); bloom.set(&*account_key_hash); } diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 43b6ca721..270a39e48 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -362,7 +362,7 @@ impl Miner { { let mut queue = self.transaction_queue.lock(); - for hash in invalid_transactions.into_iter() { + for hash in invalid_transactions { queue.remove_invalid(&hash, &fetch_account); } for hash in transactions_to_penalize { @@ -522,6 +522,8 @@ impl Miner { /// Are we allowed to do a non-mandatory reseal? fn tx_reseal_allowed(&self) -> bool { Instant::now() > *self.next_allowed_reseal.lock() } + #[cfg_attr(feature="dev", allow(wrong_self_convention))] + #[cfg_attr(feature="dev", allow(redundant_closure))] fn from_pending_block(&self, latest_block_number: BlockNumber, from_chain: F, map_block: G) -> H where F: Fn() -> H, G: Fn(&ClosedBlock) -> H { let sealing_work = self.sealing_work.lock(); @@ -885,7 +887,7 @@ impl MinerService for Miner { fn pending_receipts(&self, best_block: BlockNumber) -> BTreeMap { self.from_pending_block( best_block, - || BTreeMap::new(), + BTreeMap::new, |pending| { let hashes = pending.transactions() .iter() @@ -1019,7 +1021,7 @@ impl MinerService for Miner { tx.sender().expect("Transaction is in block, so sender has to be defined.") }) .collect::>(); - for sender in to_remove.into_iter() { + for sender in to_remove { transaction_queue.remove_all(sender, chain.latest_nonce(&sender)); } }); diff --git a/ethcore/src/miner/transaction_queue.rs b/ethcore/src/miner/transaction_queue.rs index fa0cce1e6..40150b78d 100644 --- a/ethcore/src/miner/transaction_queue.rs +++ b/ethcore/src/miner/transaction_queue.rs @@ -446,6 +446,7 @@ pub struct AccountDetails { const GAS_LIMIT_HYSTERESIS: usize = 10; // (100/GAS_LIMIT_HYSTERESIS) % /// Describes the strategy used to prioritize transactions in the queue. +#[cfg_attr(feature="dev", allow(enum_variant_names))] #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum PrioritizationStrategy { /// Use only gas price. Disregards the actual computation cost of the transaction. diff --git a/ethcore/src/service.rs b/ethcore/src/service.rs index 95cbe745e..1180c1b3c 100644 --- a/ethcore/src/service.rs +++ b/ethcore/src/service.rs @@ -87,7 +87,7 @@ impl ClientService { db_config.set_cache(::db::COL_STATE, size); } - db_config.compaction = config.db_compaction.compaction_profile(&client_path); + db_config.compaction = config.db_compaction.compaction_profile(client_path); db_config.wal = config.db_wal; let pruning = config.pruning; diff --git a/ethcore/src/snapshot/mod.rs b/ethcore/src/snapshot/mod.rs index 4fa00f771..86f921cf0 100644 --- a/ethcore/src/snapshot/mod.rs +++ b/ethcore/src/snapshot/mod.rs @@ -202,7 +202,7 @@ impl<'a> BlockChunker<'a> { // cut off the chunk if too large. - if new_loaded_size > PREFERRED_CHUNK_SIZE && self.rlps.len() > 0 { + if new_loaded_size > PREFERRED_CHUNK_SIZE && !self.rlps.is_empty() { try!(self.write_chunk(last)); loaded_size = pair.len(); } else { diff --git a/ethcore/src/state/account.rs b/ethcore/src/state/account.rs index b26c79cba..2bd8a2d15 100644 --- a/ethcore/src/state/account.rs +++ b/ethcore/src/state/account.rs @@ -413,7 +413,7 @@ impl Account { self.code_size = other.code_size; self.address_hash = other.address_hash; let mut cache = self.storage_cache.borrow_mut(); - for (k, v) in other.storage_cache.into_inner().into_iter() { + for (k, v) in other.storage_cache.into_inner() { cache.insert(k.clone() , v.clone()); //TODO: cloning should not be required here } self.storage_changes = other.storage_changes; diff --git a/ethcore/src/state/mod.rs b/ethcore/src/state/mod.rs index 6befcad12..bef20d257 100644 --- a/ethcore/src/state/mod.rs +++ b/ethcore/src/state/mod.rs @@ -127,11 +127,10 @@ impl AccountEntry { fn overwrite_with(&mut self, other: AccountEntry) { self.state = other.state; match other.account { - Some(acc) => match self.account { - Some(ref mut ours) => { + Some(acc) => { + if let Some(ref mut ours) = self.account { ours.overwrite_with(acc); - }, - None => {}, + } }, None => self.account = None, } @@ -281,13 +280,10 @@ impl State { } }, None => { - match self.cache.get_mut().entry(k) { - Entry::Occupied(e) => { - if e.get().is_dirty() { - e.remove(); - } - }, - _ => {} + if let Entry::Occupied(e) = self.cache.get_mut().entry(k) { + if e.get().is_dirty() { + e.remove(); + } } } } @@ -501,6 +497,7 @@ impl State { /// Commit accounts to SecTrieDBMut. This is similar to cpp-ethereum's dev::eth::commit. /// `accounts` is mutable because we may need to commit the code or storage and record that. #[cfg_attr(feature="dev", allow(match_ref_pats))] + #[cfg_attr(feature="dev", allow(needless_borrow))] fn commit_into( factories: &Factories, db: &mut StateDB, @@ -509,17 +506,14 @@ impl State { ) -> Result<(), Error> { // first, commit the sub trees. for (address, ref mut a) in accounts.iter_mut().filter(|&(_, ref a)| a.is_dirty()) { - match a.account { - Some(ref mut account) => { - if !account.is_empty() { - db.note_account_bloom(&address); - } - let addr_hash = account.address_hash(address); - let mut account_db = factories.accountdb.create(db.as_hashdb_mut(), addr_hash); - account.commit_storage(&factories.trie, account_db.as_hashdb_mut()); - account.commit_code(account_db.as_hashdb_mut()); + if let Some(ref mut account) = a.account { + if !account.is_empty() { + db.note_account_bloom(address); } - _ => {} + let addr_hash = account.address_hash(address); + let mut account_db = factories.accountdb.create(db.as_hashdb_mut(), addr_hash); + account.commit_storage(&factories.trie, account_db.as_hashdb_mut()); + account.commit_code(account_db.as_hashdb_mut()); } } @@ -586,7 +580,7 @@ impl State { fn query_pod(&mut self, query: &PodState) { for (address, pod_account) in query.get().into_iter() - .filter(|&(ref a, _)| self.ensure_cached(a, RequireCache::Code, true, |a| a.is_some())) + .filter(|&(a, _)| self.ensure_cached(a, RequireCache::Code, true, |a| a.is_some())) { // needs to be split into two parts for the refcell code here // to work. @@ -679,14 +673,12 @@ impl State { None => { let maybe_acc = if self.db.check_account_bloom(a) { let db = self.factories.trie.readonly(self.db.as_hashdb(), &self.root).expect(SEC_TRIE_DB_UNWRAP_STR); - let maybe_acc = match db.get(a) { + match db.get(a) { Ok(Some(acc)) => AccountEntry::new_clean(Some(Account::from_rlp(&acc))), Ok(None) => AccountEntry::new_clean(None), Err(e) => panic!("Potential DB corruption encountered: {}", e), - }; - maybe_acc - } - else { + } + } else { AccountEntry::new_clean(None) }; self.insert_cache(a, maybe_acc); diff --git a/ethcore/src/state_db.rs b/ethcore/src/state_db.rs index 2823b9b1b..dfa65ab1d 100644 --- a/ethcore/src/state_db.rs +++ b/ethcore/src/state_db.rs @@ -170,7 +170,7 @@ impl StateDB { pub fn commit_bloom(batch: &mut DBTransaction, journal: BloomJournal) -> Result<(), UtilError> { assert!(journal.hash_functions <= 255); - batch.put(COL_ACCOUNT_BLOOM, ACCOUNT_BLOOM_HASHCOUNT_KEY, &vec![journal.hash_functions as u8]); + batch.put(COL_ACCOUNT_BLOOM, ACCOUNT_BLOOM_HASHCOUNT_KEY, &[journal.hash_functions as u8]); let mut key = [0u8; 8]; let mut val = [0u8; 8]; @@ -216,7 +216,7 @@ impl StateDB { let mut clear = false; for block in enacted.iter().filter(|h| self.commit_hash.as_ref().map_or(true, |p| *h != p)) { clear = clear || { - if let Some(ref mut m) = cache.modifications.iter_mut().find(|ref m| &m.hash == block) { + if let Some(ref mut m) = cache.modifications.iter_mut().find(|m| &m.hash == block) { trace!("Reverting enacted block {:?}", block); m.is_canon = true; for a in &m.accounts { @@ -232,7 +232,7 @@ impl StateDB { for block in retracted { clear = clear || { - if let Some(ref mut m) = cache.modifications.iter_mut().find(|ref m| &m.hash == block) { + if let Some(ref mut m) = cache.modifications.iter_mut().find(|m| &m.hash == block) { trace!("Retracting block {:?}", block); m.is_canon = false; for a in &m.accounts { @@ -286,7 +286,7 @@ impl StateDB { is_canon: is_best, parent: parent.clone(), }; - let insert_at = cache.modifications.iter().enumerate().find(|&(_, ref m)| m.number < *number).map(|(i, _)| i); + let insert_at = cache.modifications.iter().enumerate().find(|&(_, m)| m.number < *number).map(|(i, _)| i); trace!("inserting modifications at {:?}", insert_at); if let Some(insert_at) = insert_at { cache.modifications.insert(insert_at, block_changes); @@ -369,7 +369,7 @@ impl StateDB { if !Self::is_allowed(addr, &self.parent_hash, &cache.modifications) { return None; } - cache.accounts.get_mut(&addr).map(|a| a.as_ref().map(|a| a.clone_basic())) + cache.accounts.get_mut(addr).map(|a| a.as_ref().map(|a| a.clone_basic())) } /// Get value from a cached account. @@ -406,8 +406,7 @@ impl StateDB { // We search for our parent in that list first and then for // all its parent until we hit the canonical block, // checking against all the intermediate modifications. - let mut iter = modifications.iter(); - while let Some(ref m) = iter.next() { + for m in modifications { if &m.hash == parent { if m.is_canon { return true; @@ -420,7 +419,7 @@ impl StateDB { } } trace!("Cache lookup skipped for {:?}: parent hash is unknown", addr); - return false; + false } } diff --git a/ethcore/src/trace/db.rs b/ethcore/src/trace/db.rs index bc867983d..6a1b55a1b 100644 --- a/ethcore/src/trace/db.rs +++ b/ethcore/src/trace/db.rs @@ -285,7 +285,7 @@ impl TraceDatabase for TraceDB where T: DatabaseExtras { let mut blooms = self.blooms.write(); batch.extend_with_cache(db::COL_TRACE, &mut *blooms, blooms_to_insert, CacheUpdatePolicy::Remove); // note_used must be called after locking blooms to avoid cache/traces deadlock on garbage collection - for key in blooms_keys.into_iter() { + for key in blooms_keys { self.note_used(CacheID::Bloom(key)); } } diff --git a/ethcore/src/trace/executive_tracer.rs b/ethcore/src/trace/executive_tracer.rs index ca9bc30b5..bb18c61a8 100644 --- a/ethcore/src/trace/executive_tracer.rs +++ b/ethcore/src/trace/executive_tracer.rs @@ -50,12 +50,12 @@ fn prefix_subtrace_addresses(mut traces: Vec) -> Vec { // [1, 0] let mut current_subtrace_index = 0; let mut first = true; - for trace in traces.iter_mut() { + for trace in &mut traces { match (first, trace.trace_address.is_empty()) { (true, _) => first = false, (_, true) => current_subtrace_index += 1, _ => {} - } + } trace.trace_address.push_front(current_subtrace_index); } traces @@ -78,7 +78,7 @@ fn should_prefix_address_properly() { let t = vec![vec![], vec![0], vec![0, 0], vec![0], vec![], vec![], vec![0], vec![]].into_iter().map(&f).collect(); let t = prefix_subtrace_addresses(t); assert_eq!(t, vec![vec![0], vec![0, 0], vec![0, 0, 0], vec![0, 0], vec![1], vec![2], vec![2, 0], vec![3]].into_iter().map(&f).collect::>()); -} +} impl Tracer for ExecutiveTracer { fn prepare_trace_call(&self, params: &ActionParams) -> Option { diff --git a/json/Cargo.toml b/json/Cargo.toml index 90c36cedc..8f7b0c227 100644 --- a/json/Cargo.toml +++ b/json/Cargo.toml @@ -10,7 +10,7 @@ rustc-serialize = "0.3" serde = "0.8" serde_json = "0.8" serde_macros = { version = "0.8", optional = true } -clippy = { version = "0.0.90", optional = true} +clippy = { version = "0.0.96", optional = true} [build-dependencies] serde_codegen = { version = "0.8", optional = true } diff --git a/rpc/Cargo.toml b/rpc/Cargo.toml index 34b68fb81..9ce638ea6 100644 --- a/rpc/Cargo.toml +++ b/rpc/Cargo.toml @@ -29,7 +29,7 @@ fetch = { path = "../util/fetch" } rustc-serialize = "0.3" transient-hashmap = "0.1" serde_macros = { version = "0.8.0", optional = true } -clippy = { version = "0.0.90", optional = true} +clippy = { version = "0.0.96", optional = true} json-ipc-server = { git = "https://github.com/ethcore/json-ipc-server.git" } ethcore-ipc = { path = "../ipc/rpc" } time = "0.1" diff --git a/signer/Cargo.toml b/signer/Cargo.toml index b8a7c5ce4..651d96cb3 100644 --- a/signer/Cargo.toml +++ b/signer/Cargo.toml @@ -23,7 +23,7 @@ ethcore-rpc = { path = "../rpc" } ethcore-devtools = { path = "../devtools" } parity-ui = { path = "../dapps/ui", version = "1.4", optional = true } -clippy = { version = "0.0.90", optional = true} +clippy = { version = "0.0.96", optional = true} [features] dev = ["clippy"] diff --git a/signer/src/authcode_store.rs b/signer/src/authcode_store.rs index d8068fc88..d8474441c 100644 --- a/signer/src/authcode_store.rs +++ b/signer/src/authcode_store.rs @@ -99,6 +99,7 @@ impl AuthCodes { } /// Checks if given hash is correct identifier of `SignerUI` + #[cfg_attr(feature="dev", allow(wrong_self_convention))] pub fn is_valid(&mut self, hash: &H256, time: u64) -> bool { let now = self.now.now(); // check time diff --git a/sync/Cargo.toml b/sync/Cargo.toml index d27929186..95d738eb4 100644 --- a/sync/Cargo.toml +++ b/sync/Cargo.toml @@ -17,7 +17,7 @@ ethcore-network = { path = "../util/network" } ethcore-io = { path = "../util/io" } ethcore = { path = "../ethcore" } rlp = { path = "../util/rlp" } -clippy = { version = "0.0.90", optional = true} +clippy = { version = "0.0.96", optional = true} log = "0.3" env_logger = "0.3" time = "0.1.34" diff --git a/sync/src/blocks.rs b/sync/src/blocks.rs index db385b9d5..ed608d9c1 100644 --- a/sync/src/blocks.rs +++ b/sync/src/blocks.rs @@ -114,7 +114,7 @@ impl BlockCollection { /// Insert a set of headers into collection and advance subchain head pointers. pub fn insert_headers(&mut self, headers: Vec) { - for h in headers.into_iter() { + for h in headers { if let Err(e) = self.insert_header(h) { trace!(target: "sync", "Ignored invalid header: {:?}", e); } @@ -125,7 +125,7 @@ impl BlockCollection { /// Insert a collection of block bodies for previously downloaded headers. pub fn insert_bodies(&mut self, bodies: Vec) -> usize { let mut inserted = 0; - for b in bodies.into_iter() { + for b in bodies { if let Err(e) = self.insert_body(b) { trace!(target: "sync", "Ignored invalid body: {:?}", e); } else { @@ -141,7 +141,7 @@ impl BlockCollection { return 0; } let mut inserted = 0; - for r in receipts.into_iter() { + for r in receipts { if let Err(e) = self.insert_receipt(r) { trace!(target: "sync", "Ignored invalid receipt: {:?}", e); } else { diff --git a/sync/src/chain.rs b/sync/src/chain.rs index 916e7424e..764eccdac 100644 --- a/sync/src/chain.rs +++ b/sync/src/chain.rs @@ -209,8 +209,8 @@ pub struct SyncStatus { impl SyncStatus { /// Indicates if snapshot download is in progress pub fn is_snapshot_syncing(&self) -> bool { - self.state == SyncState::SnapshotManifest - || self.state == SyncState::SnapshotData + self.state == SyncState::SnapshotManifest + || self.state == SyncState::SnapshotData || self.state == SyncState::SnapshotWaiting } @@ -381,7 +381,7 @@ impl ChainSync { /// Returns information on peers connections pub fn peers(&self, io: &SyncIo) -> Vec { self.peers.iter() - .filter_map(|(&peer_id, ref peer_data)| + .filter_map(|(&peer_id, peer_data)| io.peer_session_info(peer_id).map(|session_info| PeerInfoDigest { id: session_info.id.map(|id| id.hex()), @@ -453,7 +453,7 @@ impl ChainSync { self.init_downloaders(io.chain()); self.reset_and_continue(io); } - + /// Restart sync after bad block has been detected. May end up re-downloading up to QUEUE_SIZE blocks fn init_downloaders(&mut self, chain: &BlockChainClient) { // Do not assume that the block queue/chain still has our last_imported_block @@ -1017,7 +1017,7 @@ impl ChainSync { return; } let (peer_latest, peer_difficulty, peer_snapshot_number, peer_snapshot_hash) = { - if let Some(ref peer) = self.peers.get_mut(&peer_id) { + if let Some(peer) = self.peers.get_mut(&peer_id) { if peer.asking != PeerAsking::Nothing || !peer.can_sync() { return; } @@ -1142,6 +1142,7 @@ impl ChainSync { } /// Checks if there are blocks fully downloaded that can be imported into the blockchain and does the import. + #[cfg_attr(feature="dev", allow(block_in_if_condition_stmt))] fn collect_blocks(&mut self, io: &mut SyncIo, block_set: BlockSet) { match block_set { BlockSet::NewBlocks => { @@ -1150,9 +1151,9 @@ impl ChainSync { } }, BlockSet::OldBlocks => { - if self.old_blocks.as_mut().map_or(false, |downloader| { downloader.collect_blocks(io, false) == Err(DownloaderImportError::Invalid) }) { - self.restart(io); - } else if self.old_blocks.as_ref().map_or(false, |downloader| { downloader.is_complete() }) { + if self.old_blocks.as_mut().map_or(false, |downloader| { downloader.collect_blocks(io, false) == Err(DownloaderImportError::Invalid) }) { + self.restart(io); + } else if self.old_blocks.as_ref().map_or(false, |downloader| { downloader.is_complete() }) { trace!(target: "sync", "Background block download is complete"); self.old_blocks = None; } @@ -1242,7 +1243,7 @@ impl ChainSync { return true; } } - return false; + false } /// Generic request sender @@ -1370,7 +1371,7 @@ impl ChainSync { while number <= last && count < max_count { if let Some(hdr) = overlay.get(&number) { trace!(target: "sync", "{}: Returning cached fork header", peer_id); - data.extend(hdr); + data.extend_from_slice(hdr); count += 1; } else if let Some(mut hdr) = io.chain().block_header(BlockID::Number(number)) { data.append(&mut hdr); @@ -1707,7 +1708,7 @@ impl ChainSync { self.send_packet(io, *peer_id, NEW_BLOCK_PACKET, rlp); } } - if let Some(ref mut peer) = self.peers.get_mut(&peer_id) { + if let Some(ref mut peer) = self.peers.get_mut(peer_id) { peer.latest_hash = chain_info.best_block_hash.clone(); } sent += 1; @@ -1725,7 +1726,7 @@ impl ChainSync { sent += match ChainSync::create_new_hashes_rlp(io.chain(), &last_parent, &chain_info.best_block_hash) { Some(rlp) => { { - if let Some(ref mut peer) = self.peers.get_mut(&peer_id) { + if let Some(ref mut peer) = self.peers.get_mut(peer_id) { peer.latest_hash = chain_info.best_block_hash.clone(); } } @@ -1793,7 +1794,7 @@ impl ChainSync { // Send RLPs let sent = lucky_peers.len(); if sent > 0 { - for (peer_id, rlp) in lucky_peers.into_iter() { + for (peer_id, rlp) in lucky_peers { self.send_packet(io, peer_id, TRANSACTIONS_PACKET, rlp); } diff --git a/util/Cargo.toml b/util/Cargo.toml index c560a6bb5..1b6939595 100644 --- a/util/Cargo.toml +++ b/util/Cargo.toml @@ -23,7 +23,7 @@ rlp = { path = "rlp" } heapsize = { version = "0.3", features = ["unstable"] } itertools = "0.4" sha3 = { path = "sha3" } -clippy = { version = "0.0.90", optional = true} +clippy = { version = "0.0.96", optional = true} ethcore-devtools = { path = "../devtools" } libc = "0.2.7" vergen = "0.1" diff --git a/util/network/Cargo.toml b/util/network/Cargo.toml index b5292db60..8b566fcf2 100644 --- a/util/network/Cargo.toml +++ b/util/network/Cargo.toml @@ -14,7 +14,7 @@ time = "0.1.34" tiny-keccak = "1.0" rust-crypto = "0.2.34" slab = "0.2" -clippy = { version = "0.0.90", optional = true} +clippy = { version = "0.0.96", optional = true} igd = "0.5.0" libc = "0.2.7" parking_lot = "0.3" diff --git a/util/src/journaldb/archivedb.rs b/util/src/journaldb/archivedb.rs index 940f92375..a8800045b 100644 --- a/util/src/journaldb/archivedb.rs +++ b/util/src/journaldb/archivedb.rs @@ -78,7 +78,7 @@ impl HashDB for ArchiveDB { ret.insert(h, 1); } - for (key, refs) in self.overlay.keys().into_iter() { + for (key, refs) in self.overlay.keys() { let refs = *ret.get(&key).unwrap_or(&0) + refs; ret.insert(key, refs); } @@ -152,7 +152,7 @@ impl JournalDB for ArchiveDB { let mut inserts = 0usize; let mut deletes = 0usize; - for i in self.overlay.drain().into_iter() { + for i in self.overlay.drain() { let (key, (value, rc)) = i; if rc > 0 { batch.put(self.column, &key, &value); @@ -164,7 +164,7 @@ impl JournalDB for ArchiveDB { } } - for (mut key, value) in self.overlay.drain_aux().into_iter() { + for (mut key, value) in self.overlay.drain_aux() { key.push(AUX_FLAG); batch.put(self.column, &key, &value); } @@ -185,7 +185,7 @@ impl JournalDB for ArchiveDB { let mut inserts = 0usize; let mut deletes = 0usize; - for i in self.overlay.drain().into_iter() { + for i in self.overlay.drain() { let (key, (value, rc)) = i; if rc > 0 { if try!(self.backing.get(self.column, &key)).is_some() { @@ -204,7 +204,7 @@ impl JournalDB for ArchiveDB { } } - for (mut key, value) in self.overlay.drain_aux().into_iter() { + for (mut key, value) in self.overlay.drain_aux() { key.push(AUX_FLAG); batch.put(self.column, &key, &value); } diff --git a/util/src/journaldb/earlymergedb.rs b/util/src/journaldb/earlymergedb.rs index 1e782c580..d17c0ef1e 100644 --- a/util/src/journaldb/earlymergedb.rs +++ b/util/src/journaldb/earlymergedb.rs @@ -63,9 +63,11 @@ enum RemoveFrom { /// the removals actually take effect. /// /// journal format: +/// ``` /// [era, 0] => [ id, [insert_0, ...], [remove_0, ...] ] /// [era, 1] => [ id, [insert_0, ...], [remove_0, ...] ] /// [era, n] => [ ... ] +/// ``` /// /// When we make a new commit, we make a journal of all blocks in the recent history and record /// all keys that were inserted and deleted. The journal is ordered by era; multiple commits can @@ -80,6 +82,7 @@ enum RemoveFrom { /// which includes an original key, if any. /// /// The semantics of the `counter` are: +/// ``` /// insert key k: /// counter already contains k: count += 1 /// counter doesn't contain k: @@ -91,9 +94,11 @@ enum RemoveFrom { /// count == 1: remove counter /// count == 0: remove key from backing db /// counter doesn't contain k: remove key from backing db +/// ``` /// /// Practically, this means that for each commit block turning from recent to ancient we do the /// following: +/// ``` /// is_canonical: /// inserts: Ignored (left alone in the backing database). /// deletes: Enacted; however, recent history queue is checked for ongoing references. This is @@ -102,8 +107,9 @@ enum RemoveFrom { /// inserts: Reverted; however, recent history queue is checked for ongoing references. This is /// reduced as a preference to deletion from the backing database. /// deletes: Ignored (they were never inserted). +/// ``` /// -/// TODO: store_reclaim_period +/// TODO: `store_reclaim_period` pub struct EarlyMergeDB { overlay: MemoryDB, backing: Arc, @@ -310,7 +316,7 @@ impl HashDB for EarlyMergeDB { ret.insert(h, 1); } - for (key, refs) in self.overlay.keys().into_iter() { + for (key, refs) in self.overlay.keys() { let refs = *ret.get(&key).unwrap_or(&0) + refs; ret.insert(key, refs); } diff --git a/util/src/journaldb/overlayrecentdb.rs b/util/src/journaldb/overlayrecentdb.rs index 83868d06b..42fe84557 100644 --- a/util/src/journaldb/overlayrecentdb.rs +++ b/util/src/journaldb/overlayrecentdb.rs @@ -379,7 +379,7 @@ impl HashDB for OverlayRecentDB { ret.insert(h, 1); } - for (key, refs) in self.transaction_overlay.keys().into_iter() { + for (key, refs) in self.transaction_overlay.keys() { let refs = *ret.get(&key).unwrap_or(&0) + refs; ret.insert(key, refs); } diff --git a/util/src/journaldb/refcounteddb.rs b/util/src/journaldb/refcounteddb.rs index 57621f321..d63f8837d 100644 --- a/util/src/journaldb/refcounteddb.rs +++ b/util/src/journaldb/refcounteddb.rs @@ -36,12 +36,14 @@ use std::env; /// the removals actually take effect. /// /// journal format: +/// ``` /// [era, 0] => [ id, [insert_0, ...], [remove_0, ...] ] /// [era, 1] => [ id, [insert_0, ...], [remove_0, ...] ] /// [era, n] => [ ... ] +/// ``` /// /// when we make a new commit, we journal the inserts and removes. -/// for each end_era that we journaled that we are no passing by, +/// for each `end_era` that we journaled that we are no passing by, /// we remove all of its removes assuming it is canonical and all /// of its inserts otherwise. // TODO: store last_era, reclaim_period. diff --git a/util/src/migration/mod.rs b/util/src/migration/mod.rs index 80cfa29b6..9c1776699 100644 --- a/util/src/migration/mod.rs +++ b/util/src/migration/mod.rs @@ -231,7 +231,7 @@ impl Manager { trace!(target: "migration", "Total migrations to execute for version {}: {}", version, migrations.len()); if migrations.is_empty() { return Err(Error::MigrationImpossible) }; - let columns = migrations.iter().nth(0).and_then(|m| m.pre_columns()); + let columns = migrations.get(0).and_then(|m| m.pre_columns()); trace!(target: "migration", "Expecting database to contain {:?} columns", columns); let mut db_config = DatabaseConfig { diff --git a/util/src/overlaydb.rs b/util/src/overlaydb.rs index 9ebc7d1d4..009ef151e 100644 --- a/util/src/overlaydb.rs +++ b/util/src/overlaydb.rs @@ -66,7 +66,7 @@ impl OverlayDB { pub fn commit_to_batch(&mut self, batch: &mut DBTransaction) -> Result { let mut ret = 0u32; let mut deletes = 0usize; - for i in self.overlay.drain().into_iter() { + for i in self.overlay.drain() { let (key, (value, rc)) = i; if rc != 0 { match self.payload(&key) { @@ -133,7 +133,7 @@ impl HashDB for OverlayDB { ret.insert(h, r as i32); } - for (key, refs) in self.overlay.keys().into_iter() { + for (key, refs) in self.overlay.keys() { let refs = *ret.get(&key).unwrap_or(&0) + refs; ret.insert(key, refs); } diff --git a/util/src/trie/journal.rs b/util/src/trie/journal.rs index 49bd1bf0f..55aed70ac 100644 --- a/util/src/trie/journal.rs +++ b/util/src/trie/journal.rs @@ -84,7 +84,7 @@ impl Journal { pub fn apply(self, db: &mut HashDB) -> Score { trace!("applying {:?} changes", self.0.len()); let mut ret = Score{inserts: 0, removes: 0}; - for d in self.0.into_iter() { + for d in self.0 { match d { Operation::Delete(h) => { trace!("TrieDBMut::apply --- {:?}", &h); diff --git a/util/src/trie/triedb.rs b/util/src/trie/triedb.rs index ad1e509a0..cd8c9939a 100644 --- a/util/src/trie/triedb.rs +++ b/util/src/trie/triedb.rs @@ -87,7 +87,7 @@ impl<'db> TrieDB<'db> { /// Convert a vector of hashes to a hashmap of hash to occurrences. pub fn to_map(hashes: Vec) -> HashMap { let mut r: HashMap = HashMap::new(); - for h in hashes.into_iter() { + for h in hashes { *r.entry(h).or_insert(0) += 1; } r @@ -97,7 +97,7 @@ impl<'db> TrieDB<'db> { /// trie. pub fn db_items_remaining(&self) -> super::Result> { let mut ret = self.db.keys(); - for (k, v) in Self::to_map(try!(self.keys())).into_iter() { + for (k, v) in Self::to_map(try!(self.keys())) { let keycount = *ret.get(&k).unwrap_or(&0); match keycount <= v as i32 { true => ret.remove(&k), From 6c79decddaca40ee577421415c58dc1a3c02f264 Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Thu, 27 Oct 2016 06:30:05 +0000 Subject: [PATCH 10/47] [ci skip] js-precompiled 20161027-062905 --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index a58a426a4..aea550503 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1211,7 +1211,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#9d77b0117c2fa0b17dd381853b0020b1fdc96855" +source = "git+https://github.com/ethcore/js-precompiled.git#a27ea0e6a88c8f0e6a1556819447f9e946549552" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] From ae46361b3302f35d3e11579a9707841435f07440 Mon Sep 17 00:00:00 2001 From: "Denis S. Soldatov aka General-Beck" Date: Thu, 27 Oct 2016 13:42:09 +0700 Subject: [PATCH 11/47] Update gitlab-ci fix windows HOST_CC HOST_CXX -> x86_64-pc-windows-msvc --- .gitlab-ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 17907b49a..46fddb579 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -311,6 +311,8 @@ windows: - set LIB=C:\vs2015\VC\lib;C:\Program Files (x86)\Windows Kits\10\Lib\10.0.10240.0\ucrt\x64 - set RUST_BACKTRACE=1 - set RUSTFLAGS=%RUSTFLAGS% -Zorbit=off -D warnings + - set HOST_CC=x86_64-pc-windows-msvc + - set HOST_CXX=x86_64-pc-windows-msvc - rustup default stable-x86_64-pc-windows-msvc - cargo build --release --verbose - curl -sL --url "https://github.com/ethcore/win-build/raw/master/SimpleFC.dll" -o nsis\SimpleFC.dll From 8e6ca7fc613c019817a8bf79bc7e547f3e421f7d Mon Sep 17 00:00:00 2001 From: "Denis S. Soldatov aka General-Beck" Date: Thu, 27 Oct 2016 13:45:16 +0700 Subject: [PATCH 12/47] Update gitlab-ci remove armv6 from master [ci skip] --- .gitlab-ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 46fddb579..17d6fe985 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -214,7 +214,6 @@ linux-armv6: stage: build image: ethcore/rust-armv6:latest only: - - master - beta - tags - stable From 005bdd5d0737a5550771926558a339c439668d4c Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Thu, 27 Oct 2016 06:55:53 +0000 Subject: [PATCH 13/47] [ci skip] js-precompiled 20161027-065456 --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index aea550503..7cbf8df18 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1211,7 +1211,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#a27ea0e6a88c8f0e6a1556819447f9e946549552" +source = "git+https://github.com/ethcore/js-precompiled.git#62f407081dfa20ad534b9ee555494978b76211b5" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] From 3347d590427346728a5d3593935c58fb937d04d6 Mon Sep 17 00:00:00 2001 From: "Denis S. Soldatov aka General-Beck" Date: Thu, 27 Oct 2016 14:00:05 +0700 Subject: [PATCH 14/47] Update gitlab-ci add to arm* export HOST_CC=gcc export HOST_CXX=g++ --- .gitlab-ci.yml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 17d6fe985..2a456e9eb 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -6,8 +6,6 @@ variables: SIMPLECOV: "true" RUST_BACKTRACE: "1" RUSTFLAGS: "-D warnings" - HOST_CC: "gcc" - HOST_CXX: "g++" cache: key: "$CI_BUILD_NAME/$CI_BUILD_REF_NAME" untracked: true @@ -145,6 +143,8 @@ linux-armv7: script: - export CC=arm-linux-gnueabihf-gcc - export CXX=arm-linux-gnueabihf-g++ + - export HOST_CC=gcc + - export HOST_CXX=g++ - rm -rf .cargo - mkdir -p .cargo - echo "[target.armv7-unknown-linux-gnueabihf]" >> .cargo/config @@ -183,6 +183,8 @@ linux-arm: script: - export CC=arm-linux-gnueabihf-gcc - export CXX=arm-linux-gnueabihf-g++ + - export HOST_CC=gcc + - export HOST_CXX=g++ - rm -rf .cargo - mkdir -p .cargo - echo "[target.arm-unknown-linux-gnueabihf]" >> .cargo/config @@ -220,6 +222,8 @@ linux-armv6: script: - export CC=arm-linux-gnueabi-gcc - export CXX=arm-linux-gnueabi-g++ + - export HOST_CC=gcc + - export HOST_CXX=g++ - rm -rf .cargo - mkdir -p .cargo - echo "[target.arm-unknown-linux-gnueabi]" >> .cargo/config @@ -251,6 +255,8 @@ linux-aarch64: script: - export CC=aarch64-linux-gnu-gcc - export CXX=aarch64-linux-gnu-g++ + - export HOST_CC=gcc + - export HOST_CXX=g++ - rm -rf .cargo - mkdir -p .cargo - echo "[target.aarch64-unknown-linux-gnu]" >> .cargo/config @@ -310,8 +316,6 @@ windows: - set LIB=C:\vs2015\VC\lib;C:\Program Files (x86)\Windows Kits\10\Lib\10.0.10240.0\ucrt\x64 - set RUST_BACKTRACE=1 - set RUSTFLAGS=%RUSTFLAGS% -Zorbit=off -D warnings - - set HOST_CC=x86_64-pc-windows-msvc - - set HOST_CXX=x86_64-pc-windows-msvc - rustup default stable-x86_64-pc-windows-msvc - cargo build --release --verbose - curl -sL --url "https://github.com/ethcore/win-build/raw/master/SimpleFC.dll" -o nsis\SimpleFC.dll From 02b6d3943dc3f20e2646d30640ac2b84dfd2888d Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Thu, 27 Oct 2016 07:09:06 +0000 Subject: [PATCH 15/47] [ci skip] js-precompiled 20161027-070811 --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 7cbf8df18..4fbed7ddb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1211,7 +1211,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#62f407081dfa20ad534b9ee555494978b76211b5" +source = "git+https://github.com/ethcore/js-precompiled.git#4fdd06b96f86061ee57bc4ca81ddf314ba2931c4" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] From bdc372462afc63f88711e77f5bc40d51f8fc4cbb Mon Sep 17 00:00:00 2001 From: Arkadiy Paronyan Date: Thu, 27 Oct 2016 13:49:44 +0200 Subject: [PATCH 16/47] Change sync protocol ID (#2912) --- sync/src/api.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sync/src/api.rs b/sync/src/api.rs index b227dcd60..67a81237a 100644 --- a/sync/src/api.rs +++ b/sync/src/api.rs @@ -34,7 +34,7 @@ use std::str::FromStr; use parking_lot::RwLock; use chain::{ETH_PACKET_COUNT, SNAPSHOT_SYNC_PACKET_COUNT}; -pub const WARP_SYNC_PROTOCOL_ID: ProtocolId = *b"bam"; +pub const WARP_SYNC_PROTOCOL_ID: ProtocolId = *b"par"; /// Sync configuration #[derive(Debug, Clone, Copy)] From 176dc0e945696eeeeeb33ee997de33f64c4fd312 Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Thu, 27 Oct 2016 11:51:52 +0000 Subject: [PATCH 17/47] [ci skip] js-precompiled 20161027-115054 --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 4fbed7ddb..cad330266 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1211,7 +1211,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#4fdd06b96f86061ee57bc4ca81ddf314ba2931c4" +source = "git+https://github.com/ethcore/js-precompiled.git#c1dc23ab7a8a01aee8587915895fd0125695f775" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] From 3edd9e4bee49af6ab72c995f3d900266862ea2c9 Mon Sep 17 00:00:00 2001 From: Arkadiy Paronyan Date: Thu, 27 Oct 2016 15:25:54 +0200 Subject: [PATCH 18/47] Fixed GetNodeData output (#2892) --- sync/src/chain.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/sync/src/chain.rs b/sync/src/chain.rs index 764eccdac..d18adb6ea 100644 --- a/sync/src/chain.rs +++ b/sync/src/chain.rs @@ -1428,16 +1428,18 @@ impl ChainSync { } count = min(count, MAX_NODE_DATA_TO_SEND); let mut added = 0usize; - let mut data = Bytes::new(); + let mut data = Vec::new(); for i in 0..count { - if let Some(mut hdr) = io.chain().state_data(&try!(r.val_at::(i))) { - data.append(&mut hdr); + if let Some(hdr) = io.chain().state_data(&try!(r.val_at::(i))) { + data.push(hdr); added += 1; } } trace!(target: "sync", "{} -> GetNodeData: return {} entries", peer_id, added); let mut rlp = RlpStream::new_list(added); - rlp.append_raw(&data, added); + for d in data.into_iter() { + rlp.append(&d); + } Ok(Some((NODE_DATA_PACKET, rlp))) } @@ -2026,7 +2028,9 @@ mod tests { assert!(rlp_result.is_some()); // the length of one rlp-encoded hashe - assert_eq!(34, rlp_result.unwrap().1.out().len()); + let rlp = rlp_result.unwrap().1.out(); + let rlp = Rlp::new(&rlp); + assert_eq!(1, rlp.item_count()); io.sender = Some(2usize); From d315ec29e190dc14d628aaa0882d39533f0bb13f Mon Sep 17 00:00:00 2001 From: Nikolay Volf Date: Thu, 27 Oct 2016 16:26:29 +0300 Subject: [PATCH 19/47] Apply pending block details on commit (#2254) * failing test * Cache pending details * [ci skip] updated comment --- ethcore/src/blockchain/blockchain.rs | 30 +++++++++++++++++----------- ethcore/src/client/client.rs | 30 ++++++++++++++++++++++++++++ ethcore/src/tests/helpers.rs | 9 +++++++-- 3 files changed, 55 insertions(+), 14 deletions(-) diff --git a/ethcore/src/blockchain/blockchain.rs b/ethcore/src/blockchain/blockchain.rs index 32d42fe31..282039c16 100644 --- a/ethcore/src/blockchain/blockchain.rs +++ b/ethcore/src/blockchain/blockchain.rs @@ -196,6 +196,7 @@ pub struct BlockChain { pending_best_block: RwLock>, pending_block_hashes: RwLock>, + pending_block_details: RwLock>, pending_transaction_addresses: RwLock>>, } @@ -439,6 +440,7 @@ impl BlockChain { cache_man: Mutex::new(cache_man), pending_best_block: RwLock::new(None), pending_block_hashes: RwLock::new(HashMap::new()), + pending_block_details: RwLock::new(HashMap::new()), pending_transaction_addresses: RwLock::new(HashMap::new()), }; @@ -895,17 +897,6 @@ impl BlockChain { /// Prepares extras update. fn prepare_update(&self, batch: &mut DBTransaction, update: ExtrasUpdate, is_best: bool) { - { - let block_hashes: Vec<_> = update.block_details.keys().cloned().collect(); - - let mut write_details = self.block_details.write(); - batch.extend_with_cache(db::COL_EXTRA, &mut *write_details, update.block_details, CacheUpdatePolicy::Overwrite); - - let mut cache_man = self.cache_man.lock(); - for hash in block_hashes { - cache_man.note_used(CacheID::BlockDetails(hash)); - } - } { let mut write_receipts = self.block_receipts.write(); @@ -917,7 +908,7 @@ impl BlockChain { batch.extend_with_cache(db::COL_EXTRA, &mut *write_blocks_blooms, update.blocks_blooms, CacheUpdatePolicy::Remove); } - // These cached values must be updated last with all three locks taken to avoid + // These cached values must be updated last with all four locks taken to avoid // cache decoherence { let mut best_block = self.pending_best_block.write(); @@ -935,8 +926,10 @@ impl BlockChain { }, } let mut write_hashes = self.pending_block_hashes.write(); + let mut write_details = self.pending_block_details.write(); let mut write_txs = self.pending_transaction_addresses.write(); + batch.extend_with_cache(db::COL_EXTRA, &mut *write_details, update.block_details, CacheUpdatePolicy::Overwrite); batch.extend_with_cache(db::COL_EXTRA, &mut *write_hashes, update.block_hashes, CacheUpdatePolicy::Overwrite); batch.extend_with_option_cache(db::COL_EXTRA, &mut *write_txs, update.transactions_addresses, CacheUpdatePolicy::Overwrite); } @@ -946,9 +939,11 @@ impl BlockChain { pub fn commit(&self) { let mut pending_best_block = self.pending_best_block.write(); let mut pending_write_hashes = self.pending_block_hashes.write(); + let mut pending_block_details = self.pending_block_details.write(); let mut pending_write_txs = self.pending_transaction_addresses.write(); let mut best_block = self.best_block.write(); + let mut write_block_details = self.block_details.write(); let mut write_hashes = self.block_hashes.write(); let mut write_txs = self.transaction_addresses.write(); // update best block @@ -961,9 +956,11 @@ impl BlockChain { let pending_hashes_keys: Vec<_> = pending_write_hashes.keys().cloned().collect(); let enacted_txs_keys: Vec<_> = enacted_txs.keys().cloned().collect(); + let pending_block_hashes: Vec<_> = pending_block_details.keys().cloned().collect(); write_hashes.extend(mem::replace(&mut *pending_write_hashes, HashMap::new())); write_txs.extend(enacted_txs.into_iter().map(|(k, v)| (k, v.expect("Transactions were partitioned; qed")))); + write_block_details.extend(mem::replace(&mut *pending_block_details, HashMap::new())); for hash in retracted_txs.keys() { write_txs.remove(hash); @@ -977,6 +974,10 @@ impl BlockChain { for hash in enacted_txs_keys { cache_man.note_used(CacheID::TransactionAddresses(hash)); } + + for hash in pending_block_hashes { + cache_man.note_used(CacheID::BlockDetails(hash)); + } } /// Iterator that lists `first` and then all of `first`'s ancestors, by hash. @@ -1297,6 +1298,11 @@ impl BlockChain { ancient_block_number: best_ancient_block.as_ref().map(|b| b.number), } } + + #[cfg(test)] + pub fn db(&self) -> &Arc { + &self.db + } } #[cfg(test)] diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index cb963dadc..b59d892c5 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -1216,3 +1216,33 @@ impl MayPanic for Client { self.panic_handler.on_panic(closure); } } + + +#[test] +fn should_not_cache_details_before_commit() { + use tests::helpers::*; + use std::thread; + use std::time::Duration; + use std::sync::atomic::{AtomicBool, Ordering}; + + let client = generate_dummy_client(0); + let genesis = client.chain_info().best_block_hash; + let (new_hash, new_block) = get_good_dummy_block_hash(); + + let go = { + // Separate thread uncommited transaction + let go = Arc::new(AtomicBool::new(false)); + let go_thread = go.clone(); + let another_client = client.reference().clone(); + thread::spawn(move || { + let mut batch = DBTransaction::new(&*another_client.chain.read().db().clone()); + another_client.chain.read().insert_block(&mut batch, &new_block, Vec::new()); + go_thread.store(true, Ordering::SeqCst); + }); + go + }; + + while !go.load(Ordering::SeqCst) { thread::park_timeout(Duration::from_millis(5)); } + + assert!(client.tree_route(&genesis, &new_hash).is_none()); +} diff --git a/ethcore/src/tests/helpers.rs b/ethcore/src/tests/helpers.rs index 7b8264720..7b952b30c 100644 --- a/ethcore/src/tests/helpers.rs +++ b/ethcore/src/tests/helpers.rs @@ -388,7 +388,7 @@ pub fn get_good_dummy_block_fork_seq(start_number: usize, count: usize, parent_h r } -pub fn get_good_dummy_block() -> Bytes { +pub fn get_good_dummy_block_hash() -> (H256, Bytes) { let mut block_header = Header::new(); let test_spec = get_test_spec(); let test_engine = &test_spec.engine; @@ -399,7 +399,12 @@ pub fn get_good_dummy_block() -> Bytes { block_header.set_parent_hash(test_spec.genesis_header().hash()); block_header.set_state_root(test_spec.genesis_header().state_root().clone()); - create_test_block(&block_header) + (block_header.hash(), create_test_block(&block_header)) +} + +pub fn get_good_dummy_block() -> Bytes { + let (_, bytes) = get_good_dummy_block_hash(); + bytes } pub fn get_bad_state_dummy_block() -> Bytes { From 123b75179c2b047a08093ce9947ddd45547fd880 Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Thu, 27 Oct 2016 13:40:42 +0000 Subject: [PATCH 20/47] [ci skip] js-precompiled 20161027-133941 --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index cad330266..9bf28cca9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1211,7 +1211,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#c1dc23ab7a8a01aee8587915895fd0125695f775" +source = "git+https://github.com/ethcore/js-precompiled.git#3fd329340b2753da0f9762d45e65960878c5d008" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] From 6978042c16e26112214f89aa97b6db7ae59fa479 Mon Sep 17 00:00:00 2001 From: arkpar Date: Thu, 27 Oct 2016 16:20:11 +0200 Subject: [PATCH 21/47] Packet id tracing --- util/network/src/session.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/util/network/src/session.rs b/util/network/src/session.rs index 8d5578e83..6d6009535 100644 --- a/util/network/src/session.rs +++ b/util/network/src/session.rs @@ -395,7 +395,7 @@ impl Session { PACKET_PEERS => Ok(SessionData::None), PACKET_USER ... PACKET_LAST => { let mut i = 0usize; - while packet_id > self.info.capabilities[i].id_offset + self.info.capabilities[i].packet_count { + while packet_id >= self.info.capabilities[i].id_offset + self.info.capabilities[i].packet_count { i += 1; if i == self.info.capabilities.len() { debug!(target: "network", "Unknown packet: {:?}", packet_id); @@ -406,6 +406,7 @@ impl Session { // map to protocol let protocol = self.info.capabilities[i].protocol; let pid = packet_id - self.info.capabilities[i].id_offset; + trace!(target: "network", "Packet {} mapped to {:?}:{}, i={}, capabilities={:?}", packet_id, protocol, pid, i, self.info.capabilities); Ok(SessionData::Packet { data: packet.data, protocol: protocol, packet_id: pid } ) }, _ => { From 7c6112e9f091fb525f5960249b2b66f587fe9c02 Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Thu, 27 Oct 2016 14:33:50 +0000 Subject: [PATCH 22/47] [ci skip] js-precompiled 20161027-143256 --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 9bf28cca9..14a8e1f01 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1211,7 +1211,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#3fd329340b2753da0f9762d45e65960878c5d008" +source = "git+https://github.com/ethcore/js-precompiled.git#10c0948cd0986df03ba9b2e5fae928f1a5618c9a" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] From 8cef5177885a94d044734a09352708b91ed3cca1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Thu, 27 Oct 2016 16:56:18 +0200 Subject: [PATCH 23/47] Fixing config values for pruning_history (#2918) --- parity/cli/mod.rs | 14 ++++++++++++++ parity/cli/usage.txt | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/parity/cli/mod.rs b/parity/cli/mod.rs index 27e0cb4dc..9290ab0f9 100644 --- a/parity/cli/mod.rs +++ b/parity/cli/mod.rs @@ -445,6 +445,20 @@ mod tests { assert_eq!(args.flag_chain, "xyz".to_owned()); } + #[test] + fn should_use_config_if_cli_is_missing() { + let mut config = Config::default(); + let mut footprint = Footprint::default(); + footprint.pruning_history = Some(128); + config.footprint = Some(footprint); + + // when + let args = Args::parse_with_config(&["parity"], config).unwrap(); + + // then + assert_eq!(args.flag_pruning_history, 128); + } + #[test] fn should_parse_full_config() { // given diff --git a/parity/cli/usage.txt b/parity/cli/usage.txt index af8e83f0e..bebdafd97 100644 --- a/parity/cli/usage.txt +++ b/parity/cli/usage.txt @@ -225,7 +225,7 @@ Footprint Options: auto - use the method most recently synced or default to fast if none synced (default: {flag_pruning}). --pruning-history NUM Set a number of recent states to keep when pruning - is active. [default: {flag_pruning_history}]. + is active. (default: {flag_pruning_history}). --cache-size-db MB Override database cache size (default: {flag_cache_size_db}). --cache-size-blocks MB Specify the prefered size of the blockchain cache in megabytes (default: {flag_cache_size_blocks}). From bb14eea66cda190b49eaab2f4bc58217089e9a3a Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Thu, 27 Oct 2016 15:09:11 +0000 Subject: [PATCH 24/47] [ci skip] js-precompiled 20161027-150816 --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 14a8e1f01..df75941da 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1211,7 +1211,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#10c0948cd0986df03ba9b2e5fae928f1a5618c9a" +source = "git+https://github.com/ethcore/js-precompiled.git#0668ff1d25ddd1db11f44a1b1e6d050e41b3841b" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] From 90aa2fefc2382d8a34fb643072f524dca7a47825 Mon Sep 17 00:00:00 2001 From: "Denis S. Soldatov aka General-Beck" Date: Thu, 27 Oct 2016 23:13:51 +0700 Subject: [PATCH 25/47] Update gitlab-ci fix darwin md5 --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2a456e9eb..3d822a9d5 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -293,6 +293,7 @@ darwin: - stable script: - cargo build --release --verbose + - rm -rf parity.md5 - md5sum target/release/parity >> parity.md5 - aws configure set aws_access_key_id $s3_key - aws configure set aws_secret_access_key $s3_secret From abb1da5f4b8b02ce164a6dd5459bb7c5883ce8f6 Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Thu, 27 Oct 2016 16:49:18 +0000 Subject: [PATCH 26/47] [ci skip] js-precompiled 20161027-164817 --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index df75941da..71684d5c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1211,7 +1211,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#0668ff1d25ddd1db11f44a1b1e6d050e41b3841b" +source = "git+https://github.com/ethcore/js-precompiled.git#0053be493c742cbd7332ba647b3c84d35327abd7" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] From 8dff4012a68c35fe50450364b617d7c1a7045c17 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Thu, 27 Oct 2016 19:26:34 +0200 Subject: [PATCH 27/47] Personal split (#2879) * Split personal namespace into Safe and Unsafe part * Re-add api.personal.accountsInfo() calls to dapps * Removing listGethAccounts from safe personal --- .../basiccoin/Application/application.js | 4 +- .../dapps/gavcoin/Application/application.js | 2 +- js/src/dapps/githubhint/services.js | 2 +- js/src/dapps/registry/addresses/actions.js | 12 +- js/src/dapps/signaturereg/services.js | 2 +- js/src/dapps/tokenreg/Accounts/actions.js | 2 +- parity/cli/config.full.toml | 4 +- parity/cli/mod.rs | 8 +- parity/cli/usage.txt | 2 +- parity/rpc_apis.rs | 27 ++- rpc/src/v1/impls/mod.rs | 2 + rpc/src/v1/impls/personal.rs | 154 +------------- rpc/src/v1/impls/personal_accounts.rs | 194 ++++++++++++++++++ rpc/src/v1/mod.rs | 2 +- rpc/src/v1/tests/mocked/personal.rs | 6 +- rpc/src/v1/traits/mod.rs | 2 +- rpc/src/v1/traits/personal.rs | 50 +++-- 17 files changed, 276 insertions(+), 199 deletions(-) create mode 100644 rpc/src/v1/impls/personal_accounts.rs diff --git a/js/src/dapps/basiccoin/Application/application.js b/js/src/dapps/basiccoin/Application/application.js index d84085c98..4ab97ab6c 100644 --- a/js/src/dapps/basiccoin/Application/application.js +++ b/js/src/dapps/basiccoin/Application/application.js @@ -16,7 +16,7 @@ import React, { Component, PropTypes } from 'react'; -// import { api } from '../parity'; +import { api } from '../parity'; import { attachInstances } from '../services'; import Header from './Header'; @@ -83,7 +83,7 @@ export default class Application extends Component { Promise .all([ attachInstances(), - null // api.personal.accountsInfo() + api.personal.accountsInfo() ]) .then(([{ managerInstance, registryInstance, tokenregInstance }, accountsInfo]) => { accountsInfo = accountsInfo || {}; diff --git a/js/src/dapps/gavcoin/Application/application.js b/js/src/dapps/gavcoin/Application/application.js index 29c86c78d..d5d26bc3e 100644 --- a/js/src/dapps/gavcoin/Application/application.js +++ b/js/src/dapps/gavcoin/Application/application.js @@ -206,7 +206,7 @@ export default class Application extends Component { .all([ registry.getAddress.call({}, [api.util.sha3('gavcoin'), 'A']), api.eth.accounts(), - null // api.personal.accountsInfo() + api.personal.accountsInfo() ]); }) .then(([address, addresses, infos]) => { diff --git a/js/src/dapps/githubhint/services.js b/js/src/dapps/githubhint/services.js index 1904be2d7..b7676d5f5 100644 --- a/js/src/dapps/githubhint/services.js +++ b/js/src/dapps/githubhint/services.js @@ -29,7 +29,7 @@ export function attachInterface () { .all([ registry.getAddress.call({}, [api.util.sha3('githubhint'), 'A']), api.eth.accounts(), - null // api.personal.accountsInfo() + api.personal.accountsInfo() ]); }) .then(([address, addresses, accountsInfo]) => { diff --git a/js/src/dapps/registry/addresses/actions.js b/js/src/dapps/registry/addresses/actions.js index dfd7d16a3..17975f9e6 100644 --- a/js/src/dapps/registry/addresses/actions.js +++ b/js/src/dapps/registry/addresses/actions.js @@ -22,12 +22,16 @@ export const fetch = () => (dispatch) => { return Promise .all([ api.eth.accounts(), - null // api.personal.accountsInfo() + api.personal.accountsInfo() ]) .then(([ accounts, data ]) => { - const addresses = accounts.map((address) => { - return { address, isAccount: true }; - }); + data = data || {}; + const addresses = Object.keys(data) + .filter((address) => data[address] && !data[address].meta.deleted) + .map((address) => ({ + ...data[address], address, + isAccount: accounts.includes(address) + })); dispatch(set(addresses)); }) .catch((error) => { diff --git a/js/src/dapps/signaturereg/services.js b/js/src/dapps/signaturereg/services.js index 7219ddff1..cab324f7e 100644 --- a/js/src/dapps/signaturereg/services.js +++ b/js/src/dapps/signaturereg/services.js @@ -50,7 +50,7 @@ export function attachInterface (callback) { .all([ registry.getAddress.call({}, [api.util.sha3('signaturereg'), 'A']), api.eth.accounts(), - null // api.personal.accountsInfo() + api.personal.accountsInfo() ]); }) .then(([address, addresses, accountsInfo]) => { diff --git a/js/src/dapps/tokenreg/Accounts/actions.js b/js/src/dapps/tokenreg/Accounts/actions.js index f093b5300..f501399c2 100644 --- a/js/src/dapps/tokenreg/Accounts/actions.js +++ b/js/src/dapps/tokenreg/Accounts/actions.js @@ -38,7 +38,7 @@ export const loadAccounts = () => (dispatch) => { Promise .all([ api.eth.accounts(), - null // api.personal.accountsInfo() + api.personal.accountsInfo() ]) .then(([ accounts, accountsInfo ]) => { accountsInfo = accountsInfo || {}; diff --git a/parity/cli/config.full.toml b/parity/cli/config.full.toml index fd2e11f98..4d1d48025 100644 --- a/parity/cli/config.full.toml +++ b/parity/cli/config.full.toml @@ -41,13 +41,13 @@ disable = false port = 8545 interface = "local" cors = "null" -apis = ["web3", "eth", "net", "ethcore", "traces", "rpc"] +apis = ["web3", "eth", "net", "ethcore", "traces", "rpc", "personal_safe"] hosts = ["none"] [ipc] disable = false path = "$HOME/.parity/jsonrpc.ipc" -apis = ["web3", "eth", "net", "ethcore", "traces", "rpc"] +apis = ["web3", "eth", "net", "ethcore", "traces", "rpc", "personal_safe"] [dapps] disable = false diff --git a/parity/cli/mod.rs b/parity/cli/mod.rs index 9290ab0f9..68411414f 100644 --- a/parity/cli/mod.rs +++ b/parity/cli/mod.rs @@ -145,7 +145,7 @@ usage! { or |c: &Config| otry!(c.rpc).interface.clone(), flag_jsonrpc_cors: Option = None, or |c: &Config| otry!(c.rpc).cors.clone().map(Some), - flag_jsonrpc_apis: String = "web3,eth,net,ethcore,traces,rpc", + flag_jsonrpc_apis: String = "web3,eth,net,ethcore,traces,rpc,personal_safe", or |c: &Config| otry!(c.rpc).apis.clone().map(|vec| vec.join(",")), flag_jsonrpc_hosts: String = "none", or |c: &Config| otry!(c.rpc).hosts.clone().map(|vec| vec.join(",")), @@ -155,7 +155,7 @@ usage! { or |c: &Config| otry!(c.ipc).disable.clone(), flag_ipc_path: String = "$HOME/.parity/jsonrpc.ipc", or |c: &Config| otry!(c.ipc).path.clone(), - flag_ipc_apis: String = "web3,eth,net,ethcore,traces,rpc", + flag_ipc_apis: String = "web3,eth,net,ethcore,traces,rpc,personal_safe", or |c: &Config| otry!(c.ipc).apis.clone().map(|vec| vec.join(",")), // DAPPS @@ -534,13 +534,13 @@ mod tests { flag_jsonrpc_port: 8545u16, flag_jsonrpc_interface: "local".into(), flag_jsonrpc_cors: Some("null".into()), - flag_jsonrpc_apis: "web3,eth,net,ethcore,traces,rpc".into(), + flag_jsonrpc_apis: "web3,eth,net,ethcore,traces,rpc,personal_safe".into(), flag_jsonrpc_hosts: "none".into(), // IPC flag_no_ipc: false, flag_ipc_path: "$HOME/.parity/jsonrpc.ipc".into(), - flag_ipc_apis: "web3,eth,net,ethcore,traces,rpc".into(), + flag_ipc_apis: "web3,eth,net,ethcore,traces,rpc,personal_safe".into(), // DAPPS flag_no_dapps: false, diff --git a/parity/cli/usage.txt b/parity/cli/usage.txt index bebdafd97..d96d875cf 100644 --- a/parity/cli/usage.txt +++ b/parity/cli/usage.txt @@ -107,7 +107,7 @@ API and Console Options: --jsonrpc-apis APIS Specify the APIs available through the JSONRPC interface. APIS is a comma-delimited list of API name. Possible name are web3, eth, net, personal, - ethcore, ethcore_set, traces, rpc. + ethcore, ethcore_set, traces, rpc, personal_safe. (default: {flag_jsonrpc_apis}). --jsonrpc-hosts HOSTS List of allowed Host header values. This option will validate the Host header sent by the browser, it diff --git a/parity/rpc_apis.rs b/parity/rpc_apis.rs index f6ccf16a3..491f58a1b 100644 --- a/parity/rpc_apis.rs +++ b/parity/rpc_apis.rs @@ -33,7 +33,8 @@ pub enum Api { Web3, Net, Eth, - Personal, + PersonalSafe, + PersonalAccounts, Signer, Ethcore, EthcoreSet, @@ -51,7 +52,8 @@ impl FromStr for Api { "web3" => Ok(Web3), "net" => Ok(Net), "eth" => Ok(Eth), - "personal" => Ok(Personal), + "personal" => Ok(PersonalAccounts), + "personal_safe" => Ok(PersonalSafe), "signer" => Ok(Signer), "ethcore" => Ok(Ethcore), "ethcore_set" => Ok(EthcoreSet), @@ -114,7 +116,8 @@ fn to_modules(apis: &[Api]) -> BTreeMap { Api::Web3 => ("web3", "1.0"), Api::Net => ("net", "1.0"), Api::Eth => ("eth", "1.0"), - Api::Personal => ("personal", "1.0"), + Api::PersonalSafe => ("personal_safe", "1.0"), + Api::PersonalAccounts => ("personal", "1.0"), Api::Signer => ("signer", "1.0"), Api::Ethcore => ("ethcore", "1.0"), Api::EthcoreSet => ("ethcore_set", "1.0"), @@ -131,11 +134,11 @@ impl ApiSet { match *self { ApiSet::List(ref apis) => apis.clone(), ApiSet::UnsafeContext => { - vec![Api::Web3, Api::Net, Api::Eth, Api::Ethcore, Api::Traces, Api::Rpc] + vec![Api::Web3, Api::Net, Api::Eth, Api::Ethcore, Api::Traces, Api::Rpc, Api::PersonalSafe] .into_iter().collect() }, ApiSet::SafeContext => { - vec![Api::Web3, Api::Net, Api::Eth, Api::Personal, Api::Signer, Api::Ethcore, Api::EthcoreSet, Api::Traces, Api::Rpc] + vec![Api::Web3, Api::Net, Api::Eth, Api::PersonalAccounts, Api::PersonalSafe, Api::Signer, Api::Ethcore, Api::EthcoreSet, Api::Traces, Api::Rpc] .into_iter().collect() }, } @@ -178,8 +181,11 @@ pub fn setup_rpc(server: T, deps: Arc, apis: ApiSet server.add_delegate(EthSigningUnsafeClient::new(&deps.client, &deps.secret_store, &deps.miner).to_delegate()); } }, - Api::Personal => { - server.add_delegate(PersonalClient::new(&deps.secret_store, &deps.client, &deps.miner, deps.geth_compatibility).to_delegate()); + Api::PersonalAccounts => { + server.add_delegate(PersonalAccountsClient::new(&deps.secret_store, &deps.client, &deps.miner, deps.geth_compatibility).to_delegate()); + }, + Api::PersonalSafe => { + server.add_delegate(PersonalClient::new(&deps.secret_store, &deps.client).to_delegate()); }, Api::Signer => { server.add_delegate(SignerClient::new(&deps.secret_store, &deps.client, &deps.miner, &deps.signer_service).to_delegate()); @@ -224,7 +230,8 @@ mod test { assert_eq!(Api::Web3, "web3".parse().unwrap()); assert_eq!(Api::Net, "net".parse().unwrap()); assert_eq!(Api::Eth, "eth".parse().unwrap()); - assert_eq!(Api::Personal, "personal".parse().unwrap()); + assert_eq!(Api::PersonalAccounts, "personal".parse().unwrap()); + assert_eq!(Api::PersonalSafe, "personal_safe".parse().unwrap()); assert_eq!(Api::Signer, "signer".parse().unwrap()); assert_eq!(Api::Ethcore, "ethcore".parse().unwrap()); assert_eq!(Api::EthcoreSet, "ethcore_set".parse().unwrap()); @@ -245,14 +252,14 @@ mod test { #[test] fn test_api_set_unsafe_context() { - let expected = vec![Api::Web3, Api::Net, Api::Eth, Api::Ethcore, Api::Traces, Api::Rpc] + let expected = vec![Api::Web3, Api::Net, Api::Eth, Api::Ethcore, Api::Traces, Api::Rpc, Api::PersonalSafe] .into_iter().collect(); assert_eq!(ApiSet::UnsafeContext.list_apis(), expected); } #[test] fn test_api_set_safe_context() { - let expected = vec![Api::Web3, Api::Net, Api::Eth, Api::Personal, Api::Signer, Api::Ethcore, Api::EthcoreSet, Api::Traces, Api::Rpc] + let expected = vec![Api::Web3, Api::Net, Api::Eth, Api::PersonalAccounts, Api::PersonalSafe, Api::Signer, Api::Ethcore, Api::EthcoreSet, Api::Traces, Api::Rpc] .into_iter().collect(); assert_eq!(ApiSet::SafeContext.list_apis(), expected); } diff --git a/rpc/src/v1/impls/mod.rs b/rpc/src/v1/impls/mod.rs index bf2013d99..c108f0b6b 100644 --- a/rpc/src/v1/impls/mod.rs +++ b/rpc/src/v1/impls/mod.rs @@ -32,6 +32,7 @@ mod ethcore; mod ethcore_set; mod net; mod personal; +mod personal_accounts; mod personal_signer; mod rpc; mod traces; @@ -43,6 +44,7 @@ pub use self::eth_filter::EthFilterClient; pub use self::eth_signing::{EthSigningUnsafeClient, EthSigningQueueClient}; pub use self::net::NetClient; pub use self::personal::PersonalClient; +pub use self::personal_accounts::PersonalAccountsClient; pub use self::personal_signer::SignerClient; pub use self::ethcore::EthcoreClient; pub use self::ethcore_set::EthcoreSetClient; diff --git a/rpc/src/v1/impls/personal.rs b/rpc/src/v1/impls/personal.rs index 0d6b63240..aacf90c91 100644 --- a/rpc/src/v1/impls/personal.rs +++ b/rpc/src/v1/impls/personal.rs @@ -17,34 +17,26 @@ //! Account management (personal) rpc implementation use std::sync::{Arc, Weak}; use std::collections::{BTreeMap}; -use util::{Address}; use jsonrpc_core::*; -use ethkey::{Brain, Generator}; use v1::traits::Personal; -use v1::types::{H160 as RpcH160, TransactionRequest}; +use v1::types::{H160 as RpcH160}; use v1::helpers::errors; use v1::helpers::params::expect_no_params; -use v1::helpers::dispatch::sign_and_dispatch; use ethcore::account_provider::AccountProvider; use ethcore::client::MiningBlockChainClient; -use ethcore::miner::MinerService; /// Account management (personal) rpc implementation. -pub struct PersonalClient where C: MiningBlockChainClient, M: MinerService { +pub struct PersonalClient where C: MiningBlockChainClient { accounts: Weak, client: Weak, - miner: Weak, - allow_perm_unlock: bool, } -impl PersonalClient where C: MiningBlockChainClient, M: MinerService { +impl PersonalClient where C: MiningBlockChainClient { /// Creates new PersonalClient - pub fn new(store: &Arc, client: &Arc, miner: &Arc, allow_perm_unlock: bool) -> Self { + pub fn new(store: &Arc, client: &Arc) -> Self { PersonalClient { accounts: Arc::downgrade(store), client: Arc::downgrade(client), - miner: Arc::downgrade(miner), - allow_perm_unlock: allow_perm_unlock, } } @@ -55,7 +47,7 @@ impl PersonalClient where C: MiningBlockChainClient, M: MinerService } } -impl Personal for PersonalClient where C: MiningBlockChainClient, M: MinerService { +impl Personal for PersonalClient where C: MiningBlockChainClient { fn accounts(&self, params: Params) -> Result { try!(self.active()); @@ -66,125 +58,6 @@ impl Personal for PersonalClient where C: MiningBl Ok(to_value(&accounts.into_iter().map(Into::into).collect::>())) } - fn new_account(&self, params: Params) -> Result { - try!(self.active()); - from_params::<(String, )>(params).and_then( - |(pass, )| { - let store = take_weak!(self.accounts); - match store.new_account(&pass) { - Ok(address) => Ok(to_value(&RpcH160::from(address))), - Err(e) => Err(errors::account("Could not create account.", e)), - } - } - ) - } - - fn new_account_from_phrase(&self, params: Params) -> Result { - try!(self.active()); - from_params::<(String, String, )>(params).and_then( - |(phrase, pass, )| { - let store = take_weak!(self.accounts); - match store.insert_account(*Brain::new(phrase).generate().unwrap().secret(), &pass) { - Ok(address) => Ok(to_value(&RpcH160::from(address))), - Err(e) => Err(errors::account("Could not create account.", e)), - } - } - ) - } - - fn new_account_from_wallet(&self, params: Params) -> Result { - try!(self.active()); - from_params::<(String, String, )>(params).and_then( - |(json, pass, )| { - let store = take_weak!(self.accounts); - match store.import_presale(json.as_bytes(), &pass).or_else(|_| store.import_wallet(json.as_bytes(), &pass)) { - Ok(address) => Ok(to_value(&RpcH160::from(address))), - Err(e) => Err(errors::account("Could not create account.", e)), - } - } - ) - } - - fn unlock_account(&self, params: Params) -> Result { - try!(self.active()); - from_params::<(RpcH160, String, Option)>(params).and_then( - |(account, account_pass, duration)| { - let account: Address = account.into(); - let store = take_weak!(self.accounts); - let r = match (self.allow_perm_unlock, duration) { - (false, _) => store.unlock_account_temporarily(account, account_pass), - (true, Some(0)) => store.unlock_account_permanently(account, account_pass), - (true, Some(d)) => store.unlock_account_timed(account, account_pass, d as u32 * 1000), - (true, None) => store.unlock_account_timed(account, account_pass, 300_000), - }; - match r { - Ok(_) => Ok(Value::Bool(true)), - Err(_) => Ok(Value::Bool(false)), - } - } - ) - } - - fn test_password(&self, params: Params) -> Result { - try!(self.active()); - from_params::<(RpcH160, String)>(params).and_then( - |(account, password)| { - let account: Address = account.into(); - take_weak!(self.accounts) - .test_password(&account, password) - .map(|b| Value::Bool(b)) - .map_err(|e| errors::account("Could not fetch account info.", e)) - } - ) - } - - fn change_password(&self, params: Params) -> Result { - try!(self.active()); - from_params::<(RpcH160, String, String)>(params).and_then( - |(account, password, new_password)| { - let account: Address = account.into(); - take_weak!(self.accounts) - .change_password(&account, password, new_password) - .map(|_| Value::Null) - .map_err(|e| errors::account("Could not fetch account info.", e)) - } - ) - } - - fn sign_and_send_transaction(&self, params: Params) -> Result { - try!(self.active()); - from_params::<(TransactionRequest, String)>(params) - .and_then(|(request, password)| { - sign_and_dispatch( - &*take_weak!(self.client), - &*take_weak!(self.miner), - &*take_weak!(self.accounts), - request.into(), - Some(password) - ) - }) - } - - fn set_account_name(&self, params: Params) -> Result { - try!(self.active()); - let store = take_weak!(self.accounts); - from_params::<(RpcH160, String)>(params).and_then(|(addr, name)| { - let addr: Address = addr.into(); - store.set_account_name(addr.clone(), name.clone()).or_else(|_| store.set_address_name(addr, name)).expect("set_address_name always returns Ok; qed"); - Ok(Value::Null) - }) - } - - fn set_account_meta(&self, params: Params) -> Result { - try!(self.active()); - let store = take_weak!(self.accounts); - from_params::<(RpcH160, String)>(params).and_then(|(addr, meta)| { - let addr: Address = addr.into(); - store.set_account_meta(addr.clone(), meta.clone()).or_else(|_| store.set_address_meta(addr, meta)).expect("set_address_meta always returns Ok; qed"); - Ok(Value::Null) - }) - } - fn accounts_info(&self, params: Params) -> Result { try!(self.active()); try!(expect_no_params(params)); @@ -204,21 +77,4 @@ impl Personal for PersonalClient where C: MiningBl (format!("0x{}", a.hex()), Value::Object(m)) }).collect::>())) } - - fn geth_accounts(&self, params: Params) -> Result { - try!(self.active()); - try!(expect_no_params(params)); - let store = take_weak!(self.accounts); - Ok(to_value(&store.list_geth_accounts(false).into_iter().map(Into::into).collect::>())) - } - - fn import_geth_accounts(&self, params: Params) -> Result { - from_params::<(Vec,)>(params).and_then(|(addresses,)| { - let store = take_weak!(self.accounts); - Ok(to_value(&try!(store - .import_geth_accounts(addresses.into_iter().map(Into::into).collect(), false) - .map_err(|e| errors::account("Couldn't import Geth accounts", e)) - ).into_iter().map(Into::into).collect::>())) - }) - } } diff --git a/rpc/src/v1/impls/personal_accounts.rs b/rpc/src/v1/impls/personal_accounts.rs new file mode 100644 index 000000000..0fa236845 --- /dev/null +++ b/rpc/src/v1/impls/personal_accounts.rs @@ -0,0 +1,194 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity 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. + +// Parity 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 Parity. If not, see . + +//! Account management (personal) rpc implementation +use std::sync::{Arc, Weak}; +use util::{Address}; +use jsonrpc_core::*; +use ethkey::{Brain, Generator}; +use v1::traits::PersonalAccounts; +use v1::types::{H160 as RpcH160, TransactionRequest}; +use v1::helpers::errors; +use v1::helpers::params::expect_no_params; +use v1::helpers::dispatch::sign_and_dispatch; +use ethcore::account_provider::AccountProvider; +use ethcore::client::MiningBlockChainClient; +use ethcore::miner::MinerService; + +/// Account management (personal) rpc implementation. +pub struct PersonalAccountsClient where C: MiningBlockChainClient, M: MinerService { + accounts: Weak, + client: Weak, + miner: Weak, + allow_perm_unlock: bool, +} + +impl PersonalAccountsClient where C: MiningBlockChainClient, M: MinerService { + /// Creates new PersonalClient + pub fn new(store: &Arc, client: &Arc, miner: &Arc, allow_perm_unlock: bool) -> Self { + PersonalAccountsClient { + accounts: Arc::downgrade(store), + client: Arc::downgrade(client), + miner: Arc::downgrade(miner), + allow_perm_unlock: allow_perm_unlock, + } + } + + fn active(&self) -> Result<(), Error> { + // TODO: only call every 30s at most. + take_weak!(self.client).keep_alive(); + Ok(()) + } +} + +impl PersonalAccounts for PersonalAccountsClient where C: MiningBlockChainClient, M: MinerService { + + fn new_account(&self, params: Params) -> Result { + try!(self.active()); + from_params::<(String, )>(params).and_then( + |(pass, )| { + let store = take_weak!(self.accounts); + match store.new_account(&pass) { + Ok(address) => Ok(to_value(&RpcH160::from(address))), + Err(e) => Err(errors::account("Could not create account.", e)), + } + } + ) + } + + fn new_account_from_phrase(&self, params: Params) -> Result { + try!(self.active()); + from_params::<(String, String, )>(params).and_then( + |(phrase, pass, )| { + let store = take_weak!(self.accounts); + match store.insert_account(*Brain::new(phrase).generate().unwrap().secret(), &pass) { + Ok(address) => Ok(to_value(&RpcH160::from(address))), + Err(e) => Err(errors::account("Could not create account.", e)), + } + } + ) + } + + fn new_account_from_wallet(&self, params: Params) -> Result { + try!(self.active()); + from_params::<(String, String, )>(params).and_then( + |(json, pass, )| { + let store = take_weak!(self.accounts); + match store.import_presale(json.as_bytes(), &pass).or_else(|_| store.import_wallet(json.as_bytes(), &pass)) { + Ok(address) => Ok(to_value(&RpcH160::from(address))), + Err(e) => Err(errors::account("Could not create account.", e)), + } + } + ) + } + + fn unlock_account(&self, params: Params) -> Result { + try!(self.active()); + from_params::<(RpcH160, String, Option)>(params).and_then( + |(account, account_pass, duration)| { + let account: Address = account.into(); + let store = take_weak!(self.accounts); + let r = match (self.allow_perm_unlock, duration) { + (false, _) => store.unlock_account_temporarily(account, account_pass), + (true, Some(0)) => store.unlock_account_permanently(account, account_pass), + (true, Some(d)) => store.unlock_account_timed(account, account_pass, d as u32 * 1000), + (true, None) => store.unlock_account_timed(account, account_pass, 300_000), + }; + match r { + Ok(_) => Ok(Value::Bool(true)), + Err(_) => Ok(Value::Bool(false)), + } + } + ) + } + + fn test_password(&self, params: Params) -> Result { + try!(self.active()); + from_params::<(RpcH160, String)>(params).and_then( + |(account, password)| { + let account: Address = account.into(); + take_weak!(self.accounts) + .test_password(&account, password) + .map(|b| Value::Bool(b)) + .map_err(|e| errors::account("Could not fetch account info.", e)) + } + ) + } + + fn change_password(&self, params: Params) -> Result { + try!(self.active()); + from_params::<(RpcH160, String, String)>(params).and_then( + |(account, password, new_password)| { + let account: Address = account.into(); + take_weak!(self.accounts) + .change_password(&account, password, new_password) + .map(|_| Value::Null) + .map_err(|e| errors::account("Could not fetch account info.", e)) + } + ) + } + + fn sign_and_send_transaction(&self, params: Params) -> Result { + try!(self.active()); + from_params::<(TransactionRequest, String)>(params) + .and_then(|(request, password)| { + sign_and_dispatch( + &*take_weak!(self.client), + &*take_weak!(self.miner), + &*take_weak!(self.accounts), + request.into(), + Some(password) + ) + }) + } + + fn set_account_name(&self, params: Params) -> Result { + try!(self.active()); + let store = take_weak!(self.accounts); + from_params::<(RpcH160, String)>(params).and_then(|(addr, name)| { + let addr: Address = addr.into(); + store.set_account_name(addr.clone(), name.clone()).or_else(|_| store.set_address_name(addr, name)).expect("set_address_name always returns Ok; qed"); + Ok(Value::Null) + }) + } + + fn set_account_meta(&self, params: Params) -> Result { + try!(self.active()); + let store = take_weak!(self.accounts); + from_params::<(RpcH160, String)>(params).and_then(|(addr, meta)| { + let addr: Address = addr.into(); + store.set_account_meta(addr.clone(), meta.clone()).or_else(|_| store.set_address_meta(addr, meta)).expect("set_address_meta always returns Ok; qed"); + Ok(Value::Null) + }) + } + + fn import_geth_accounts(&self, params: Params) -> Result { + from_params::<(Vec,)>(params).and_then(|(addresses,)| { + let store = take_weak!(self.accounts); + Ok(to_value(&try!(store + .import_geth_accounts(addresses.into_iter().map(Into::into).collect(), false) + .map_err(|e| errors::account("Couldn't import Geth accounts", e)) + ).into_iter().map(Into::into).collect::>())) + }) + } + + fn geth_accounts(&self, params: Params) -> Result { + try!(self.active()); + try!(expect_no_params(params)); + let store = take_weak!(self.accounts); + Ok(to_value(&store.list_geth_accounts(false).into_iter().map(Into::into).collect::>())) + } +} diff --git a/rpc/src/v1/mod.rs b/rpc/src/v1/mod.rs index 5ba302cea..24560160c 100644 --- a/rpc/src/v1/mod.rs +++ b/rpc/src/v1/mod.rs @@ -26,6 +26,6 @@ pub mod traits; pub mod tests; pub mod types; -pub use self::traits::{Web3, Eth, EthFilter, EthSigning, Personal, PersonalSigner, Net, Ethcore, EthcoreSet, Traces, Rpc}; +pub use self::traits::{Web3, Eth, EthFilter, EthSigning, Personal, PersonalAccounts, PersonalSigner, Net, Ethcore, EthcoreSet, Traces, Rpc}; pub use self::impls::*; pub use self::helpers::{SigningQueue, SignerService, ConfirmationsQueue, NetworkSettings, block_import}; diff --git a/rpc/src/v1/tests/mocked/personal.rs b/rpc/src/v1/tests/mocked/personal.rs index 91e1ef0f5..2dd186cca 100644 --- a/rpc/src/v1/tests/mocked/personal.rs +++ b/rpc/src/v1/tests/mocked/personal.rs @@ -19,7 +19,7 @@ use std::str::FromStr; use jsonrpc_core::IoHandler; use util::{U256, Uint, Address}; use ethcore::account_provider::AccountProvider; -use v1::{PersonalClient, Personal}; +use v1::{PersonalClient, PersonalAccountsClient, PersonalAccounts, Personal}; use v1::tests::helpers::TestMinerService; use ethcore::client::TestBlockChainClient; use ethcore::transaction::{Action, Transaction}; @@ -50,10 +50,12 @@ fn setup() -> PersonalTester { let accounts = accounts_provider(); let client = blockchain_client(); let miner = miner_service(); - let personal = PersonalClient::new(&accounts, &client, &miner, false); + let personal = PersonalClient::new(&accounts, &client); + let personal_accounts = PersonalAccountsClient::new(&accounts, &client, &miner, false); let io = IoHandler::new(); io.add_delegate(personal.to_delegate()); + io.add_delegate(personal_accounts.to_delegate()); let tester = PersonalTester { accounts: accounts, diff --git a/rpc/src/v1/traits/mod.rs b/rpc/src/v1/traits/mod.rs index e804c5553..ea0834463 100644 --- a/rpc/src/v1/traits/mod.rs +++ b/rpc/src/v1/traits/mod.rs @@ -30,7 +30,7 @@ pub use self::web3::Web3; pub use self::eth::{Eth, EthFilter}; pub use self::eth_signing::EthSigning; pub use self::net::Net; -pub use self::personal::{Personal, PersonalSigner}; +pub use self::personal::{Personal, PersonalAccounts, PersonalSigner}; pub use self::ethcore::Ethcore; pub use self::ethcore_set::EthcoreSet; pub use self::traces::Traces; diff --git a/rpc/src/v1/traits/personal.rs b/rpc/src/v1/traits/personal.rs index 2114131d4..92a9df6eb 100644 --- a/rpc/src/v1/traits/personal.rs +++ b/rpc/src/v1/traits/personal.rs @@ -18,12 +18,28 @@ use std::sync::Arc; use jsonrpc_core::*; -/// Personal rpc interface. +/// Personal rpc interface. Safe (read-only) functions. pub trait Personal: Sized + Send + Sync + 'static { /// Lists all stored accounts fn accounts(&self, _: Params) -> Result; + /// Returns accounts information. + fn accounts_info(&self, _: Params) -> Result; + + /// Should be used to convert object to io delegate. + fn to_delegate(self) -> IoDelegate { + let mut delegate = IoDelegate::new(Arc::new(self)); + delegate.add_method("personal_listAccounts", Personal::accounts); + delegate.add_method("personal_accountsInfo", Personal::accounts_info); + + delegate + } +} + +/// Personal rpc methods altering stored accounts or their settings. +pub trait PersonalAccounts: Sized + Send + Sync + 'static { + /// Creates new account (it becomes new current unlocked account) /// Param is the password for the account. fn new_account(&self, _: Params) -> Result; @@ -56,31 +72,26 @@ pub trait Personal: Sized + Send + Sync + 'static { /// Set an account's metadata string. fn set_account_meta(&self, _: Params) -> Result; - /// Returns accounts information. - fn accounts_info(&self, _: Params) -> Result; + /// Imports a number of Geth accounts, with the list provided as the argument. + fn import_geth_accounts(&self, _: Params) -> Result; /// Returns the accounts available for importing from Geth. fn geth_accounts(&self, _: Params) -> Result; - /// Imports a number of Geth accounts, with the list provided as the argument. - fn import_geth_accounts(&self, _: Params) -> Result; - /// Should be used to convert object to io delegate. fn to_delegate(self) -> IoDelegate { let mut delegate = IoDelegate::new(Arc::new(self)); - delegate.add_method("personal_listAccounts", Personal::accounts); - delegate.add_method("personal_newAccount", Personal::new_account); - delegate.add_method("personal_newAccountFromPhrase", Personal::new_account_from_phrase); - delegate.add_method("personal_newAccountFromWallet", Personal::new_account_from_wallet); - delegate.add_method("personal_unlockAccount", Personal::unlock_account); - delegate.add_method("personal_testPassword", Personal::test_password); - delegate.add_method("personal_changePassword", Personal::change_password); - delegate.add_method("personal_signAndSendTransaction", Personal::sign_and_send_transaction); - delegate.add_method("personal_setAccountName", Personal::set_account_name); - delegate.add_method("personal_setAccountMeta", Personal::set_account_meta); - delegate.add_method("personal_accountsInfo", Personal::accounts_info); - delegate.add_method("personal_listGethAccounts", Personal::geth_accounts); - delegate.add_method("personal_importGethAccounts", Personal::import_geth_accounts); + delegate.add_method("personal_newAccount", PersonalAccounts::new_account); + delegate.add_method("personal_newAccountFromPhrase", PersonalAccounts::new_account_from_phrase); + delegate.add_method("personal_newAccountFromWallet", PersonalAccounts::new_account_from_wallet); + delegate.add_method("personal_unlockAccount", PersonalAccounts::unlock_account); + delegate.add_method("personal_testPassword", PersonalAccounts::test_password); + delegate.add_method("personal_changePassword", PersonalAccounts::change_password); + delegate.add_method("personal_signAndSendTransaction", PersonalAccounts::sign_and_send_transaction); + delegate.add_method("personal_setAccountName", PersonalAccounts::set_account_name); + delegate.add_method("personal_setAccountMeta", PersonalAccounts::set_account_meta); + delegate.add_method("personal_importGethAccounts", PersonalAccounts::import_geth_accounts); + delegate.add_method("personal_listGethAccounts", PersonalAccounts::geth_accounts); delegate } @@ -108,6 +119,7 @@ pub trait PersonalSigner: Sized + Send + Sync + 'static { delegate.add_method("personal_confirmRequest", PersonalSigner::confirm_request); delegate.add_method("personal_rejectRequest", PersonalSigner::reject_request); delegate.add_method("personal_generateAuthorizationToken", PersonalSigner::generate_token); + delegate } } From ce37b6dcb90c23b17b296f34e5e73b17d31a46e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Thu, 27 Oct 2016 19:27:08 +0200 Subject: [PATCH 28/47] Revert to gas price ordering (#2919) --- ethcore/src/miner/miner.rs | 2 +- parity/cli/mod.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 270a39e48..0a00b79eb 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -98,7 +98,7 @@ impl Default for MinerOptions { tx_gas_limit: !U256::zero(), tx_queue_size: 1024, tx_queue_gas_limit: GasLimit::Auto, - tx_queue_strategy: PrioritizationStrategy::GasFactorAndGasPrice, + tx_queue_strategy: PrioritizationStrategy::GasPriceOnly, pending_set: PendingSet::AlwaysQueue, reseal_min_period: Duration::from_secs(2), work_queue_size: 20, diff --git a/parity/cli/mod.rs b/parity/cli/mod.rs index 68411414f..98e815bd0 100644 --- a/parity/cli/mod.rs +++ b/parity/cli/mod.rs @@ -205,7 +205,7 @@ usage! { or |c: &Config| otry!(c.mining).tx_queue_size.clone(), flag_tx_queue_gas: String = "auto", or |c: &Config| otry!(c.mining).tx_queue_gas.clone(), - flag_tx_queue_strategy: String = "gas_factor", + flag_tx_queue_strategy: String = "gas_price", or |c: &Config| otry!(c.mining).tx_queue_strategy.clone(), flag_remove_solved: bool = false, or |c: &Config| otry!(c.mining).remove_solved.clone(), From 152a551e8b5526a2fb728a1dc98bee23ee9d30e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Thu, 27 Oct 2016 19:28:34 +0200 Subject: [PATCH 29/47] Transaction Queue banning (#2524) * Blacklisting transaction queue * Using blacklisting queue in miner * Restoring todo [ci:skip] * Blacklisting recipients and code * Renaming blacklisting->banning * CLI option for banning. * Fixing submodule commit [ci:skip] * Fixing RPC tests * Additional logging when dropping transactions * whitespace [ci:skip] * Configurable ban duration * Reverting fix for pruning history from config file --- Cargo.lock | 1 + ethcore/Cargo.toml | 7 +- ethcore/src/error.rs | 9 + ethcore/src/lib.rs | 1 + ethcore/src/miner/banning_queue.rs | 331 +++++++++++++++++++++++++++++ ethcore/src/miner/miner.rs | 74 ++++++- ethcore/src/miner/mod.rs | 3 +- parity/cli/config.full.toml | 3 + parity/cli/mod.rs | 15 ++ parity/cli/usage.txt | 12 ++ parity/configuration.rs | 10 +- rpc/src/v1/helpers/errors.rs | 3 + rpc/src/v1/tests/eth.rs | 3 +- 13 files changed, 457 insertions(+), 15 deletions(-) create mode 100644 ethcore/src/miner/banning_queue.rs diff --git a/Cargo.lock b/Cargo.lock index 71684d5c4..6fac719d4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -307,6 +307,7 @@ dependencies = [ "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", "semver 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", + "transient-hashmap 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] diff --git a/ethcore/Cargo.toml b/ethcore/Cargo.toml index a60eccddd..d48e76e94 100644 --- a/ethcore/Cargo.toml +++ b/ethcore/Cargo.toml @@ -24,6 +24,10 @@ rayon = "0.4.2" semver = "0.2" bit-set = "0.4" time = "0.1" +rand = "0.3" +byteorder = "0.5" +transient-hashmap = "0.1" +lru-cache = { git = "https://github.com/contain-rs/lru-cache" } evmjit = { path = "../evmjit", optional = true } clippy = { version = "0.0.96", optional = true} ethash = { path = "../ethash" } @@ -36,10 +40,7 @@ ethstore = { path = "../ethstore" } ethkey = { path = "../ethkey" } ethcore-ipc-nano = { path = "../ipc/nano" } rlp = { path = "../util/rlp" } -rand = "0.3" -lru-cache = { git = "https://github.com/contain-rs/lru-cache" } ethcore-bloom-journal = { path = "../util/bloom" } -byteorder = "0.5" [dependencies.hyper] git = "https://github.com/ethcore/hyper" diff --git a/ethcore/src/error.rs b/ethcore/src/error.rs index e87aa231f..40f4c6d28 100644 --- a/ethcore/src/error.rs +++ b/ethcore/src/error.rs @@ -63,6 +63,12 @@ pub enum TransactionError { }, /// Transaction's gas limit (aka gas) is invalid. InvalidGasLimit(OutOfBounds), + /// Transaction sender is banned. + SenderBanned, + /// Transaction receipient is banned. + RecipientBanned, + /// Contract creation code is banned. + CodeBanned, } impl fmt::Display for TransactionError { @@ -81,6 +87,9 @@ impl fmt::Display for TransactionError { GasLimitExceeded { limit, got } => format!("Gas limit exceeded. Limit={}, Given={}", limit, got), InvalidGasLimit(ref err) => format!("Invalid gas limit. {}", err), + SenderBanned => "Sender is temporarily banned.".into(), + RecipientBanned => "Recipient is temporarily banned.".into(), + CodeBanned => "Contract code is temporarily banned.".into(), }; f.write_fmt(format_args!("Transaction error ({})", msg)) diff --git a/ethcore/src/lib.rs b/ethcore/src/lib.rs index 9985dc58e..c7f40418c 100644 --- a/ethcore/src/lib.rs +++ b/ethcore/src/lib.rs @@ -101,6 +101,7 @@ extern crate bit_set; extern crate rlp; extern crate ethcore_bloom_journal as bloom_journal; extern crate byteorder; +extern crate transient_hashmap; #[macro_use] extern crate log; diff --git a/ethcore/src/miner/banning_queue.rs b/ethcore/src/miner/banning_queue.rs new file mode 100644 index 000000000..0ad99b008 --- /dev/null +++ b/ethcore/src/miner/banning_queue.rs @@ -0,0 +1,331 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity 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. + +// Parity 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 Parity. If not, see . + +//! Banning Queue +//! Transacton Queue wrapper maintaining additional list of banned senders and contract hashes. + +use std::time::Duration; +use std::ops::{Deref, DerefMut}; +use std::cell::Cell; +use transaction::{SignedTransaction, Action}; +use transient_hashmap::TransientHashMap; +use miner::{TransactionQueue, TransactionImportResult, TransactionOrigin, AccountDetails}; +use error::{Error, TransactionError}; +use util::{Uint, U256, H256, Address, Hashable}; + +type Count = u16; + +/// Auto-Banning threshold +pub enum Threshold { + /// Should ban after given number of misbehaves reported. + BanAfter(Count), + /// Should never ban anything + NeverBan +} + +impl Default for Threshold { + fn default() -> Self { + Threshold::NeverBan + } +} + +/// Transaction queue with banlist. +pub struct BanningTransactionQueue { + queue: TransactionQueue, + ban_threshold: Threshold, + senders_bans: TransientHashMap>, + recipients_bans: TransientHashMap>, + codes_bans: TransientHashMap>, +} + +impl BanningTransactionQueue { + /// Creates new banlisting transaction queue + pub fn new(queue: TransactionQueue, ban_threshold: Threshold, ban_lifetime: Duration) -> Self { + let ban_lifetime_sec = ban_lifetime.as_secs(); + assert!(ban_lifetime_sec > 0, "Lifetime has to be specified in seconds."); + BanningTransactionQueue { + queue: queue, + ban_threshold: ban_threshold, + senders_bans: TransientHashMap::new(ban_lifetime_sec), + recipients_bans: TransientHashMap::new(ban_lifetime_sec), + codes_bans: TransientHashMap::new(ban_lifetime_sec), + } + } + + /// Borrows internal queue. + /// NOTE: you can insert transactions to the queue even + /// if they would be rejected because of ban otherwise. + /// But probably you shouldn't. + pub fn queue(&mut self) -> &mut TransactionQueue { + &mut self.queue + } + + /// Add to the queue taking bans into consideration. + /// May reject transaction because of the banlist. + pub fn add_with_banlist( + &mut self, + transaction: SignedTransaction, + account_details: &F, + ) -> Result where F: Fn(&Address) -> AccountDetails { + if let Threshold::BanAfter(threshold) = self.ban_threshold { + // NOTE In all checks use direct query to avoid increasing ban timeout. + + // Check sender + if let Ok(sender) = transaction.sender() { + let count = self.senders_bans.direct().get(&sender).map(|v| v.get()).unwrap_or(0); + if count > threshold { + debug!(target: "txqueue", "Ignoring transaction {:?} because sender is banned.", transaction.hash()); + return Err(Error::Transaction(TransactionError::SenderBanned)); + } + } + + // Check recipient + if let Action::Call(recipient) = transaction.action { + let count = self.recipients_bans.direct().get(&recipient).map(|v| v.get()).unwrap_or(0); + if count > threshold { + debug!(target: "txqueue", "Ignoring transaction {:?} because recipient is banned.", transaction.hash()); + return Err(Error::Transaction(TransactionError::RecipientBanned)); + } + } + + // Check code + if let Action::Create = transaction.action { + let code_hash = transaction.data.sha3(); + let count = self.codes_bans.direct().get(&code_hash).map(|v| v.get()).unwrap_or(0); + if count > threshold { + debug!(target: "txqueue", "Ignoring transaction {:?} because code is banned.", transaction.hash()); + return Err(Error::Transaction(TransactionError::CodeBanned)); + } + } + } + self.queue.add(transaction, account_details, TransactionOrigin::External) + } + + /// Ban transaction with given hash. + /// Transaction has to be in the queue. + /// + /// Bans sender and recipient/code and returns `true` when any ban has reached threshold. + pub fn ban_transaction(&mut self, hash: &H256) -> bool { + let transaction = self.queue.find(hash); + match transaction { + Some(transaction) => { + let sender = transaction.sender().expect("Transaction is in queue, so the sender is already validated; qed"); + // Ban sender + let sender_banned = self.ban_sender(sender); + // Ban recipient and codehash + let is_banned = sender_banned || match transaction.action { + Action::Call(recipient) => { + self.ban_recipient(recipient) + }, + Action::Create => { + self.ban_codehash(transaction.data.sha3()) + }, + }; + is_banned + }, + None => false, + } + } + + /// Ban given sender. + /// If bans threshold is reached all subsequent transactions from this sender will be rejected. + /// Reaching bans threshold also removes all existsing transaction from this sender that are already in the + /// queue. + fn ban_sender(&mut self, address: Address) -> bool { + let count = { + let mut count = self.senders_bans.entry(address).or_insert_with(|| Cell::new(0)); + *count.get_mut() = count.get().saturating_add(1); + count.get() + }; + match self.ban_threshold { + Threshold::BanAfter(threshold) if count > threshold => { + // Banlist the sender. + // Remove all transactions from the queue. + self.remove_all(address, !U256::zero()); + true + }, + _ => false + } + } + + /// Ban given recipient. + /// If bans threshold is reached all subsequent transactions to this address will be rejected. + /// Returns true if bans threshold has been reached. + fn ban_recipient(&mut self, address: Address) -> bool { + let count = { + let mut count = self.recipients_bans.entry(address).or_insert_with(|| Cell::new(0)); + *count.get_mut() = count.get().saturating_add(1); + count.get() + }; + match self.ban_threshold { + // TODO [ToDr] Consider removing other transactions to the same recipient from the queue? + Threshold::BanAfter(threshold) if count > threshold => true, + _ => false + } + } + + + /// Ban given codehash. + /// If bans threshold is reached all subsequent transactions to contracts with this codehash will be rejected. + /// Returns true if bans threshold has been reached. + fn ban_codehash(&mut self, code_hash: H256) -> bool { + let mut count = self.codes_bans.entry(code_hash).or_insert_with(|| Cell::new(0)); + *count.get_mut() = count.get().saturating_add(1); + + match self.ban_threshold { + // TODO [ToDr] Consider removing other transactions with the same code from the queue? + Threshold::BanAfter(threshold) if count.get() > threshold => true, + _ => false, + } + } +} + +impl Deref for BanningTransactionQueue { + type Target = TransactionQueue; + + fn deref(&self) -> &Self::Target { + &self.queue + } +} +impl DerefMut for BanningTransactionQueue { + fn deref_mut(&mut self) -> &mut Self::Target { + self.queue() + } +} + +#[cfg(test)] +mod tests { + use std::time::Duration; + use super::{BanningTransactionQueue, Threshold}; + use ethkey::{Random, Generator}; + use transaction::{Transaction, SignedTransaction, Action}; + use error::{Error, TransactionError}; + use client::TransactionImportResult; + use miner::{TransactionQueue, TransactionOrigin, AccountDetails}; + use util::{Uint, U256, Address, FromHex, Hashable}; + + fn queue() -> BanningTransactionQueue { + BanningTransactionQueue::new(TransactionQueue::default(), Threshold::BanAfter(1), Duration::from_secs(180)) + } + + fn default_account_details(_address: &Address) -> AccountDetails { + AccountDetails { + nonce: U256::zero(), + balance: !U256::zero(), + } + } + + fn transaction(action: Action) -> SignedTransaction { + let keypair = Random.generate().unwrap(); + Transaction { + action: action, + value: U256::from(100), + data: "3331600055".from_hex().unwrap(), + gas: U256::from(100_000), + gas_price: U256::from(10), + nonce: U256::from(0), + }.sign(keypair.secret()) + } + + fn unwrap_err(res: Result) -> TransactionError { + match res { + Err(Error::Transaction(e)) => e, + Ok(x) => panic!("Expected error, got: Ok({:?})", x), + Err(e) => panic!("Unexpected error type returned by queue: {:?}", e), + } + } + + #[test] + fn should_allow_to_borrow_the_queue() { + // given + let tx = transaction(Action::Create); + let mut txq = queue(); + + // when + txq.queue().add(tx, &default_account_details, TransactionOrigin::External).unwrap(); + + // then + // should also deref to queue + assert_eq!(txq.status().pending, 1); + } + + #[test] + fn should_not_accept_transactions_from_banned_sender() { + // given + let tx = transaction(Action::Create); + let mut txq = queue(); + // Banlist once (threshold not reached) + let banlist1 = txq.ban_sender(tx.sender().unwrap()); + assert!(!banlist1, "Threshold not reached yet."); + // Insert once + let import1 = txq.add_with_banlist(tx.clone(), &default_account_details).unwrap(); + assert_eq!(import1, TransactionImportResult::Current); + + // when + let banlist2 = txq.ban_sender(tx.sender().unwrap()); + let import2 = txq.add_with_banlist(tx.clone(), &default_account_details); + + // then + assert!(banlist2, "Threshold should be reached - banned."); + assert_eq!(unwrap_err(import2), TransactionError::SenderBanned); + // Should also remove transacion from the queue + assert_eq!(txq.find(&tx.hash()), None); + } + + #[test] + fn should_not_accept_transactions_to_banned_recipient() { + // given + let recipient = Address::default(); + let tx = transaction(Action::Call(recipient)); + let mut txq = queue(); + // Banlist once (threshold not reached) + let banlist1 = txq.ban_recipient(recipient); + assert!(!banlist1, "Threshold not reached yet."); + // Insert once + let import1 = txq.add_with_banlist(tx.clone(), &default_account_details).unwrap(); + assert_eq!(import1, TransactionImportResult::Current); + + // when + let banlist2 = txq.ban_recipient(recipient); + let import2 = txq.add_with_banlist(tx.clone(), &default_account_details); + + // then + assert!(banlist2, "Threshold should be reached - banned."); + assert_eq!(unwrap_err(import2), TransactionError::RecipientBanned); + } + + #[test] + fn should_not_accept_transactions_with_banned_code() { + // given + let tx = transaction(Action::Create); + let codehash = tx.data.sha3(); + let mut txq = queue(); + // Banlist once (threshold not reached) + let banlist1 = txq.ban_codehash(codehash); + assert!(!banlist1, "Threshold not reached yet."); + // Insert once + let import1 = txq.add_with_banlist(tx.clone(), &default_account_details).unwrap(); + assert_eq!(import1, TransactionImportResult::Current); + + // when + let banlist2 = txq.ban_codehash(codehash); + let import2 = txq.add_with_banlist(tx.clone(), &default_account_details); + + // then + assert!(banlist2, "Threshold should be reached - banned."); + assert_eq!(unwrap_err(import2), TransactionError::CodeBanned); + } +} diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 0a00b79eb..47464b36f 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -31,6 +31,7 @@ use receipt::{Receipt, RichReceipt}; use spec::Spec; use engines::Engine; use miner::{MinerService, MinerStatus, TransactionQueue, PrioritizationStrategy, AccountDetails, TransactionOrigin}; +use miner::banning_queue::{BanningTransactionQueue, Threshold}; use miner::work_notify::WorkPoster; use client::TransactionImportResult; use miner::price_info::PriceInfo; @@ -59,6 +60,22 @@ pub enum GasLimit { Fixed(U256), } +/// Transaction queue banning settings. +#[derive(Debug, PartialEq, Clone)] +pub enum Banning { + /// Banning in transaction queue is disabled + Disabled, + /// Banning in transaction queue is enabled + Enabled { + /// Upper limit of transaction processing time before banning. + offend_threshold: Duration, + /// Number of similar offending transactions before banning. + min_offends: u16, + /// Number of seconds the offender is banned for. + ban_duration: Duration, + }, +} + /// Configures the behaviour of the miner. #[derive(Debug, PartialEq)] pub struct MinerOptions { @@ -86,6 +103,8 @@ pub struct MinerOptions { pub enable_resubmission: bool, /// Global gas limit for all transaction in the queue except for local and retracted. pub tx_queue_gas_limit: GasLimit, + /// Banning settings + pub tx_queue_banning: Banning, } impl Default for MinerOptions { @@ -103,6 +122,7 @@ impl Default for MinerOptions { reseal_min_period: Duration::from_secs(2), work_queue_size: 20, enable_resubmission: true, + tx_queue_banning: Banning::Disabled, } } } @@ -186,7 +206,7 @@ struct SealingWork { /// Handles preparing work for "work sealing" or seals "internally" if Engine does not require work. pub struct Miner { // NOTE [ToDr] When locking always lock in this order! - transaction_queue: Arc>, + transaction_queue: Arc>, sealing_work: Mutex, next_allowed_reseal: Mutex, sealing_block_last_request: Mutex, @@ -215,11 +235,18 @@ impl Miner { GasLimit::Fixed(ref limit) => *limit, _ => !U256::zero(), }; - let txq = Arc::new(Mutex::new(TransactionQueue::with_limits( - options.tx_queue_strategy, options.tx_queue_size, gas_limit, options.tx_gas_limit - ))); + + let txq = TransactionQueue::with_limits(options.tx_queue_strategy, options.tx_queue_size, gas_limit, options.tx_gas_limit); + let txq = match options.tx_queue_banning { + Banning::Disabled => BanningTransactionQueue::new(txq, Threshold::NeverBan, Duration::from_secs(180)), + Banning::Enabled { ban_duration, min_offends, .. } => BanningTransactionQueue::new( + txq, + Threshold::BanAfter(min_offends), + ban_duration, + ), + }; Miner { - transaction_queue: txq, + transaction_queue: Arc::new(Mutex::new(txq)), next_allowed_reseal: Mutex::new(Instant::now()), sealing_block_last_request: Mutex::new(0), sealing_work: Mutex::new(SealingWork{ @@ -318,10 +345,31 @@ impl Miner { let mut invalid_transactions = HashSet::new(); let mut transactions_to_penalize = HashSet::new(); let block_number = open_block.block().fields().header.number(); - // TODO: push new uncles, too. + + // TODO Push new uncles too. for tx in transactions { let hash = tx.hash(); - match open_block.push_transaction(tx, None) { + let start = Instant::now(); + let result = open_block.push_transaction(tx, None); + let took = start.elapsed(); + + // Check for heavy transactions + match self.options.tx_queue_banning { + Banning::Enabled { ref offend_threshold, .. } if &took > offend_threshold => { + match self.transaction_queue.lock().ban_transaction(&hash) { + true => { + warn!(target: "miner", "Detected heavy transaction. Banning the sender and recipient/code."); + }, + false => { + transactions_to_penalize.insert(hash); + debug!(target: "miner", "Detected heavy transaction. Penalizing sender.") + } + } + }, + _ => {}, + } + + match result { Err(Error::Execution(ExecutionError::BlockGasLimitReached { gas_limit, gas_used, gas })) => { debug!(target: "miner", "Skipping adding transaction to block because of gas limit: {:?} (limit: {:?}, used: {:?}, gas: {:?})", hash, gas_limit, gas_used, gas); @@ -506,7 +554,7 @@ impl Miner { prepare_new } - fn add_transactions_to_queue(&self, chain: &MiningBlockChainClient, transactions: Vec, origin: TransactionOrigin, transaction_queue: &mut TransactionQueue) -> + fn add_transactions_to_queue(&self, chain: &MiningBlockChainClient, transactions: Vec, origin: TransactionOrigin, transaction_queue: &mut BanningTransactionQueue) -> Vec> { let fetch_account = |a: &Address| AccountDetails { @@ -515,7 +563,14 @@ impl Miner { }; transactions.into_iter() - .map(|tx| transaction_queue.add(tx, &fetch_account, origin)) + .map(|tx| match origin { + TransactionOrigin::Local | TransactionOrigin::RetractedBlock => { + transaction_queue.add(tx, &fetch_account, origin) + }, + TransactionOrigin::External => { + transaction_queue.add_with_banlist(tx, &fetch_account) + } + }) .collect() } @@ -1099,6 +1154,7 @@ mod tests { pending_set: PendingSet::AlwaysSealing, work_queue_size: 5, enable_resubmission: true, + tx_queue_banning: Banning::Disabled, }, GasPricer::new_fixed(0u64.into()), &Spec::new_test(), diff --git a/ethcore/src/miner/mod.rs b/ethcore/src/miner/mod.rs index 5fe8dbf44..145d790dd 100644 --- a/ethcore/src/miner/mod.rs +++ b/ethcore/src/miner/mod.rs @@ -44,11 +44,12 @@ mod miner; mod external; mod transaction_queue; +mod banning_queue; mod work_notify; mod price_info; pub use self::transaction_queue::{TransactionQueue, PrioritizationStrategy, AccountDetails, TransactionOrigin}; -pub use self::miner::{Miner, MinerOptions, PendingSet, GasPricer, GasPriceCalibratorOptions, GasLimit}; +pub use self::miner::{Miner, MinerOptions, Banning, PendingSet, GasPricer, GasPriceCalibratorOptions, GasLimit}; pub use self::external::{ExternalMiner, ExternalMinerService}; pub use client::TransactionImportResult; diff --git a/parity/cli/config.full.toml b/parity/cli/config.full.toml index 4d1d48025..84e44ee77 100644 --- a/parity/cli/config.full.toml +++ b/parity/cli/config.full.toml @@ -74,7 +74,10 @@ gas_cap = "6283184" tx_queue_size = 1024 tx_queue_gas = "auto" tx_queue_strategy = "gas_factor" +tx_queue_ban_count = 1 +tx_queue_ban_time = 180 #s tx_gas_limit = "6283184" +tx_time_limit = 100 #ms extra_data = "Parity" remove_solved = false notify_work = ["http://localhost:3001"] diff --git a/parity/cli/mod.rs b/parity/cli/mod.rs index 98e815bd0..d13d791ad 100644 --- a/parity/cli/mod.rs +++ b/parity/cli/mod.rs @@ -187,6 +187,8 @@ usage! { or |c: &Config| otry!(c.mining).work_queue_size.clone(), flag_tx_gas_limit: Option = None, or |c: &Config| otry!(c.mining).tx_gas_limit.clone().map(Some), + flag_tx_time_limit: Option = None, + or |c: &Config| otry!(c.mining).tx_time_limit.clone().map(Some), flag_relay_set: String = "cheap", or |c: &Config| otry!(c.mining).relay_set.clone(), flag_usd_per_tx: String = "0", @@ -207,6 +209,10 @@ usage! { or |c: &Config| otry!(c.mining).tx_queue_gas.clone(), flag_tx_queue_strategy: String = "gas_price", or |c: &Config| otry!(c.mining).tx_queue_strategy.clone(), + flag_tx_queue_ban_count: u16 = 1u16, + or |c: &Config| otry!(c.mining).tx_queue_ban_count.clone(), + flag_tx_queue_ban_time: u16 = 180u16, + or |c: &Config| otry!(c.mining).tx_queue_ban_time.clone(), flag_remove_solved: bool = false, or |c: &Config| otry!(c.mining).remove_solved.clone(), flag_notify_work: Option = None, @@ -361,6 +367,7 @@ struct Mining { reseal_min_period: Option, work_queue_size: Option, tx_gas_limit: Option, + tx_time_limit: Option, relay_set: Option, usd_per_tx: Option, usd_per_eth: Option, @@ -371,6 +378,8 @@ struct Mining { tx_queue_size: Option, tx_queue_gas: Option, tx_queue_strategy: Option, + tx_queue_ban_count: Option, + tx_queue_ban_time: Option, remove_solved: Option, notify_work: Option>, } @@ -558,6 +567,7 @@ mod tests { flag_reseal_min_period: 4000u64, flag_work_queue_size: 20usize, flag_tx_gas_limit: Some("6283184".into()), + flag_tx_time_limit: Some(100u64), flag_relay_set: "cheap".into(), flag_usd_per_tx: "0".into(), flag_usd_per_eth: "auto".into(), @@ -568,6 +578,8 @@ mod tests { flag_tx_queue_size: 1024usize, flag_tx_queue_gas: "auto".into(), flag_tx_queue_strategy: "gas_factor".into(), + flag_tx_queue_ban_count: 1u16, + flag_tx_queue_ban_time: 180u16, flag_remove_solved: false, flag_notify_work: Some("http://localhost:3001".into()), @@ -727,7 +739,10 @@ mod tests { tx_queue_size: Some(1024), tx_queue_gas: Some("auto".into()), tx_queue_strategy: None, + tx_queue_ban_count: None, + tx_queue_ban_time: None, tx_gas_limit: None, + tx_time_limit: None, extra_data: None, remove_solved: None, notify_work: None, diff --git a/parity/cli/usage.txt b/parity/cli/usage.txt index d96d875cf..bf7e82561 100644 --- a/parity/cli/usage.txt +++ b/parity/cli/usage.txt @@ -166,6 +166,11 @@ Sealing/Mining Options: --tx-gas-limit GAS Apply a limit of GAS as the maximum amount of gas a single transaction may have for it to be mined. (default: {flag_tx_gas_limit:?}) + --tx-time-limit MS Maximal time for processing single transaction. + If enabled senders/recipients/code of transactions + offending the limit will be banned from being included + in transaction queue for 180 seconds. + (default: {flag_tx_time_limit:?}) --relay-set SET Set of transactions to relay. SET may be: cheap - Relay any transaction in the queue (this may include invalid transactions); @@ -203,6 +208,13 @@ Sealing/Mining Options: gas_price - Prioritize txs with high gas price; gas_factor - Prioritize txs using gas price and gas limit ratio (default: {flag_tx_queue_strategy}). + --tx-queue-ban-count C Number of times maximal time for execution (--tx-time-limit) + can be exceeded before banning sender/recipient/code. + (default: {flag_tx_queue_ban_count}) + --tx-queue-ban-time SEC Banning time (in seconds) for offenders of specified + execution time limit. Also number of offending actions + have to reach the threshold within that time. + (default: {flag_tx_queue_ban_time} seconds) --remove-solved Move solved blocks from the work package queue instead of cloning them. This gives a slightly faster import speed, but means that extra solutions diff --git a/parity/configuration.rs b/parity/configuration.rs index 5680e6110..1972b9f1e 100644 --- a/parity/configuration.rs +++ b/parity/configuration.rs @@ -24,7 +24,7 @@ use util::{Hashable, U256, Uint, Bytes, version_data, Secret, Address}; use util::log::Colour; use ethsync::{NetworkConfiguration, is_valid_node_url, AllowIP}; use ethcore::client::{VMType, Mode}; -use ethcore::miner::MinerOptions; +use ethcore::miner::{MinerOptions, Banning}; use rpc::{IpcConfiguration, HttpConfiguration}; use ethcore_rpc::NetworkSettings; @@ -387,6 +387,14 @@ impl Configuration { reseal_min_period: Duration::from_millis(self.args.flag_reseal_min_period), work_queue_size: self.args.flag_work_queue_size, enable_resubmission: !self.args.flag_remove_solved, + tx_queue_banning: match self.args.flag_tx_time_limit { + Some(limit) => Banning::Enabled { + min_offends: self.args.flag_tx_queue_ban_count, + offend_threshold: Duration::from_millis(limit), + ban_duration: Duration::from_secs(self.args.flag_tx_queue_ban_time as u64), + }, + None => Banning::Disabled, + } }; Ok(options) diff --git a/rpc/src/v1/helpers/errors.rs b/rpc/src/v1/helpers/errors.rs index 572adca3a..3f5dda2d3 100644 --- a/rpc/src/v1/helpers/errors.rs +++ b/rpc/src/v1/helpers/errors.rs @@ -231,6 +231,9 @@ pub fn from_transaction_error(error: EthcoreError) -> Error { format!("Transaction cost exceeds current gas limit. Limit: {}, got: {}. Try decreasing supplied gas.", limit, got) }, InvalidGasLimit(_) => "Supplied gas is beyond limit.".into(), + SenderBanned => "Sender is banned in local queue.".into(), + RecipientBanned => "Recipient is banned in local queue.".into(), + CodeBanned => "Code is banned in local queue.".into(), }; Error { code: ErrorCode::ServerError(codes::TRANSACTION_ERROR), diff --git a/rpc/src/v1/tests/eth.rs b/rpc/src/v1/tests/eth.rs index 5d7481551..d556d11ef 100644 --- a/rpc/src/v1/tests/eth.rs +++ b/rpc/src/v1/tests/eth.rs @@ -24,7 +24,7 @@ use ethcore::spec::{Genesis, Spec}; use ethcore::block::Block; use ethcore::views::BlockView; use ethcore::ethereum; -use ethcore::miner::{MinerOptions, GasPricer, MinerService, ExternalMiner, Miner, PendingSet, PrioritizationStrategy, GasLimit}; +use ethcore::miner::{MinerOptions, Banning, GasPricer, MinerService, ExternalMiner, Miner, PendingSet, PrioritizationStrategy, GasLimit}; use ethcore::account_provider::AccountProvider; use devtools::RandomTempPath; use util::Hashable; @@ -61,6 +61,7 @@ fn miner_service(spec: &Spec, accounts: Arc) -> Arc { tx_gas_limit: !U256::zero(), tx_queue_strategy: PrioritizationStrategy::GasPriceOnly, tx_queue_gas_limit: GasLimit::None, + tx_queue_banning: Banning::Disabled, pending_set: PendingSet::SealingOrElseQueue, reseal_min_period: Duration::from_secs(0), work_queue_size: 50, From dedc4d5cfc03c84de78686e8cd5d3ef2d1042ba5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Thu, 27 Oct 2016 19:29:23 +0200 Subject: [PATCH 30/47] Get rid of duplicated code (#2915) --- ethcore/src/evm/instructions.rs | 8 +- ethcore/src/evm/interpreter/gasometer.rs | 141 ++++++++++++++--------- ethcore/src/evm/interpreter/mod.rs | 14 +-- evmbin/Cargo.lock | 38 +++++- evmbin/src/ext.rs | 2 +- 5 files changed, 130 insertions(+), 73 deletions(-) diff --git a/ethcore/src/evm/instructions.rs b/ethcore/src/evm/instructions.rs index 62cfe77d6..e609bf542 100644 --- a/ethcore/src/evm/instructions.rs +++ b/ethcore/src/evm/instructions.rs @@ -173,7 +173,7 @@ lazy_static! { arr[SIGNEXTEND as usize] = InstructionInfo::new("SIGNEXTEND", 0, 2, 1, false, GasPriceTier::Low); arr[SHA3 as usize] = InstructionInfo::new("SHA3", 0, 2, 1, false, GasPriceTier::Special); arr[ADDRESS as usize] = InstructionInfo::new("ADDRESS", 0, 0, 1, false, GasPriceTier::Base); - arr[BALANCE as usize] = InstructionInfo::new("BALANCE", 0, 1, 1, false, GasPriceTier::Ext); + arr[BALANCE as usize] = InstructionInfo::new("BALANCE", 0, 1, 1, false, GasPriceTier::Special); arr[ORIGIN as usize] = InstructionInfo::new("ORIGIN", 0, 0, 1, false, GasPriceTier::Base); arr[CALLER as usize] = InstructionInfo::new("CALLER", 0, 0, 1, false, GasPriceTier::Base); arr[CALLVALUE as usize] = InstructionInfo::new("CALLVALUE", 0, 0, 1, false, GasPriceTier::Base); @@ -183,8 +183,8 @@ lazy_static! { arr[CODESIZE as usize] = InstructionInfo::new("CODESIZE", 0, 0, 1, false, GasPriceTier::Base); arr[CODECOPY as usize] = InstructionInfo::new("CODECOPY", 0, 3, 0, true, GasPriceTier::VeryLow); arr[GASPRICE as usize] = InstructionInfo::new("GASPRICE", 0, 0, 1, false, GasPriceTier::Base); - arr[EXTCODESIZE as usize] = InstructionInfo::new("EXTCODESIZE", 0, 1, 1, false, GasPriceTier::Ext); - arr[EXTCODECOPY as usize] = InstructionInfo::new("EXTCODECOPY", 0, 4, 0, true, GasPriceTier::Ext); + arr[EXTCODESIZE as usize] = InstructionInfo::new("EXTCODESIZE", 0, 1, 1, false, GasPriceTier::Special); + arr[EXTCODECOPY as usize] = InstructionInfo::new("EXTCODECOPY", 0, 4, 0, true, GasPriceTier::Special); arr[BLOCKHASH as usize] = InstructionInfo::new("BLOCKHASH", 0, 1, 1, false, GasPriceTier::Ext); arr[COINBASE as usize] = InstructionInfo::new("COINBASE", 0, 0, 1, false, GasPriceTier::Base); arr[TIMESTAMP as usize] = InstructionInfo::new("TIMESTAMP", 0, 0, 1, false, GasPriceTier::Base); @@ -277,7 +277,7 @@ lazy_static! { arr[CALLCODE as usize] = InstructionInfo::new("CALLCODE", 0, 7, 1, true, GasPriceTier::Special); arr[RETURN as usize] = InstructionInfo::new("RETURN", 0, 2, 0, true, GasPriceTier::Zero); arr[DELEGATECALL as usize] = InstructionInfo::new("DELEGATECALL", 0, 6, 1, true, GasPriceTier::Special); - arr[SUICIDE as usize] = InstructionInfo::new("SUICIDE", 0, 1, 0, true, GasPriceTier::Zero); + arr[SUICIDE as usize] = InstructionInfo::new("SUICIDE", 0, 1, 0, true, GasPriceTier::Special); arr }; } diff --git a/ethcore/src/evm/interpreter/gasometer.rs b/ethcore/src/evm/interpreter/gasometer.rs index 3fde3f664..a2b940655 100644 --- a/ethcore/src/evm/interpreter/gasometer.rs +++ b/ethcore/src/evm/interpreter/gasometer.rs @@ -30,12 +30,20 @@ macro_rules! overflowing { } #[cfg_attr(feature="dev", allow(enum_variant_names))] -enum InstructionCost { +enum Request { Gas(Cost), - GasMem(Cost, Cost, Option), + GasMem(Cost, Cost), + GasMemProvide(Cost, Cost, Option), GasMemCopy(Cost, Cost, Cost) } +pub struct InstructionRequirements { + pub gas_cost: Cost, + pub provide_gas: Option, + pub memory_total_gas: Cost, + pub memory_required_size: usize, +} + pub struct Gasometer { pub current_gas: Gas, pub current_mem_gas: Gas, @@ -59,11 +67,19 @@ impl Gasometer { /// How much gas is provided to a CALL/CREATE, given that we need to deduct `needed` for this operation /// and that we `requested` some. - pub fn gas_provided(&self, schedule: &Schedule, needed: Gas, requested: Option>) -> evm::Result { + pub fn gas_provided(&self, schedule: &Schedule, needed: Gas, requested: Option) -> evm::Result { + // Try converting requested gas to `Gas` (`U256/u64`) + // but in EIP150 even if we request more we should never fail from OOG + let requested = requested.map(Gas::from_u256); + match schedule.sub_gas_cap_divisor { Some(cap_divisor) if self.current_gas >= needed => { let gas_remaining = self.current_gas - needed; - let max_gas_provided = gas_remaining - gas_remaining / Gas::from(cap_divisor); + let max_gas_provided = match cap_divisor { + 64 => gas_remaining - (gas_remaining >> 6), + cap_divisor => gas_remaining - gas_remaining / Gas::from(cap_divisor), + }; + if let Some(Ok(r)) = requested { Ok(min(r, max_gas_provided)) } else { @@ -78,7 +94,7 @@ impl Gasometer { } else { Ok(0.into()) } - } + }, } } @@ -88,21 +104,21 @@ impl Gasometer { /// We guarantee that the final element of the returned tuple (`provided`) will be `Some` /// iff the `instruction` is one of `CREATE`, or any of the `CALL` variants. In this case, /// it will be the amount of gas that the current context provides to the child context. - pub fn get_gas_cost_mem( + pub fn requirements( &mut self, ext: &evm::Ext, instruction: Instruction, info: &InstructionInfo, stack: &Stack, current_mem_size: usize, - ) -> evm::Result<(Gas, Gas, usize, Option)> { + ) -> evm::Result> { let schedule = ext.schedule(); let tier = instructions::get_tier_idx(info.tier); let default_gas = Gas::from(schedule.tier_step_gas[tier]); let cost = match instruction { instructions::JUMPDEST => { - InstructionCost::Gas(Gas::from(1)) + Request::Gas(Gas::from(1)) }, instructions::SSTORE => { let address = H256::from(stack.peek(0)); @@ -116,16 +132,16 @@ impl Gasometer { // !is_zero(&val) && is_zero(newval) schedule.sstore_reset_gas }; - InstructionCost::Gas(Gas::from(gas)) + Request::Gas(Gas::from(gas)) }, instructions::SLOAD => { - InstructionCost::Gas(Gas::from(schedule.sload_gas)) + Request::Gas(Gas::from(schedule.sload_gas)) }, instructions::BALANCE => { - InstructionCost::Gas(Gas::from(schedule.balance_gas)) + Request::Gas(Gas::from(schedule.balance_gas)) }, instructions::EXTCODESIZE => { - InstructionCost::Gas(Gas::from(schedule.extcodesize_gas)) + Request::Gas(Gas::from(schedule.extcodesize_gas)) }, instructions::SUICIDE => { let mut gas = Gas::from(schedule.suicide_gas); @@ -135,28 +151,28 @@ impl Gasometer { gas = overflowing!(gas.overflow_add(schedule.suicide_to_new_account_cost.into())); } - InstructionCost::Gas(gas) + Request::Gas(gas) }, instructions::MSTORE | instructions::MLOAD => { - InstructionCost::GasMem(default_gas, try!(mem_needed_const(stack.peek(0), 32)), None) + Request::GasMem(default_gas, try!(mem_needed_const(stack.peek(0), 32))) }, instructions::MSTORE8 => { - InstructionCost::GasMem(default_gas, try!(mem_needed_const(stack.peek(0), 1)), None) + Request::GasMem(default_gas, try!(mem_needed_const(stack.peek(0), 1))) }, instructions::RETURN => { - InstructionCost::GasMem(default_gas, try!(mem_needed(stack.peek(0), stack.peek(1))), None) + Request::GasMem(default_gas, try!(mem_needed(stack.peek(0), stack.peek(1)))) }, instructions::SHA3 => { let w = overflowing!(add_gas_usize(try!(Gas::from_u256(*stack.peek(1))), 31)); let words = w >> 5; let gas = Gas::from(schedule.sha3_gas) + (Gas::from(schedule.sha3_word_gas) * words); - InstructionCost::GasMem(gas, try!(mem_needed(stack.peek(0), stack.peek(1))), None) + Request::GasMem(gas, try!(mem_needed(stack.peek(0), stack.peek(1)))) }, instructions::CALLDATACOPY | instructions::CODECOPY => { - InstructionCost::GasMemCopy(default_gas, try!(mem_needed(stack.peek(0), stack.peek(2))), try!(Gas::from_u256(*stack.peek(2)))) + Request::GasMemCopy(default_gas, try!(mem_needed(stack.peek(0), stack.peek(2))), try!(Gas::from_u256(*stack.peek(2)))) }, instructions::EXTCODECOPY => { - InstructionCost::GasMemCopy(schedule.extcodecopy_base_gas.into(), try!(mem_needed(stack.peek(1), stack.peek(3))), try!(Gas::from_u256(*stack.peek(3)))) + Request::GasMemCopy(schedule.extcodecopy_base_gas.into(), try!(mem_needed(stack.peek(1), stack.peek(3))), try!(Gas::from_u256(*stack.peek(3)))) }, instructions::LOG0...instructions::LOG4 => { let no_of_topics = instructions::get_log_topics(instruction); @@ -164,7 +180,7 @@ impl Gasometer { let data_gas = overflowing!(try!(Gas::from_u256(*stack.peek(1))).overflow_mul(Gas::from(schedule.log_data_gas))); let gas = overflowing!(data_gas.overflow_add(Gas::from(log_gas))); - InstructionCost::GasMem(gas, try!(mem_needed(stack.peek(0), stack.peek(1))), None) + Request::GasMem(gas, try!(mem_needed(stack.peek(0), stack.peek(1)))) }, instructions::CALL | instructions::CALLCODE => { let mut gas = Gas::from(schedule.call_gas); @@ -183,70 +199,82 @@ impl Gasometer { gas = overflowing!(gas.overflow_add(schedule.call_value_transfer_gas.into())); }; - // TODO: refactor to avoid duplicate calculation here and later on. - let (mem_gas_cost, _, _) = try!(self.mem_gas_cost(schedule, current_mem_size, &mem)); - let cost_so_far = overflowing!(gas.overflow_add(mem_gas_cost.into())); - let requested = Gas::from_u256(*stack.peek(0)); - let provided = try!(self.gas_provided(schedule, cost_so_far, Some(requested))); - gas = overflowing!(gas.overflow_add(provided)); + let requested = *stack.peek(0); - InstructionCost::GasMem(gas, mem, Some(provided)) + Request::GasMemProvide(gas, mem, Some(requested)) }, instructions::DELEGATECALL => { - let mut gas = Gas::from(schedule.call_gas); + let gas = Gas::from(schedule.call_gas); let mem = cmp::max( try!(mem_needed(stack.peek(4), stack.peek(5))), try!(mem_needed(stack.peek(2), stack.peek(3))) ); + let requested = *stack.peek(0); - // TODO: refactor to avoid duplicate calculation here and later on. - let (mem_gas_cost, _, _) = try!(self.mem_gas_cost(schedule, current_mem_size, &mem)); - let cost_so_far = overflowing!(gas.overflow_add(mem_gas_cost.into())); - let requested = Gas::from_u256(*stack.peek(0)); - let provided = try!(self.gas_provided(schedule, cost_so_far, Some(requested))); - gas = overflowing!(gas.overflow_add(provided)); - - InstructionCost::GasMem(gas, mem, Some(provided)) + Request::GasMemProvide(gas, mem, Some(requested)) }, instructions::CREATE => { - let mut gas = Gas::from(schedule.create_gas); + let gas = Gas::from(schedule.create_gas); let mem = try!(mem_needed(stack.peek(1), stack.peek(2))); - // TODO: refactor to avoid duplicate calculation here and later on. - let (mem_gas_cost, _, _) = try!(self.mem_gas_cost(schedule, current_mem_size, &mem)); - let cost_so_far = overflowing!(gas.overflow_add(mem_gas_cost.into())); - let provided = try!(self.gas_provided(schedule, cost_so_far, None)); - gas = overflowing!(gas.overflow_add(provided)); - - InstructionCost::GasMem(gas, mem, Some(provided)) + Request::GasMemProvide(gas, mem, None) }, instructions::EXP => { let expon = stack.peek(1); let bytes = ((expon.bits() + 7) / 8) as usize; let gas = Gas::from(schedule.exp_gas + schedule.exp_byte_gas * bytes); - InstructionCost::Gas(gas) + Request::Gas(gas) }, - _ => InstructionCost::Gas(default_gas), + _ => Request::Gas(default_gas), }; - match cost { - InstructionCost::Gas(gas) => { - Ok((gas, self.current_mem_gas, 0, None)) + Ok(match cost { + Request::Gas(gas) => { + InstructionRequirements { + gas_cost: gas, + provide_gas: None, + memory_required_size: 0, + memory_total_gas: self.current_mem_gas, + } }, - InstructionCost::GasMem(gas, mem_size, provided) => { + Request::GasMem(gas, mem_size) => { let (mem_gas_cost, new_mem_gas, new_mem_size) = try!(self.mem_gas_cost(schedule, current_mem_size, &mem_size)); let gas = overflowing!(gas.overflow_add(mem_gas_cost)); - Ok((gas, new_mem_gas, new_mem_size, provided)) + InstructionRequirements { + gas_cost: gas, + provide_gas: None, + memory_required_size: new_mem_size, + memory_total_gas: new_mem_gas, + } }, - InstructionCost::GasMemCopy(gas, mem_size, copy) => { + Request::GasMemProvide(gas, mem_size, requested) => { + let (mem_gas_cost, new_mem_gas, new_mem_size) = try!(self.mem_gas_cost(schedule, current_mem_size, &mem_size)); + let gas = overflowing!(gas.overflow_add(mem_gas_cost)); + let provided = try!(self.gas_provided(schedule, gas, requested)); + let total_gas = overflowing!(gas.overflow_add(provided)); + + InstructionRequirements { + gas_cost: total_gas, + provide_gas: Some(provided), + memory_required_size: new_mem_size, + memory_total_gas: new_mem_gas, + } + }, + Request::GasMemCopy(gas, mem_size, copy) => { let (mem_gas_cost, new_mem_gas, new_mem_size) = try!(self.mem_gas_cost(schedule, current_mem_size, &mem_size)); let copy = overflowing!(add_gas_usize(copy, 31)) >> 5; let copy_gas = Gas::from(schedule.copy_gas) * copy; let gas = overflowing!(gas.overflow_add(copy_gas)); let gas = overflowing!(gas.overflow_add(mem_gas_cost)); - Ok((gas, new_mem_gas, new_mem_size, None)) - } - } + + InstructionRequirements { + gas_cost: gas, + provide_gas: None, + memory_required_size: new_mem_size, + memory_total_gas: new_mem_gas, + } + }, + }) } fn mem_gas_cost(&self, schedule: &evm::Schedule, current_mem_size: usize, mem_size: &Gas) -> evm::Result<(Gas, Gas, usize)> { @@ -256,7 +284,7 @@ impl Gasometer { let a = overflowing!(s.overflow_mul(Gas::from(schedule.memory_gas))); // Calculate s*s/quad_coeff_div - debug_assert_eq!(schedule.quad_coeff_div, 512); + assert_eq!(schedule.quad_coeff_div, 512); let b = overflowing!(s.overflow_mul_shr(s, 9)); Ok(overflowing!(a.overflow_add(b))) }; @@ -328,3 +356,4 @@ fn test_calculate_mem_cost() { assert_eq!(new_mem_gas, 3); assert_eq!(mem_size, 32); } + diff --git a/ethcore/src/evm/interpreter/mod.rs b/ethcore/src/evm/interpreter/mod.rs index 8ded2e1f1..bb9791abe 100644 --- a/ethcore/src/evm/interpreter/mod.rs +++ b/ethcore/src/evm/interpreter/mod.rs @@ -120,14 +120,14 @@ impl evm::Evm for Interpreter { try!(self.verify_instruction(ext, instruction, info, &stack)); // Calculate gas cost - let (gas_cost, mem_gas, mem_size, provided) = try!(gasometer.get_gas_cost_mem(ext, instruction, info, &stack, self.mem.size())); + let requirements = try!(gasometer.requirements(ext, instruction, info, &stack, self.mem.size())); // TODO: make compile-time removable if too much of a performance hit. - let trace_executed = ext.trace_prepare_execute(reader.position - 1, instruction, &gas_cost.as_u256()); + let trace_executed = ext.trace_prepare_execute(reader.position - 1, instruction, &requirements.gas_cost.as_u256()); - try!(gasometer.verify_gas(&gas_cost)); - self.mem.expand(mem_size); - gasometer.current_mem_gas = mem_gas; - gasometer.current_gas = gasometer.current_gas - gas_cost; + try!(gasometer.verify_gas(&requirements.gas_cost)); + self.mem.expand(requirements.memory_required_size); + gasometer.current_mem_gas = requirements.memory_total_gas; + gasometer.current_gas = gasometer.current_gas - requirements.gas_cost; evm_debug!({ informant.before_instruction(reader.position, instruction, info, &gasometer.current_gas, &stack) }); @@ -138,7 +138,7 @@ impl evm::Evm for Interpreter { // Execute instruction let result = try!(self.exec_instruction( - gasometer.current_gas, ¶ms, ext, instruction, &mut reader, &mut stack, provided + gasometer.current_gas, ¶ms, ext, instruction, &mut reader, &mut stack, requirements.provide_gas )); evm_debug!({ informant.after_instruction(instruction) }); diff --git a/evmbin/Cargo.lock b/evmbin/Cargo.lock index 600af473b..fde83d1bf 100644 --- a/evmbin/Cargo.lock +++ b/evmbin/Cargo.lock @@ -155,7 +155,7 @@ name = "ethash" version = "1.4.0" dependencies = [ "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", - "parking_lot 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "primal 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "sha3 0.1.0", ] @@ -208,6 +208,9 @@ dependencies = [ [[package]] name = "ethcore-bloom-journal" version = "0.1.0" +dependencies = [ + "siphasher 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "ethcore-devtools" @@ -223,7 +226,7 @@ dependencies = [ "crossbeam 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "mio 0.5.1 (git+https://github.com/ethcore/mio?branch=v0.5.x)", - "parking_lot 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "slab 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -275,8 +278,9 @@ dependencies = [ "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", - "parking_lot 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 0.1.77 (registry+https://github.com/rust-lang/crates.io-index)", "rlp 0.1.0", "rocksdb 0.4.5 (git+https://github.com/ethcore/rust-rocksdb)", "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", @@ -334,6 +338,7 @@ dependencies = [ "itertools 0.4.19 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", @@ -632,13 +637,28 @@ name = "odds" version = "0.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "owning_ref" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "parking_lot" -version = "0.2.8" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "owning_ref 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot_core 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "parking_lot_core" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -867,6 +887,11 @@ dependencies = [ "gcc 0.3.37 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "siphasher" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "slab" version = "0.1.3" @@ -1179,7 +1204,9 @@ dependencies = [ "checksum num-traits 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)" = "a16a42856a256b39c6d3484f097f6713e14feacd9bfb02290917904fae46c81c" "checksum num_cpus 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "cee7e88156f3f9e19bdd598f8d6c9db7bf4078f99f8381f43a55b09648d1a6e3" "checksum odds 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)" = "e04630a62b3f1cc8c58b4d8f2555a40136f02b420e158242936ef286a72d33a0" -"checksum parking_lot 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "968f685642555d2f7e202c48b8b11de80569e9bfea817f7f12d7c61aac62d4e6" +"checksum owning_ref 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8d91377085359426407a287ab16884a0111ba473aa6844ff01d4ec20ce3d75e7" +"checksum parking_lot 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "e1435e7a2a00dfebededd6c6bdbd54008001e94b4a2aadd6aef0dc4c56317621" +"checksum parking_lot_core 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fb1b97670a2ffadce7c397fb80a3d687c4f3060140b885621ef1653d0e5d5068" "checksum primal 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0e31b86efadeaeb1235452171a66689682783149a6249ff334a2c5d8218d00a4" "checksum primal-bit 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "464a91febc06166783d4f5ba3577b5ed8dda8e421012df80bfe48a971ed7be8f" "checksum primal-check 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "647c81b67bb9551a7b88d0bcd785ac35b7d0bf4b2f358683d7c2375d04daec51" @@ -1205,6 +1232,7 @@ dependencies = [ "checksum serde_codegen 0.8.13 (registry+https://github.com/rust-lang/crates.io-index)" = "e575e583f7d162e163af117fb9791fbd2bd203c31023b3219617e12c5997a738" "checksum serde_codegen_internals 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "318f7e77aa5187391d74aaf4553d2189f56b0ce25e963414c951b97877ffdcec" "checksum serde_json 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1cb6b19e74d9f65b9d03343730b643d729a446b29376785cd65efdff4675e2fc" +"checksum siphasher 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5c44e42fa187b5a8782489cf7740cc27c3125806be2bf33563cf5e02e9533fcd" "checksum slab 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d807fd58c4181bbabed77cb3b891ba9748241a552bcc5be698faaebefc54f46e" "checksum slab 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6dbdd334bd28d328dad1c41b0ea662517883d8880d8533895ef96c8003dec9c4" "checksum smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "fcc8d19212aacecf95e4a7a2179b26f7aeb9732a915cf01f05b0d3e044865410" diff --git a/evmbin/src/ext.rs b/evmbin/src/ext.rs index 79e6eb3bd..11fb3a876 100644 --- a/evmbin/src/ext.rs +++ b/evmbin/src/ext.rs @@ -31,7 +31,7 @@ pub struct FakeExt { impl Default for FakeExt { fn default() -> Self { FakeExt { - schedule: Schedule::new_homestead(), + schedule: Schedule::new_homestead_gas_fix(), store: HashMap::new(), depth: 1, } From 07fca24b446cc0d36bf8fe45856c61af26820929 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Thu, 27 Oct 2016 19:29:55 +0200 Subject: [PATCH 31/47] Next nonce RPC (#2917) --- rpc/src/v1/impls/ethcore.rs | 13 +++++++++++++ rpc/src/v1/tests/mocked/ethcore.rs | 24 +++++++++++++++++++++++- rpc/src/v1/traits/ethcore.rs | 4 ++++ 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/rpc/src/v1/impls/ethcore.rs b/rpc/src/v1/impls/ethcore.rs index 1a1410ebd..2619b84da 100644 --- a/rpc/src/v1/impls/ethcore.rs +++ b/rpc/src/v1/impls/ethcore.rs @@ -334,4 +334,17 @@ impl Ethcore for EthcoreClient where self.dapps_port .ok_or_else(|| errors::dapps_disabled()) } + + fn next_nonce(&self, address: H160) -> Result { + try!(self.active()); + let address: Address = address.into(); + let miner = take_weak!(self.miner); + let client = take_weak!(self.client); + + Ok(miner.last_nonce(&address) + .map(|n| n + 1.into()) + .unwrap_or_else(|| client.latest_nonce(&address)) + .into() + ) + } } diff --git a/rpc/src/v1/tests/mocked/ethcore.rs b/rpc/src/v1/tests/mocked/ethcore.rs index ea4112c19..e33f1a8f7 100644 --- a/rpc/src/v1/tests/mocked/ethcore.rs +++ b/rpc/src/v1/tests/mocked/ethcore.rs @@ -16,7 +16,7 @@ use std::sync::Arc; use util::log::RotatingLogger; -use util::U256; +use util::{U256, Address}; use ethsync::ManageNetwork; use ethcore::client::{TestBlockChainClient}; use ethstore::ethkey::{Generator, Random}; @@ -320,3 +320,25 @@ fn rpc_ethcore_dapps_port() { assert_eq!(io1.handle_request_sync(request), Some(response1.to_owned())); assert_eq!(io2.handle_request_sync(request), Some(response2.to_owned())); } + +#[test] +fn rpc_ethcore_next_nonce() { + let deps = Dependencies::new(); + let address = Address::default(); + let io1 = deps.default_client(); + let deps = Dependencies::new(); + deps.miner.last_nonces.write().insert(address.clone(), 2.into()); + let io2 = deps.default_client(); + + let request = r#"{ + "jsonrpc": "2.0", + "method": "ethcore_nextNonce", + "params": [""#.to_owned() + &format!("0x{:?}", address) + r#""], + "id": 1 + }"#; + let response1 = r#"{"jsonrpc":"2.0","result":"0x0","id":1}"#; + let response2 = r#"{"jsonrpc":"2.0","result":"0x3","id":1}"#; + + assert_eq!(io1.handle_request_sync(&request), Some(response1.to_owned())); + assert_eq!(io2.handle_request_sync(&request), Some(response2.to_owned())); +} diff --git a/rpc/src/v1/traits/ethcore.rs b/rpc/src/v1/traits/ethcore.rs index ea5f0b13d..e787ce5ac 100644 --- a/rpc/src/v1/traits/ethcore.rs +++ b/rpc/src/v1/traits/ethcore.rs @@ -125,5 +125,9 @@ build_rpc_trait! { /// Returns current Dapps Server port or an error if dapps server is disabled. #[rpc(name = "ethcore_dappsPort")] fn dapps_port(&self) -> Result; + + /// Returns next nonce for particular sender. Should include all transactions in the queue. + #[rpc(name = "ethcore_nextNonce")] + fn next_nonce(&self, H160) -> Result; } } From a4e84c375e05f5dbef26b730094fd653a61803ca Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Thu, 27 Oct 2016 19:30:13 +0200 Subject: [PATCH 32/47] Pass the js-precompiled commit hash to cargo update (#2920) --- js/scripts/release.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/js/scripts/release.sh b/js/scripts/release.sh index 392bdd3b8..8e90713cb 100755 --- a/js/scripts/release.sh +++ b/js/scripts/release.sh @@ -28,9 +28,10 @@ git remote add origin https://${GITHUB_JS_PRECOMPILED}:@github.com/ethcore/js-pr git fetch origin 2>$GITLOG git checkout -b $CI_BUILD_REF_NAME git add . -git commit -m "$UTCDATE [compiled]" +git commit -m "$UTCDATE" git merge origin/$CI_BUILD_REF_NAME -X ours --commit -m "$UTCDATE [release]" git push origin HEAD:refs/heads/$CI_BUILD_REF_NAME 2>$GITLOG +PRECOMPILED_HASH=$(git rev-parse HEAD) # back to root popd @@ -43,7 +44,7 @@ git remote set-url origin https://${GITHUB_JS_PRECOMPILED}:@github.com/ethcore/p git reset --hard origin/$CI_BUILD_REF_NAME 2>$GITLOG # bump js-precompiled, add, commit & push -cargo update -p parity-ui-precompiled +cargo update -p parity-ui-precompiled --precise $PRECOMPILED_HASH git add . || true git commit -m "[ci skip] js-precompiled $UTCDATE" git push origin HEAD:refs/heads/$CI_BUILD_REF_NAME 2>$GITLOG From 22af04e81d6be8bec6b4b1f164aeb84420c7f9c4 Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Thu, 27 Oct 2016 17:53:23 +0000 Subject: [PATCH 33/47] [ci skip] js-precompiled 20161027-175225 --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 6fac719d4..dff3416e5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1212,7 +1212,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#0053be493c742cbd7332ba647b3c84d35327abd7" +source = "git+https://github.com/ethcore/js-precompiled.git#0b2b13109f3cc4daa33511bddcb1c7edefc25dd1" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] From 9fd8ac6a155825f150b0c0c78116e42ed4db4e67 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Thu, 27 Oct 2016 21:05:47 +0200 Subject: [PATCH 34/47] additional release.sh debugging info (#2922) --- js/scripts/release.sh | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/js/scripts/release.sh b/js/scripts/release.sh index 8e90713cb..c08d92ca0 100755 --- a/js/scripts/release.sh +++ b/js/scripts/release.sh @@ -23,28 +23,37 @@ rm -rf ./.git git init # add local files and send it up +echo "Setting up GitHub config for js-precompiled" setup_git_user + +echo "Checking out $CI_BUILD_REF_NAME branch" git remote add origin https://${GITHUB_JS_PRECOMPILED}:@github.com/ethcore/js-precompiled.git git fetch origin 2>$GITLOG git checkout -b $CI_BUILD_REF_NAME + +echo "Committing compiled files for $UTCDATE" git add . git commit -m "$UTCDATE" + +echo "Merging remote" git merge origin/$CI_BUILD_REF_NAME -X ours --commit -m "$UTCDATE [release]" git push origin HEAD:refs/heads/$CI_BUILD_REF_NAME 2>$GITLOG PRECOMPILED_HASH=$(git rev-parse HEAD) +echo "Remote updated with [$PRECOMPILED_HASH]" + # back to root popd -# inti git with right origin +echo "Setting up GitHub config for parity" setup_git_user git remote set-url origin https://${GITHUB_JS_PRECOMPILED}:@github.com/ethcore/parity.git - -# at this point we have a detached head on GitLab, reset git reset --hard origin/$CI_BUILD_REF_NAME 2>$GITLOG -# bump js-precompiled, add, commit & push +echo "Updating cargo package parity-ui-precompiled" cargo update -p parity-ui-precompiled --precise $PRECOMPILED_HASH + +echo "Committing updated files" git add . || true git commit -m "[ci skip] js-precompiled $UTCDATE" git push origin HEAD:refs/heads/$CI_BUILD_REF_NAME 2>$GITLOG From a9e7f592034038c6ca71837faa37f89d3e36098c Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Thu, 27 Oct 2016 21:15:41 +0200 Subject: [PATCH 35/47] Merge branch 'master' into jg-js-release-fix (#2924) # Conflicts: # js/scripts/release.sh --- js/scripts/release.sh | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/js/scripts/release.sh b/js/scripts/release.sh index c08d92ca0..4f5bf421d 100755 --- a/js/scripts/release.sh +++ b/js/scripts/release.sh @@ -40,8 +40,6 @@ git merge origin/$CI_BUILD_REF_NAME -X ours --commit -m "$UTCDATE [release]" git push origin HEAD:refs/heads/$CI_BUILD_REF_NAME 2>$GITLOG PRECOMPILED_HASH=$(git rev-parse HEAD) -echo "Remote updated with [$PRECOMPILED_HASH]" - # back to root popd @@ -50,11 +48,11 @@ setup_git_user git remote set-url origin https://${GITHUB_JS_PRECOMPILED}:@github.com/ethcore/parity.git git reset --hard origin/$CI_BUILD_REF_NAME 2>$GITLOG -echo "Updating cargo package parity-ui-precompiled" -cargo update -p parity-ui-precompiled --precise $PRECOMPILED_HASH +echo "Updating cargo package parity-ui-precompiled#$PRECOMPILED_HASH" +cargo update -p parity-ui-precompiled --precise "$PRECOMPILED_HASH" echo "Committing updated files" -git add . || true +git add . git commit -m "[ci skip] js-precompiled $UTCDATE" git push origin HEAD:refs/heads/$CI_BUILD_REF_NAME 2>$GITLOG From 908e563f1884c45b2fbac534fb5dc801bf0a37e4 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Thu, 27 Oct 2016 22:26:47 +0200 Subject: [PATCH 36/47] Revert hash updates until testable (#2925) * Revert hash updates until testable * Re-add hash to echo (debug info) --- js/scripts/release.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/js/scripts/release.sh b/js/scripts/release.sh index 4f5bf421d..95e561b77 100755 --- a/js/scripts/release.sh +++ b/js/scripts/release.sh @@ -38,7 +38,7 @@ git commit -m "$UTCDATE" echo "Merging remote" git merge origin/$CI_BUILD_REF_NAME -X ours --commit -m "$UTCDATE [release]" git push origin HEAD:refs/heads/$CI_BUILD_REF_NAME 2>$GITLOG -PRECOMPILED_HASH=$(git rev-parse HEAD) +PRECOMPILED_HASH=`git rev-parse HEAD` # back to root popd @@ -49,7 +49,8 @@ git remote set-url origin https://${GITHUB_JS_PRECOMPILED}:@github.com/ethcore/p git reset --hard origin/$CI_BUILD_REF_NAME 2>$GITLOG echo "Updating cargo package parity-ui-precompiled#$PRECOMPILED_HASH" -cargo update -p parity-ui-precompiled --precise "$PRECOMPILED_HASH" +cargo update -p parity-ui-precompiled +# --precise "$PRECOMPILED_HASH" echo "Committing updated files" git add . From 151ec869a6ac0caf64dc0f6c46f181a7d7b3f826 Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Thu, 27 Oct 2016 22:41:22 +0200 Subject: [PATCH 37/47] [ci skip] js-precompiled 20161027-204107 --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index dff3416e5..f7c08953e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1212,7 +1212,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#0b2b13109f3cc4daa33511bddcb1c7edefc25dd1" +source = "git+https://github.com/ethcore/js-precompiled.git#3f31aca3dd0b71871f6439407c3e258c3ed91abf" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] From 55efa16e4248d48375f4bb5d8de96e1fd3387422 Mon Sep 17 00:00:00 2001 From: Arkadiy Paronyan Date: Fri, 28 Oct 2016 12:00:01 +0200 Subject: [PATCH 38/47] More bootnodes (#2926) --- ethcore/res/ethereum/frontier.json | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/ethcore/res/ethereum/frontier.json b/ethcore/res/ethereum/frontier.json index d5f57defd..111dff30e 100644 --- a/ethcore/res/ethereum/frontier.json +++ b/ethcore/res/ethereum/frontier.json @@ -158,9 +158,17 @@ "stateRoot": "0xd7f8974fb5ac78d9ac099b9ad5018bedc2ce0a72dad1827a1709da30580f0544" }, "nodes": [ - "enode://cd6611461840543d5b9c56fbf088736154c699c43973b3a1a32390cf27106f87e58a818a606ccb05f3866de95a4fe860786fea71bf891ea95f234480d3022aa3@136.243.154.245:30303", + "enode://efe4f2493f4aff2d641b1db8366b96ddacfe13e7a6e9c8f8f8cf49f9cdba0fdf3258d8c8f8d0c5db529f8123c8f1d95f36d54d590ca1bb366a5818b9a4ba521c@163.172.187.252:30303", + "enode://cd6611461840543d5b9c56fbf088736154c699c43973b3a1a32390cf27106f87e58a818a606ccb05f3866de95a4fe860786fea71bf891ea95f234480d3022aa3@163.172.157.114:30303", "enode://bcc7240543fe2cf86f5e9093d05753dd83343f8fda7bf0e833f65985c73afccf8f981301e13ef49c4804491eab043647374df1c4adf85766af88a624ecc3330e@136.243.154.244:30303", "enode://ed4227681ca8c70beb2277b9e870353a9693f12e7c548c35df6bca6a956934d6f659999c2decb31f75ce217822eefca149ace914f1cbe461ed5a2ebaf9501455@88.212.206.70:30303", + "enode://cadc6e573b6bc2a9128f2f635ac0db3353e360b56deef239e9be7e7fce039502e0ec670b595f6288c0d2116812516ad6b6ff8d5728ff45eba176989e40dead1e@37.128.191.230:30303", + "enode://595a9a06f8b9bc9835c8723b6a82105aea5d55c66b029b6d44f229d6d135ac3ecdd3e9309360a961ea39d7bee7bac5d03564077a4e08823acc723370aace65ec@46.20.235.22:30303", + "enode://029178d6d6f9f8026fc0bc17d5d1401aac76ec9d86633bba2320b5eed7b312980c0a210b74b20c4f9a8b0b2bf884b111fa9ea5c5f916bb9bbc0e0c8640a0f56c@216.158.85.185:30303", + "enode://84f5d5957b4880a8b0545e32e05472318898ad9fc8ebe1d56c90c12334a98e12351eccfdf3a2bf72432ac38b57e9d348400d17caa083879ade3822390f89773f@10.1.52.78:30303", + "enode://f90dc9b9bf7b8db97726b7849e175f1eb2707f3d8f281c929336e398dd89b0409fc6aeceb89e846278e9d3ecc3857cebfbe6758ff352ece6fe5d42921ee761db@10.1.173.87:30303", + "enode://6a868ced2dec399c53f730261173638a93a40214cf299ccf4d42a76e3fa54701db410669e8006347a4b3a74fa090bb35af0320e4bc8d04cf5b7f582b1db285f5@10.3.149.199:30303", + "enode://fdd1b9bb613cfbc200bba17ce199a9490edc752a833f88d4134bf52bb0d858aa5524cb3ec9366c7a4ef4637754b8b15b5dc913e4ed9fdb6022f7512d7b63f181@212.47.247.103:30303", "enode://a979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c@52.16.188.185:30303", "enode://de471bccee3d042261d52e9bff31458daecc406142b401d4cd848f677479f73104b9fdeb090af9583d3391b7f10cb2ba9e26865dd5fca4fcdc0fb1e3b723c786@54.94.239.50:30303", "enode://1118980bf48b0a3640bdba04e0fe78b1add18e1cd99bf22d53daac1fd9972ad650df52176e7c7d89d1114cfef2bc23a2959aa54998a46afcf7d91809f0855082@52.74.57.123:30303", From 956a059a06806cf01c9dce39ec9b42e2a9f6e576 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Fri, 28 Oct 2016 16:02:23 +0200 Subject: [PATCH 39/47] Updating bootnodes for ETC (#2938) --- ethcore/res/ethereum/classic.json | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/ethcore/res/ethereum/classic.json b/ethcore/res/ethereum/classic.json index 5d951752f..5be7b1caf 100644 --- a/ethcore/res/ethereum/classic.json +++ b/ethcore/res/ethereum/classic.json @@ -39,10 +39,18 @@ "stateRoot": "0xd7f8974fb5ac78d9ac099b9ad5018bedc2ce0a72dad1827a1709da30580f0544" }, "nodes": [ + "enode://08c7ee6a4f861ff0664a49532bcc86de1363acd608999d1b76609bb9bc278649906f069057630fd9493924a368b5d1dc9b8f8bf13ac26df72512f6d1fabd8c95@45.32.7.81:30303", "enode://e809c4a2fec7daed400e5e28564e23693b23b2cc5a019b612505631bbe7b9ccf709c1796d2a3d29ef2b045f210caf51e3c4f5b6d3587d43ad5d6397526fa6179@174.112.32.157:30303", "enode://687be94c3a7beaa3d2fde82fa5046cdeb3e8198354e05b29d6e0d4e276713e3707ac10f784a7904938b06b46c764875c241b0337dd853385a4d8bfcbf8190647@95.183.51.229:30303", "enode://6e538e7c1280f0a31ff08b382db5302480f775480b8e68f8febca0ceff81e4b19153c6f8bf60313b93bef2cc34d34e1df41317de0ce613a201d1660a788a03e2@52.206.67.235:30303", - "enode://217ebe27e89bf4fec8ce06509323ff095b1014378deb75ab2e5f6759a4e8750a3bd8254b8c6833136e4d5e58230d65ee8ab34a5db5abf0640408c4288af3c8a7@188.138.1.237:30303" + "enode://ca5ae4eca09ba6787e29cf6d86f7634d07aae6b9e6317a59aff675851c0bf445068173208cf8ef7f5cd783d8e29b85b2fa3fa358124cf0546823149724f9bde1@138.68.1.16:30303", + "enode://217ebe27e89bf4fec8ce06509323ff095b1014378deb75ab2e5f6759a4e8750a3bd8254b8c6833136e4d5e58230d65ee8ab34a5db5abf0640408c4288af3c8a7@188.138.1.237:30303", + "enode://fa20444ef991596ce99b81652ac4e61de1eddc4ff21d3cd42762abd7ed47e7cf044d3c9ccddaf6035d39725e4eb372806787829ccb9a08ec7cb71883cb8c3abd@50.149.116.182:30303", + "enode://4bd6a4df3612c718333eb5ea7f817923a8cdf1bed89cee70d1710b45a0b6b77b2819846440555e451a9b602ad2efa2d2facd4620650249d8468008946887820a@71.178.232.20:30304", + "enode://921cf8e4c345fe8db913c53964f9cadc667644e7f20195a0b7d877bd689a5934e146ff2c2259f1bae6817b6585153a007ceb67d260b720fa3e6fc4350df25c7f@51.255.49.170:30303", + "enode://ffea3b01c000cdd89e1e9229fea3e80e95b646f9b2aa55071fc865e2f19543c9b06045cc2e69453e6b78100a119e66be1b5ad50b36f2ffd27293caa28efdd1b2@128.199.93.177:3030", + "enode://ee3da491ce6a155eb132708eb0e8d04b0637926ec0ae1b79e63fc97cb9fc3818f49250a0ae0d7f79ed62b66ec677f408c4e01741504dc7a051e274f1e803d454@91.121.65.179:40404", + "enode://48e063a6cf5f335b1ef2ed98126bf522cf254396f850c7d442fe2edbbc23398787e14cd4de7968a00175a82762de9cbe9e1407d8ccbcaeca5004d65f8398d759@159.203.255.59:30303" ], "accounts": { "0000000000000000000000000000000000000001": { "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } }, From 29ab4ecac1e78d90980a56f39116c46f40f9a473 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Fri, 28 Oct 2016 16:04:44 +0200 Subject: [PATCH 40/47] Shared code cache (#2921) * add a global code cache * extract memory-limited lru cache to util * use memory-limited code cache * account for code cache size in mem_used --- Cargo.lock | 9 +-- ethcore/Cargo.toml | 2 +- ethcore/src/evm/interpreter/shared_cache.rs | 53 +++++--------- ethcore/src/state/account.rs | 21 ++++-- ethcore/src/state/mod.rs | 40 +++++++---- ethcore/src/state_db.rs | 34 ++++++++- util/Cargo.toml | 1 + util/src/cache.rs | 79 +++++++++++++++++++++ util/src/lib.rs | 2 + 9 files changed, 180 insertions(+), 61 deletions(-) create mode 100644 util/src/cache.rs diff --git a/Cargo.lock b/Cargo.lock index f7c08953e..26fab3d49 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -298,7 +298,7 @@ dependencies = [ "hyper 0.9.4 (git+https://github.com/ethcore/hyper)", "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", - "lru-cache 0.0.7 (git+https://github.com/contain-rs/lru-cache)", + "lru-cache 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "num_cpus 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "rayon 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -555,6 +555,7 @@ dependencies = [ "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "lru-cache 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", "regex 0.1.68 (registry+https://github.com/rust-lang/crates.io-index)", @@ -907,8 +908,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "lru-cache" -version = "0.0.7" -source = "git+https://github.com/contain-rs/lru-cache#13255e33c45ceb69a4b143f235a4322df5fb580e" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "linked-hash-map 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1987,7 +1988,7 @@ dependencies = [ "checksum linked-hash-map 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bda158e0dabeb97ee8a401f4d17e479d6b891a14de0bba79d5cc2d4d325b5e48" "checksum linked-hash-map 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6d262045c5b87c0861b3f004610afd0e2c851e2908d08b6c870cbb9d5f494ecd" "checksum log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ab83497bf8bf4ed2a74259c1c802351fcd67a65baa86394b6ba73c36f4838054" -"checksum lru-cache 0.0.7 (git+https://github.com/contain-rs/lru-cache)" = "" +"checksum lru-cache 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "656fa4dfcb02bcf1063c592ba3ff6a5303ee1f2afe98c8a889e8b1a77c6dfdb7" "checksum matches 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "15305656809ce5a4805b1ff2946892810992197ce1270ff79baded852187942e" "checksum memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d8b629fb514376c675b98c1421e80b151d3817ac42d7c667717d282761418d20" "checksum mime 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a74cc2587bf97c49f3f5bab62860d6abf3902ca73b66b51d9b049fbdcd727bd2" diff --git a/ethcore/Cargo.toml b/ethcore/Cargo.toml index d48e76e94..1f8413339 100644 --- a/ethcore/Cargo.toml +++ b/ethcore/Cargo.toml @@ -27,7 +27,6 @@ time = "0.1" rand = "0.3" byteorder = "0.5" transient-hashmap = "0.1" -lru-cache = { git = "https://github.com/contain-rs/lru-cache" } evmjit = { path = "../evmjit", optional = true } clippy = { version = "0.0.96", optional = true} ethash = { path = "../ethash" } @@ -40,6 +39,7 @@ ethstore = { path = "../ethstore" } ethkey = { path = "../ethkey" } ethcore-ipc-nano = { path = "../ipc/nano" } rlp = { path = "../util/rlp" } +lru-cache = "0.1.0" ethcore-bloom-journal = { path = "../util/bloom" } [dependencies.hyper] diff --git a/ethcore/src/evm/interpreter/shared_cache.rs b/ethcore/src/evm/interpreter/shared_cache.rs index dee557522..cacc4dde3 100644 --- a/ethcore/src/evm/interpreter/shared_cache.rs +++ b/ethcore/src/evm/interpreter/shared_cache.rs @@ -15,20 +15,27 @@ // along with Parity. If not, see . use std::sync::Arc; -use lru_cache::LruCache; -use util::{H256, Mutex}; +use util::{H256, HeapSizeOf, Mutex}; use util::sha3::*; +use util::cache::MemoryLruCache; use bit_set::BitSet; use super::super::instructions; -const INITIAL_CAPACITY: usize = 32; const DEFAULT_CACHE_SIZE: usize = 4 * 1024 * 1024; +// stub for a HeapSizeOf implementation. +struct Bits(Arc); + +impl HeapSizeOf for Bits { + fn heap_size_of_children(&self) -> usize { + // dealing in bits here + self.0.capacity() * 8 + } +} + /// Global cache for EVM interpreter pub struct SharedCache { - jump_destinations: Mutex>>, - max_size: usize, - cur_size: Mutex, + jump_destinations: Mutex>, } impl SharedCache { @@ -36,9 +43,7 @@ impl SharedCache { /// to cache. pub fn new(max_size: usize) -> Self { SharedCache { - jump_destinations: Mutex::new(LruCache::new(INITIAL_CAPACITY)), - max_size: max_size * 8, // dealing with bits here. - cur_size: Mutex::new(0), + jump_destinations: Mutex::new(MemoryLruCache::new(max_size)), } } @@ -49,37 +54,11 @@ impl SharedCache { } if let Some(d) = self.jump_destinations.lock().get_mut(code_hash) { - return d.clone(); + return d.0.clone(); } let d = Self::find_jump_destinations(code); - - { - let mut cur_size = self.cur_size.lock(); - *cur_size += d.capacity(); - - let mut jump_dests = self.jump_destinations.lock(); - let cap = jump_dests.capacity(); - - // grow the cache as necessary; it operates on amount of items - // but we're working based on memory usage. - if jump_dests.len() == cap && *cur_size < self.max_size { - jump_dests.set_capacity(cap * 2); - } - - // account for any element displaced from the cache. - if let Some(lru) = jump_dests.insert(code_hash.clone(), d.clone()) { - *cur_size -= lru.capacity(); - } - - // remove elements until we are below the memory target. - while *cur_size > self.max_size { - match jump_dests.remove_lru() { - Some((_, v)) => *cur_size -= v.capacity(), - _ => break, - } - } - } + self.jump_destinations.lock().insert(code_hash.clone(), Bits(d.clone())); d } diff --git a/ethcore/src/state/account.rs b/ethcore/src/state/account.rs index 2bd8a2d15..d8d281b17 100644 --- a/ethcore/src/state/account.rs +++ b/ethcore/src/state/account.rs @@ -247,23 +247,34 @@ impl Account { } /// Provide a database to get `code_hash`. Should not be called if it is a contract without code. - pub fn cache_code(&mut self, db: &HashDB) -> bool { + pub fn cache_code(&mut self, db: &HashDB) -> Option> { // TODO: fill out self.code_cache; trace!("Account::cache_code: ic={}; self.code_hash={:?}, self.code_cache={}", self.is_cached(), self.code_hash, self.code_cache.pretty()); - self.is_cached() || + + if self.is_cached() { return Some(self.code_cache.clone()) } + match db.get(&self.code_hash) { Some(x) => { self.code_size = Some(x.len()); self.code_cache = Arc::new(x.to_vec()); - true + Some(self.code_cache.clone()) }, _ => { warn!("Failed reverse get of {}", self.code_hash); - false + None }, } } + /// Provide code to cache. For correctness, should be the correct code for the + /// account. + pub fn cache_given_code(&mut self, code: Arc) { + trace!("Account::cache_given_code: ic={}; self.code_hash={:?}, self.code_cache={}", self.is_cached(), self.code_hash, self.code_cache.pretty()); + + self.code_size = Some(code.len()); + self.code_cache = code; + } + /// Provide a database to get `code_size`. Should not be called if it is a contract without code. pub fn cache_code_size(&mut self, db: &HashDB) -> bool { // TODO: fill out self.code_cache; @@ -476,7 +487,7 @@ mod tests { }; let mut a = Account::from_rlp(&rlp); - assert!(a.cache_code(&db.immutable())); + assert!(a.cache_code(&db.immutable()).is_some()); let mut a = Account::from_rlp(&rlp); assert_eq!(a.note_code(vec![0x55, 0x44, 0xffu8]), Ok(())); diff --git a/ethcore/src/state/mod.rs b/ethcore/src/state/mod.rs index bef20d257..7c0f43d97 100644 --- a/ethcore/src/state/mod.rs +++ b/ethcore/src/state/mod.rs @@ -599,14 +599,30 @@ impl State { pod_state::diff_pod(&state_pre.to_pod(), &pod_state_post) } - fn update_account_cache(require: RequireCache, account: &mut Account, db: &HashDB) { - match require { - RequireCache::None => {}, - RequireCache::Code => { - account.cache_code(db); - } - RequireCache::CodeSize => { - account.cache_code_size(db); + // load required account data from the databases. + fn update_account_cache(require: RequireCache, account: &mut Account, state_db: &StateDB, db: &HashDB) { + match (account.is_cached(), require) { + (true, _) | (false, RequireCache::None) => {} + (false, require) => { + // if there's already code in the global cache, always cache it + // locally. + let hash = account.code_hash(); + match state_db.get_cached_code(&hash) { + Some(code) => account.cache_given_code(code), + None => match require { + RequireCache::None => {}, + RequireCache::Code => { + if let Some(code) = account.cache_code(db) { + // propagate code loaded from the database to + // the global code cache. + state_db.cache_code(hash, code) + } + } + RequireCache::CodeSize => { + account.cache_code_size(db); + } + } + } } } } @@ -620,7 +636,7 @@ impl State { if let Some(ref mut maybe_acc) = self.cache.borrow_mut().get_mut(a) { if let Some(ref mut account) = maybe_acc.account { let accountdb = self.factories.accountdb.readonly(self.db.as_hashdb(), account.address_hash(a)); - Self::update_account_cache(require, account, accountdb.as_hashdb()); + Self::update_account_cache(require, account, &self.db, accountdb.as_hashdb()); return f(Some(account)); } return f(None); @@ -629,7 +645,7 @@ impl State { let result = self.db.get_cached(a, |mut acc| { if let Some(ref mut account) = acc { let accountdb = self.factories.accountdb.readonly(self.db.as_hashdb(), account.address_hash(a)); - Self::update_account_cache(require, account, accountdb.as_hashdb()); + Self::update_account_cache(require, account, &self.db, accountdb.as_hashdb()); } f(acc.map(|a| &*a)) }); @@ -647,7 +663,7 @@ impl State { }; if let Some(ref mut account) = maybe_acc.as_mut() { let accountdb = self.factories.accountdb.readonly(self.db.as_hashdb(), account.address_hash(a)); - Self::update_account_cache(require, account, accountdb.as_hashdb()); + Self::update_account_cache(require, account, &self.db, accountdb.as_hashdb()); } let r = f(maybe_acc.as_ref()); self.insert_cache(a, AccountEntry::new_clean(maybe_acc)); @@ -703,7 +719,7 @@ impl State { if require_code { let addr_hash = account.address_hash(a); let accountdb = self.factories.accountdb.readonly(self.db.as_hashdb(), addr_hash); - account.cache_code(accountdb.as_hashdb()); + Self::update_account_cache(RequireCache::Code, account, &self.db, accountdb.as_hashdb()); } account }, diff --git a/ethcore/src/state_db.rs b/ethcore/src/state_db.rs index dfa65ab1d..affc0b405 100644 --- a/ethcore/src/state_db.rs +++ b/ethcore/src/state_db.rs @@ -16,6 +16,7 @@ use std::collections::{VecDeque, HashSet}; use lru_cache::LruCache; +use util::cache::MemoryLruCache; use util::journaldb::JournalDB; use util::hash::{H256}; use util::hashdb::HashDB; @@ -33,12 +34,17 @@ pub const ACCOUNT_BLOOM_HASHCOUNT_KEY: &'static [u8] = b"account_hash_count"; const STATE_CACHE_BLOCKS: usize = 12; +// The percentage of supplied cache size to go to accounts. +const ACCOUNT_CACHE_RATIO: usize = 90; + /// Shared canonical state cache. struct AccountCache { /// DB Account cache. `None` indicates that account is known to be missing. // When changing the type of the values here, be sure to update `mem_used` and // `new`. accounts: LruCache>, + /// DB Code cache. Maps code hashes to shared bytes. + code: MemoryLruCache>>, /// Information on the modifications in recently committed blocks; specifically which addresses /// changed in which block. Ordered by block number. modifications: VecDeque, @@ -111,12 +117,15 @@ impl StateDB { // into the `AccountCache` structure as its own `LruCache<(Address, H256), H256>`. pub fn new(db: Box, cache_size: usize) -> StateDB { let bloom = Self::load_bloom(db.backing()); - let cache_items = cache_size / ::std::mem::size_of::>(); + let acc_cache_size = cache_size * ACCOUNT_CACHE_RATIO / 100; + let code_cache_size = cache_size - acc_cache_size; + let cache_items = acc_cache_size / ::std::mem::size_of::>(); StateDB { db: db, account_cache: Arc::new(Mutex::new(AccountCache { accounts: LruCache::new(cache_items), + code: MemoryLruCache::new(code_cache_size), modifications: VecDeque::new(), })), local_cache: Vec::new(), @@ -342,7 +351,12 @@ impl StateDB { /// Heap size used. pub fn mem_used(&self) -> usize { // TODO: account for LRU-cache overhead; this is a close approximation. - self.db.mem_used() + self.account_cache.lock().accounts.len() * ::std::mem::size_of::>() + self.db.mem_used() + { + let cache = self.account_cache.lock(); + + cache.code.current_size() + + cache.accounts.len() * ::std::mem::size_of::>() + } } /// Returns underlying `JournalDB`. @@ -362,6 +376,15 @@ impl StateDB { }) } + /// Add a global code cache entry. This doesn't need to worry about canonicality because + /// it simply maps hashes to raw code and will always be correct in the absence of + /// hash collisions. + pub fn cache_code(&self, hash: H256, code: Arc>) { + let mut cache = self.account_cache.lock(); + + cache.code.insert(hash, code); + } + /// Get basic copy of the cached account. Does not include storage. /// Returns 'None' if cache is disabled or if the account is not cached. pub fn get_cached_account(&self, addr: &Address) -> Option> { @@ -372,6 +395,13 @@ impl StateDB { cache.accounts.get_mut(addr).map(|a| a.as_ref().map(|a| a.clone_basic())) } + /// Get cached code based on hash. + pub fn get_cached_code(&self, hash: &H256) -> Option>> { + let mut cache = self.account_cache.lock(); + + cache.code.get_mut(hash).map(|code| code.clone()) + } + /// Get value from a cached account. /// Returns 'None' if cache is disabled or if the account is not cached. pub fn get_cached(&self, a: &Address, f: F) -> Option diff --git a/util/Cargo.toml b/util/Cargo.toml index 1b6939595..78cca92e0 100644 --- a/util/Cargo.toml +++ b/util/Cargo.toml @@ -36,6 +36,7 @@ ansi_term = "0.7" tiny-keccak= "1.0" ethcore-bloom-journal = { path = "bloom" } regex = "0.1" +lru-cache = "0.1.0" [features] default = [] diff --git a/util/src/cache.rs b/util/src/cache.rs new file mode 100644 index 000000000..2b2c50c8b --- /dev/null +++ b/util/src/cache.rs @@ -0,0 +1,79 @@ +// Copyright 2015, 2016 Ethcore (UK) Ltd. +// This file is part of Parity. + +// Parity 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. + +// Parity 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 Parity. If not, see . + +//! Lru-cache related utilities as quick-and-dirty wrappers around the lru-cache +//! crate. +// TODO: push changes upstream in a clean way. + +use heapsize::HeapSizeOf; +use lru_cache::LruCache; + +use std::hash::Hash; + +const INITIAL_CAPACITY: usize = 4; + +/// An LRU-cache which operates on memory used. +pub struct MemoryLruCache { + inner: LruCache, + cur_size: usize, + max_size: usize, +} + +impl MemoryLruCache { + /// Create a new cache with a maximum size in bytes. + pub fn new(max_size: usize) -> Self { + MemoryLruCache { + inner: LruCache::new(INITIAL_CAPACITY), + max_size: max_size, + cur_size: 0, + } + } + + /// Insert an item. + pub fn insert(&mut self, key: K, val: V) { + let cap = self.inner.capacity(); + + // grow the cache as necessary; it operates on amount of items + // but we're working based on memory usage. + if self.inner.len() == cap && self.cur_size < self.max_size { + self.inner.set_capacity(cap * 2); + } + + // account for any element displaced from the cache. + if let Some(lru) = self.inner.insert(key, val) { + self.cur_size -= lru.heap_size_of_children(); + } + + // remove elements until we are below the memory target. + while self.cur_size > self.max_size { + match self.inner.remove_lru() { + Some((_, v)) => self.cur_size -= v.heap_size_of_children(), + _ => break, + } + } + } + + /// Get a reference to an item in the cache. It is a logic error for its + /// heap size to be altered while borrowed. + pub fn get_mut(&mut self, key: &K) -> Option<&mut V> { + self.inner.get_mut(key) + } + + /// Currently-used size of values in bytes. + pub fn current_size(&self) -> usize { + self.cur_size + } +} \ No newline at end of file diff --git a/util/src/lib.rs b/util/src/lib.rs index e362459a6..f5558bcfc 100644 --- a/util/src/lib.rs +++ b/util/src/lib.rs @@ -105,6 +105,7 @@ extern crate ansi_term; extern crate tiny_keccak; extern crate rlp; extern crate regex; +extern crate lru_cache; #[macro_use] extern crate heapsize; @@ -143,6 +144,7 @@ pub mod semantic_version; pub mod log; pub mod path; pub mod snappy; +pub mod cache; mod timer; pub use common::*; From 2806f1d4c938c87386d0ee0f91e5a6d4446b16d4 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Fri, 28 Oct 2016 16:10:30 +0200 Subject: [PATCH 41/47] Even more snapshot validity checks (#2935) * clarify "cancelled periodic snapshot" message * more rigorous checks for snapshot validity * verify ancient blocks on import * limit number of fed blocks * make it possible to feed snapshot service canonical hashes * fix failing test build * swap ethash DAG only when more recent --- ethash/src/lib.rs | 21 +++-- ethcore/src/blockchain/blockchain.rs | 5 +- ethcore/src/client/client.rs | 23 +++++- ethcore/src/snapshot/error.rs | 12 ++- ethcore/src/snapshot/mod.rs | 80 ++++++++++++++++--- ethcore/src/snapshot/service.rs | 28 +++++-- .../src/snapshot/snapshot_service_trait.rs | 4 + ethcore/src/snapshot/tests/blocks.rs | 13 +-- sync/src/tests/snapshot.rs | 7 ++ 9 files changed, 154 insertions(+), 39 deletions(-) diff --git a/ethash/src/lib.rs b/ethash/src/lib.rs index 130882318..a04e2486b 100644 --- a/ethash/src/lib.rs +++ b/ethash/src/lib.rs @@ -69,14 +69,19 @@ impl EthashManager { Some(ref e) if *e == epoch => lights.recent.clone(), _ => match lights.prev_epoch.clone() { Some(e) if e == epoch => { - // swap - let t = lights.prev_epoch; - lights.prev_epoch = lights.recent_epoch; - lights.recent_epoch = t; - let t = lights.prev.clone(); - lights.prev = lights.recent.clone(); - lights.recent = t; - lights.recent.clone() + // don't swap if recent is newer. + if lights.recent_epoch > lights.prev_epoch { + None + } else { + // swap + let t = lights.prev_epoch; + lights.prev_epoch = lights.recent_epoch; + lights.recent_epoch = t; + let t = lights.prev.clone(); + lights.prev = lights.recent.clone(); + lights.recent = t; + lights.recent.clone() + } } _ => None, }, diff --git a/ethcore/src/blockchain/blockchain.rs b/ethcore/src/blockchain/blockchain.rs index 282039c16..d95c199ed 100644 --- a/ethcore/src/blockchain/blockchain.rs +++ b/ethcore/src/blockchain/blockchain.rs @@ -792,11 +792,10 @@ impl BlockChain { /// the chain and the child's parent is this block. /// /// Used in snapshots to glue the chunks together at the end. - pub fn add_child(&self, block_hash: H256, child_hash: H256) { + pub fn add_child(&self, batch: &mut DBTransaction, block_hash: H256, child_hash: H256) { let mut parent_details = self.block_details(&block_hash) .unwrap_or_else(|| panic!("Invalid block hash: {:?}", block_hash)); - let mut batch = self.db.transaction(); parent_details.children.push(child_hash); let mut update = HashMap::new(); @@ -807,8 +806,6 @@ impl BlockChain { batch.extend_with_cache(db::COL_EXTRA, &mut *write_details, update, CacheUpdatePolicy::Overwrite); self.cache_man.lock().note_used(CacheID::BlockDetails(block_hash)); - - self.db.write(batch).unwrap(); } #[cfg_attr(feature="dev", allow(similar_names))] diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index b59d892c5..388ea97e8 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -66,6 +66,7 @@ use snapshot::{self, io as snapshot_io}; use factory::Factories; use rlp::{View, UntrustedRlp}; use state_db::StateDB; +use rand::OsRng; // re-export pub use types::blockchain_info::BlockChainInfo; @@ -144,6 +145,7 @@ pub struct Client { last_hashes: RwLock>, factories: Factories, history: u64, + rng: Mutex, } impl Client { @@ -239,6 +241,7 @@ impl Client { last_hashes: RwLock::new(VecDeque::new()), factories: factories, history: history, + rng: Mutex::new(try!(OsRng::new().map_err(::util::UtilError::StdIo))), }; Ok(Arc::new(client)) } @@ -434,14 +437,26 @@ impl Client { /// Import a block with transaction receipts. /// The block is guaranteed to be the next best blocks in the first block sequence. /// Does no sealing or transaction validation. - fn import_old_block(&self, block_bytes: Bytes, receipts_bytes: Bytes) -> H256 { + fn import_old_block(&self, block_bytes: Bytes, receipts_bytes: Bytes) -> Result { let block = BlockView::new(&block_bytes); - let hash = block.header().hash(); + let header = block.header(); + let hash = header.hash(); let _import_lock = self.import_lock.lock(); { let _timer = PerfTimer::new("import_old_block"); + let mut rng = self.rng.lock(); let chain = self.chain.read(); + // verify block. + try!(::snapshot::verify_old_block( + &mut *rng, + &header, + &*self.engine, + &*chain, + Some(&block_bytes), + false, + )); + // Commit results let receipts = ::rlp::decode(&receipts_bytes); let mut batch = DBTransaction::new(&self.db.read()); @@ -451,7 +466,7 @@ impl Client { chain.commit(); } self.db.read().flush().expect("DB flush failed."); - hash + Ok(hash) } fn commit_block(&self, block: B, hash: &H256, block_data: &[u8]) -> ImportRoute where B: IsBlock + Drain { @@ -1036,7 +1051,7 @@ impl BlockChainClient for Client { return Err(BlockImportError::Block(BlockError::UnknownParent(header.parent_hash()))); } } - Ok(self.import_old_block(block_bytes, receipts_bytes)) + self.import_old_block(block_bytes, receipts_bytes).map_err(Into::into) } fn queue_info(&self) -> BlockQueueInfo { diff --git a/ethcore/src/snapshot/error.rs b/ethcore/src/snapshot/error.rs index acd9409f7..d634057dc 100644 --- a/ethcore/src/snapshot/error.rs +++ b/ethcore/src/snapshot/error.rs @@ -33,6 +33,12 @@ pub enum Error { BlockNotFound(H256), /// Incomplete chain. IncompleteChain, + /// Best block has wrong state root. + WrongStateRoot(H256, H256), + /// Wrong block hash. + WrongBlockHash(u64, H256, H256), + /// Too many blocks contained within the snapshot. + TooManyBlocks(u64, u64), /// Old starting block in a pruned database. OldBlockPrunedDB, /// Missing code. @@ -52,7 +58,11 @@ impl fmt::Display for Error { match *self { Error::InvalidStartingBlock(ref id) => write!(f, "Invalid starting block: {:?}", id), Error::BlockNotFound(ref hash) => write!(f, "Block not found in chain: {}", hash), - Error::IncompleteChain => write!(f, "Cannot create snapshot due to incomplete chain."), + Error::IncompleteChain => write!(f, "Incomplete blockchain."), + Error::WrongStateRoot(ref expected, ref found) => write!(f, "Final block has wrong state root. Expected {:?}, got {:?}", expected, found), + Error::WrongBlockHash(ref num, ref expected, ref found) => + write!(f, "Block {} had wrong hash. expected {:?}, got {:?}", num, expected, found), + Error::TooManyBlocks(ref expected, ref found) => write!(f, "Snapshot contained too many blocks. Expected {}, got {}", expected, found), Error::OldBlockPrunedDB => write!(f, "Attempted to create a snapshot at an old block while using \ a pruned database. Please re-run with the --pruning archive flag."), Error::MissingCode(ref missing) => write!(f, "Incomplete snapshot: {} contract codes not found.", missing.len()), diff --git a/ethcore/src/snapshot/mod.rs b/ethcore/src/snapshot/mod.rs index 86f921cf0..22c44ba3b 100644 --- a/ethcore/src/snapshot/mod.rs +++ b/ethcore/src/snapshot/mod.rs @@ -26,6 +26,7 @@ use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use account_db::{AccountDB, AccountDBMut}; use blockchain::{BlockChain, BlockProvider}; use engines::Engine; +use header::Header; use ids::BlockID; use views::BlockView; @@ -528,6 +529,20 @@ fn rebuild_accounts( /// Proportion of blocks which we will verify `PoW` for. const POW_VERIFY_RATE: f32 = 0.02; +/// Verify an old block with the given header, engine, blockchain, body. If `always` is set, it will perform +/// the fullest verification possible. If not, it will take a random sample to determine whether it will +/// do heavy or light verification. +pub fn verify_old_block(rng: &mut OsRng, header: &Header, engine: &Engine, chain: &BlockChain, body: Option<&[u8]>, always: bool) -> Result<(), ::error::Error> { + if always || rng.gen::() <= POW_VERIFY_RATE { + match chain.block_header(header.parent_hash()) { + Some(parent) => engine.verify_block_family(&header, &parent, body), + None => engine.verify_block_seal(&header), + } + } else { + engine.verify_block_basic(&header, body) + } +} + /// Rebuilds the blockchain from chunks. /// /// Does basic verification for all blocks, but `PoW` verification for some. @@ -543,17 +558,23 @@ pub struct BlockRebuilder { rng: OsRng, disconnected: Vec<(u64, H256)>, best_number: u64, + best_hash: H256, + best_root: H256, + fed_blocks: u64, } impl BlockRebuilder { /// Create a new BlockRebuilder. - pub fn new(chain: BlockChain, db: Arc, best_number: u64) -> Result { + pub fn new(chain: BlockChain, db: Arc, manifest: &ManifestData) -> Result { Ok(BlockRebuilder { chain: chain, db: db, rng: try!(OsRng::new()), disconnected: Vec::new(), - best_number: best_number, + best_number: manifest.block_number, + best_hash: manifest.block_hash, + best_root: manifest.state_root, + fed_blocks: 0, }) } @@ -566,9 +587,14 @@ impl BlockRebuilder { let rlp = UntrustedRlp::new(chunk); let item_count = rlp.item_count(); + let num_blocks = (item_count - 3) as u64; trace!(target: "snapshot", "restoring block chunk with {} blocks.", item_count - 3); + if self.fed_blocks + num_blocks > SNAPSHOT_BLOCKS { + return Err(Error::TooManyBlocks(SNAPSHOT_BLOCKS, self.fed_blocks).into()) + } + // todo: assert here that these values are consistent with chunks being in order. let mut cur_number = try!(rlp.val_at::(0)) + 1; let mut parent_hash = try!(rlp.val_at::(1)); @@ -585,14 +611,27 @@ impl BlockRebuilder { let block = try!(abridged_block.to_block(parent_hash, cur_number, receipts_root)); let block_bytes = block.rlp_bytes(With); + let is_best = cur_number == self.best_number; - if self.rng.gen::() <= POW_VERIFY_RATE { - try!(engine.verify_block_seal(&block.header)) - } else { - try!(engine.verify_block_basic(&block.header, Some(&block_bytes))); + if is_best { + if block.header.hash() != self.best_hash { + return Err(Error::WrongBlockHash(cur_number, self.best_hash, block.header.hash()).into()) + } + + if block.header.state_root() != &self.best_root { + return Err(Error::WrongStateRoot(self.best_root, *block.header.state_root()).into()) + } } - let is_best = cur_number == self.best_number; + try!(verify_old_block( + &mut self.rng, + &block.header, + engine, + &self.chain, + Some(&block_bytes), + is_best + )); + let mut batch = self.db.transaction(); // special-case the first block in each chunk. @@ -610,11 +649,15 @@ impl BlockRebuilder { cur_number += 1; } - Ok(item_count as u64 - 3) + self.fed_blocks += num_blocks; + + Ok(num_blocks) } - /// Glue together any disconnected chunks. To be called at the end. - pub fn glue_chunks(self) { + /// Glue together any disconnected chunks and check that the chain is complete. + pub fn finalize(self, canonical: HashMap) -> Result<(), Error> { + let mut batch = self.db.transaction(); + for (first_num, first_hash) in self.disconnected { let parent_num = first_num - 1; @@ -623,8 +666,23 @@ impl BlockRebuilder { // the first block of the first chunks has nothing to connect to. if let Some(parent_hash) = self.chain.block_hash(parent_num) { // if so, add the child to it. - self.chain.add_child(parent_hash, first_hash); + self.chain.add_child(&mut batch, parent_hash, first_hash); } } + self.db.write_buffered(batch); + + let best_number = self.best_number; + for num in (0..self.fed_blocks).map(|x| best_number - x) { + + let hash = try!(self.chain.block_hash(num).ok_or(Error::IncompleteChain)); + + if let Some(canon_hash) = canonical.get(&num).cloned() { + if canon_hash != hash { + return Err(Error::WrongBlockHash(num, canon_hash, hash)); + } + } + } + + Ok(()) } } diff --git a/ethcore/src/snapshot/service.rs b/ethcore/src/snapshot/service.rs index 9b66a5cdc..cc30a5c26 100644 --- a/ethcore/src/snapshot/service.rs +++ b/ethcore/src/snapshot/service.rs @@ -16,7 +16,7 @@ //! Snapshot network service implementation. -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use std::io::ErrorKind; use std::fs; use std::path::PathBuf; @@ -74,6 +74,7 @@ struct Restoration { snappy_buffer: Bytes, final_state_root: H256, guard: Guard, + canonical_hashes: HashMap, db: Arc, } @@ -99,7 +100,7 @@ impl Restoration { .map_err(UtilError::SimpleString))); let chain = BlockChain::new(Default::default(), params.genesis, raw_db.clone()); - let blocks = try!(BlockRebuilder::new(chain, raw_db.clone(), manifest.block_number)); + let blocks = try!(BlockRebuilder::new(chain, raw_db.clone(), &manifest)); let root = manifest.state_root.clone(); Ok(Restoration { @@ -112,6 +113,7 @@ impl Restoration { snappy_buffer: Vec::new(), final_state_root: root, guard: params.guard, + canonical_hashes: HashMap::new(), db: raw_db, }) } @@ -138,13 +140,18 @@ impl Restoration { try!(self.blocks.feed(&self.snappy_buffer[..len], engine)); if let Some(ref mut writer) = self.writer.as_mut() { - try!(writer.write_block_chunk(hash, chunk)); + try!(writer.write_block_chunk(hash, chunk)); } } Ok(()) } + // note canonical hashes. + fn note_canonical(&mut self, hashes: &[(u64, H256)]) { + self.canonical_hashes.extend(hashes.iter().cloned()); + } + // finish up restoration. fn finalize(self) -> Result<(), Error> { use util::trie::TrieError; @@ -161,8 +168,8 @@ impl Restoration { // check for missing code. try!(self.state.check_missing()); - // connect out-of-order chunks. - self.blocks.glue_chunks(); + // connect out-of-order chunks and verify chain integrity. + try!(self.blocks.finalize(self.canonical_hashes)); if let Some(writer) = self.writer { try!(writer.finish(self.manifest)); @@ -352,7 +359,8 @@ impl Service { // "Cancelled" is mincing words a bit -- what really happened // is that the state we were snapshotting got pruned out // before we could finish. - info!("Cancelled prematurely-started periodic snapshot."); + info!("Periodic snapshot failed: block state pruned.\ + Run with a longer `--pruning-history` or with `--no-periodic-snapshot`"); return Ok(()) } else { return Err(e); @@ -580,6 +588,14 @@ impl SnapshotService for Service { trace!("Error sending snapshot service message: {:?}", e); } } + + fn provide_canon_hashes(&self, canonical: &[(u64, H256)]) { + let mut rest = self.restoration.lock(); + + if let Some(ref mut rest) = rest.as_mut() { + rest.note_canonical(canonical); + } + } } impl Drop for Service { diff --git a/ethcore/src/snapshot/snapshot_service_trait.rs b/ethcore/src/snapshot/snapshot_service_trait.rs index 65448090f..42223f878 100644 --- a/ethcore/src/snapshot/snapshot_service_trait.rs +++ b/ethcore/src/snapshot/snapshot_service_trait.rs @@ -48,6 +48,10 @@ pub trait SnapshotService : Sync + Send { /// Feed a raw block chunk to the service to be processed asynchronously. /// no-op if currently restoring. fn restore_block_chunk(&self, hash: H256, chunk: Bytes); + + /// Give the restoration in-progress some canonical block hashes for + /// extra verification (performed at the end) + fn provide_canon_hashes(&self, canonical: &[(u64, H256)]); } impl IpcConfig for SnapshotService { } diff --git a/ethcore/src/snapshot/tests/blocks.rs b/ethcore/src/snapshot/tests/blocks.rs index 62c6ea2fe..12efcda77 100644 --- a/ethcore/src/snapshot/tests/blocks.rs +++ b/ethcore/src/snapshot/tests/blocks.rs @@ -26,6 +26,7 @@ use snapshot::io::{PackedReader, PackedWriter, SnapshotReader, SnapshotWriter}; use util::{Mutex, snappy}; use util::kvdb::{Database, DatabaseConfig}; +use std::collections::HashMap; use std::sync::Arc; fn chunk_and_restore(amount: u64) { @@ -58,18 +59,20 @@ fn chunk_and_restore(amount: u64) { // snapshot it. let writer = Mutex::new(PackedWriter::new(&snapshot_path).unwrap()); let block_hashes = chunk_blocks(&bc, best_hash, &writer, &Progress::default()).unwrap(); - writer.into_inner().finish(::snapshot::ManifestData { + let manifest = ::snapshot::ManifestData { state_hashes: Vec::new(), block_hashes: block_hashes, - state_root: Default::default(), + state_root: ::util::sha3::SHA3_NULL_RLP, block_number: amount, block_hash: best_hash, - }).unwrap(); + }; + + writer.into_inner().finish(manifest.clone()).unwrap(); // restore it. let new_db = Arc::new(Database::open(&db_cfg, new_path.as_str()).unwrap()); let new_chain = BlockChain::new(Default::default(), &genesis, new_db.clone()); - let mut rebuilder = BlockRebuilder::new(new_chain, new_db.clone(), amount).unwrap(); + let mut rebuilder = BlockRebuilder::new(new_chain, new_db.clone(), &manifest).unwrap(); let reader = PackedReader::new(&snapshot_path).unwrap().unwrap(); let engine = ::engines::NullEngine::new(Default::default(), Default::default()); for chunk_hash in &reader.manifest().block_hashes { @@ -78,7 +81,7 @@ fn chunk_and_restore(amount: u64) { rebuilder.feed(&chunk, &engine).unwrap(); } - rebuilder.glue_chunks(); + rebuilder.finalize(HashMap::new()).unwrap(); // and test it. let new_chain = BlockChain::new(Default::default(), &genesis, new_db); diff --git a/sync/src/tests/snapshot.rs b/sync/src/tests/snapshot.rs index 58b7ec786..813513e84 100644 --- a/sync/src/tests/snapshot.rs +++ b/sync/src/tests/snapshot.rs @@ -23,6 +23,7 @@ use super::helpers::*; pub struct TestSnapshotService { manifest: Option, chunks: HashMap, + canon_hashes: Mutex>, restoration_manifest: Mutex>, state_restoration_chunks: Mutex>, @@ -34,6 +35,7 @@ impl TestSnapshotService { TestSnapshotService { manifest: None, chunks: HashMap::new(), + canon_hashes: Mutex::new(HashMap::new()), restoration_manifest: Mutex::new(None), state_restoration_chunks: Mutex::new(HashMap::new()), block_restoration_chunks: Mutex::new(HashMap::new()), @@ -57,6 +59,7 @@ impl TestSnapshotService { TestSnapshotService { manifest: Some(manifest), chunks: chunks, + canon_hashes: Mutex::new(HashMap::new()), restoration_manifest: Mutex::new(None), state_restoration_chunks: Mutex::new(HashMap::new()), block_restoration_chunks: Mutex::new(HashMap::new()), @@ -110,6 +113,10 @@ impl SnapshotService for TestSnapshotService { self.block_restoration_chunks.lock().insert(hash, chunk); } } + + fn provide_canon_hashes(&self, hashes: &[(u64, H256)]) { + self.canon_hashes.lock().extend(hashes.iter().cloned()); + } } #[test] From 0f0334275eded44ae1646501d5fc73f3d1404889 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Fri, 28 Oct 2016 16:42:24 +0200 Subject: [PATCH 42/47] Validating minimal required gas for a transaction (#2937) * Validating minimal required gas for a transaction * Adding RPC case and note * Fixing whitespace [ci skip] --- ethcore/src/client/client.rs | 19 +- ethcore/src/client/test_client.rs | 8 +- ethcore/src/client/traits.rs | 5 +- ethcore/src/error.rs | 9 + ethcore/src/miner/banning_queue.rs | 28 ++- ethcore/src/miner/miner.rs | 6 +- ethcore/src/miner/transaction_queue.rs | 270 ++++++++++++++----------- rpc/src/v1/helpers/errors.rs | 3 + 8 files changed, 218 insertions(+), 130 deletions(-) diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 388ea97e8..4709955d1 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -60,7 +60,7 @@ use receipt::LocalizedReceipt; use trace::{TraceDB, ImportRequest as TraceImportRequest, LocalizedTrace, Database as TraceDatabase}; use trace; use trace::FlatTransactionTraces; -use evm::Factory as EvmFactory; +use evm::{Factory as EvmFactory, Schedule}; use miner::{Miner, MinerService}; use snapshot::{self, io as snapshot_io}; use factory::Factories; @@ -1156,6 +1156,23 @@ impl BlockChainClient for Client { } impl MiningBlockChainClient for Client { + + fn latest_schedule(&self) -> Schedule { + let header_data = self.best_block_header(); + let view = HeaderView::new(&header_data); + + let env_info = EnvInfo { + number: view.number(), + author: view.author(), + timestamp: view.timestamp(), + difficulty: view.difficulty(), + last_hashes: self.build_last_hashes(view.hash()), + gas_used: U256::default(), + gas_limit: view.gas_limit(), + }; + self.engine.schedule(&env_info) + } + fn prepare_open_block(&self, author: Address, gas_range_target: (U256, U256), extra_data: Bytes) -> OpenBlock { let engine = &*self.engine; let chain = self.chain.read(); diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index e32a074bb..11a9286da 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -34,7 +34,7 @@ use log_entry::LocalizedLogEntry; use receipt::{Receipt, LocalizedReceipt}; use blockchain::extras::BlockReceipts; use error::{ImportResult}; -use evm::{Factory as EvmFactory, VMType}; +use evm::{Factory as EvmFactory, VMType, Schedule}; use miner::{Miner, MinerService, TransactionImportResult}; use spec::Spec; @@ -137,7 +137,7 @@ impl TestBlockChainClient { client.genesis_hash = client.last_hash.read().clone(); client } - + /// Set the transaction receipt result pub fn set_transaction_receipt(&self, id: TransactionID, receipt: LocalizedReceipt) { self.receipts.write().insert(id, receipt); @@ -306,6 +306,10 @@ pub fn get_temp_state_db() -> GuardedTempResult { } impl MiningBlockChainClient for TestBlockChainClient { + fn latest_schedule(&self) -> Schedule { + Schedule::new_homestead_gas_fix() + } + fn prepare_open_block(&self, author: Address, gas_range_target: (U256, U256), extra_data: Bytes) -> OpenBlock { let engine = &*self.spec.engine; let genesis_header = self.spec.genesis_header(); diff --git a/ethcore/src/client/traits.rs b/ethcore/src/client/traits.rs index 0bc9e70fa..700b88f8b 100644 --- a/ethcore/src/client/traits.rs +++ b/ethcore/src/client/traits.rs @@ -27,7 +27,7 @@ use views::{BlockView}; use error::{ImportResult, CallError}; use receipt::LocalizedReceipt; use trace::LocalizedTrace; -use evm::Factory as EvmFactory; +use evm::{Factory as EvmFactory, Schedule}; use types::ids::*; use types::trace_filter::Filter as TraceFilter; use executive::Executed; @@ -236,6 +236,9 @@ pub trait MiningBlockChainClient : BlockChainClient { /// Import sealed block. Skips all verifications. fn import_sealed_block(&self, block: SealedBlock) -> ImportResult; + + /// Returns latest schedule. + fn latest_schedule(&self) -> Schedule; } impl IpcConfig for BlockChainClient { } diff --git a/ethcore/src/error.rs b/ethcore/src/error.rs index 40f4c6d28..04a0920fa 100644 --- a/ethcore/src/error.rs +++ b/ethcore/src/error.rs @@ -47,6 +47,13 @@ pub enum TransactionError { /// Transaction gas price got: U256, }, + /// Transaction's gas is below currently set minimal gas requirement. + InsufficientGas { + /// Minimal expected gas + minimal: U256, + /// Transaction gas + got: U256, + }, /// Sender doesn't have enough funds to pay for this transaction InsufficientBalance { /// Senders balance @@ -81,6 +88,8 @@ impl fmt::Display for TransactionError { LimitReached => "Transaction limit reached".into(), InsufficientGasPrice { minimal, got } => format!("Insufficient gas price. Min={}, Given={}", minimal, got), + InsufficientGas { minimal, got } => + format!("Insufficient gas. Min={}, Given={}", minimal, got), InsufficientBalance { balance, cost } => format!("Insufficient balance for transaction. Balance={}, Cost={}", balance, cost), diff --git a/ethcore/src/miner/banning_queue.rs b/ethcore/src/miner/banning_queue.rs index 0ad99b008..0329503bf 100644 --- a/ethcore/src/miner/banning_queue.rs +++ b/ethcore/src/miner/banning_queue.rs @@ -75,11 +75,15 @@ impl BanningTransactionQueue { /// Add to the queue taking bans into consideration. /// May reject transaction because of the banlist. - pub fn add_with_banlist( + pub fn add_with_banlist( &mut self, transaction: SignedTransaction, account_details: &F, - ) -> Result where F: Fn(&Address) -> AccountDetails { + gas_estimator: &G, + ) -> Result where + F: Fn(&Address) -> AccountDetails, + G: Fn(&SignedTransaction) -> U256, + { if let Threshold::BanAfter(threshold) = self.ban_threshold { // NOTE In all checks use direct query to avoid increasing ban timeout. @@ -111,7 +115,7 @@ impl BanningTransactionQueue { } } } - self.queue.add(transaction, account_details, TransactionOrigin::External) + self.queue.add(transaction, TransactionOrigin::External, account_details, gas_estimator) } /// Ban transaction with given hash. @@ -228,6 +232,10 @@ mod tests { } } + fn gas_required(_tx: &SignedTransaction) -> U256 { + 0.into() + } + fn transaction(action: Action) -> SignedTransaction { let keypair = Random.generate().unwrap(); Transaction { @@ -255,7 +263,7 @@ mod tests { let mut txq = queue(); // when - txq.queue().add(tx, &default_account_details, TransactionOrigin::External).unwrap(); + txq.queue().add(tx, TransactionOrigin::External, &default_account_details, &gas_required).unwrap(); // then // should also deref to queue @@ -271,12 +279,12 @@ mod tests { let banlist1 = txq.ban_sender(tx.sender().unwrap()); assert!(!banlist1, "Threshold not reached yet."); // Insert once - let import1 = txq.add_with_banlist(tx.clone(), &default_account_details).unwrap(); + let import1 = txq.add_with_banlist(tx.clone(), &default_account_details, &gas_required).unwrap(); assert_eq!(import1, TransactionImportResult::Current); // when let banlist2 = txq.ban_sender(tx.sender().unwrap()); - let import2 = txq.add_with_banlist(tx.clone(), &default_account_details); + let import2 = txq.add_with_banlist(tx.clone(), &default_account_details, &gas_required); // then assert!(banlist2, "Threshold should be reached - banned."); @@ -295,12 +303,12 @@ mod tests { let banlist1 = txq.ban_recipient(recipient); assert!(!banlist1, "Threshold not reached yet."); // Insert once - let import1 = txq.add_with_banlist(tx.clone(), &default_account_details).unwrap(); + let import1 = txq.add_with_banlist(tx.clone(), &default_account_details, &gas_required).unwrap(); assert_eq!(import1, TransactionImportResult::Current); // when let banlist2 = txq.ban_recipient(recipient); - let import2 = txq.add_with_banlist(tx.clone(), &default_account_details); + let import2 = txq.add_with_banlist(tx.clone(), &default_account_details, &gas_required); // then assert!(banlist2, "Threshold should be reached - banned."); @@ -317,12 +325,12 @@ mod tests { let banlist1 = txq.ban_codehash(codehash); assert!(!banlist1, "Threshold not reached yet."); // Insert once - let import1 = txq.add_with_banlist(tx.clone(), &default_account_details).unwrap(); + let import1 = txq.add_with_banlist(tx.clone(), &default_account_details, &gas_required).unwrap(); assert_eq!(import1, TransactionImportResult::Current); // when let banlist2 = txq.ban_codehash(codehash); - let import2 = txq.add_with_banlist(tx.clone(), &default_account_details); + let import2 = txq.add_with_banlist(tx.clone(), &default_account_details, &gas_required); // then assert!(banlist2, "Threshold should be reached - banned."); diff --git a/ethcore/src/miner/miner.rs b/ethcore/src/miner/miner.rs index 47464b36f..d36869a74 100644 --- a/ethcore/src/miner/miner.rs +++ b/ethcore/src/miner/miner.rs @@ -562,13 +562,15 @@ impl Miner { balance: chain.latest_balance(a), }; + let schedule = chain.latest_schedule(); + let gas_required = |tx: &SignedTransaction| tx.gas_required(&schedule).into(); transactions.into_iter() .map(|tx| match origin { TransactionOrigin::Local | TransactionOrigin::RetractedBlock => { - transaction_queue.add(tx, &fetch_account, origin) + transaction_queue.add(tx, origin, &fetch_account, &gas_required) }, TransactionOrigin::External => { - transaction_queue.add_with_banlist(tx, &fetch_account) + transaction_queue.add_with_banlist(tx, &fetch_account, &gas_required) } }) .collect() diff --git a/ethcore/src/miner/transaction_queue.rs b/ethcore/src/miner/transaction_queue.rs index 40150b78d..f8baf8989 100644 --- a/ethcore/src/miner/transaction_queue.rs +++ b/ethcore/src/miner/transaction_queue.rs @@ -48,10 +48,11 @@ //! nonce: U256::from(10), //! balance: U256::from(1_000_000), //! }; +//! let gas_estimator = |_tx: &SignedTransaction| 2.into(); //! //! let mut txq = TransactionQueue::default(); -//! txq.add(st2.clone(), &default_account_details, TransactionOrigin::External).unwrap(); -//! txq.add(st1.clone(), &default_account_details, TransactionOrigin::External).unwrap(); +//! txq.add(st2.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); +//! txq.add(st1.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); //! //! // Check status //! assert_eq!(txq.status().pending, 2); @@ -593,9 +594,20 @@ impl TransactionQueue { } } - /// Add signed transaction to queue to be verified and imported - pub fn add(&mut self, tx: SignedTransaction, fetch_account: &T, origin: TransactionOrigin) -> Result - where T: Fn(&Address) -> AccountDetails { + /// Add signed transaction to queue to be verified and imported. + /// + /// NOTE fetch_account and gas_estimator should be cheap to compute + /// otherwise it might open up an attack vector. + pub fn add( + &mut self, + tx: SignedTransaction, + origin: TransactionOrigin, + fetch_account: &F, + gas_estimator: &G, + ) -> Result where + F: Fn(&Address) -> AccountDetails, + G: Fn(&SignedTransaction) -> U256, + { if tx.gas_price < self.minimal_gas_price && origin != TransactionOrigin::Local { trace!(target: "txqueue", @@ -626,8 +638,6 @@ impl TransactionQueue { })); } - try!(tx.check_low_s()); - if tx.gas > self.gas_limit || tx.gas > self.tx_gas_limit { trace!(target: "txqueue", "Dropping transaction above gas limit: {:?} ({} > min({}, {}))", @@ -643,6 +653,24 @@ impl TransactionQueue { })); } + let minimal_gas = gas_estimator(&tx); + if tx.gas < minimal_gas { + trace!(target: "txqueue", + "Dropping transaction with insufficient gas: {:?} ({} > {})", + tx.hash(), + tx.gas, + minimal_gas, + ); + + return Err(Error::Transaction(TransactionError::InsufficientGas { + minimal: minimal_gas, + got: tx.gas, + })); + } + + // Verify signature + try!(tx.check_low_s()); + let vtx = try!(VerifiedTransaction::new(tx, origin)); let client_account = fetch_account(&vtx.sender()); @@ -905,16 +933,6 @@ impl TransactionQueue { let nonce = tx.nonce(); let hash = tx.hash(); - { - // Rough size sanity check - let gas = &tx.transaction.gas; - if U256::from(tx.transaction.data.len()) > *gas { - // Droping transaction - trace!(target: "txqueue", "Dropping oversized transaction: {:?} (gas: {} < size {})", hash, gas, tx.transaction.data.len()); - return Err(TransactionError::LimitReached); - } - } - // The transaction might be old, let's check that. // This has to be the first test, otherwise calculating // nonce height would result in overflow. @@ -1104,6 +1122,10 @@ mod test { } } + fn gas_estimator(_tx: &SignedTransaction) -> U256 { + U256::zero() + } + fn new_tx_pair(nonce: U256, gas_price: U256, nonce_increment: U256, gas_price_increment: U256) -> (SignedTransaction, SignedTransaction) { let tx1 = new_unsigned_tx(nonce, default_gas_val(), gas_price); let tx2 = new_unsigned_tx(nonce + nonce_increment, default_gas_val(), gas_price + gas_price_increment); @@ -1155,14 +1177,14 @@ mod test { let (tx1, tx2) = new_tx_pair(123.into(), 1.into(), 1.into(), 0.into()); let sender = tx1.sender().unwrap(); let nonce = tx1.nonce; - txq.add(tx1.clone(), &default_account_details, TransactionOrigin::External).unwrap(); - txq.add(tx2.clone(), &default_account_details, TransactionOrigin::External).unwrap(); + txq.add(tx1.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx2.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); assert_eq!(txq.status().pending, 2); assert_eq!(txq.last_nonce(&sender), Some(nonce + 1.into())); // when let tx = new_tx(123.into(), 1.into()); - let res = txq.add(tx.clone(), &default_account_details, TransactionOrigin::External); + let res = txq.add(tx.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator); // then // No longer the case as we don't even consider a transaction that isn't above a full @@ -1318,12 +1340,12 @@ mod test { !U256::zero() }; // First insert one transaction to future - let res = txq.add(tx, &prev_nonce, TransactionOrigin::External); + let res = txq.add(tx, TransactionOrigin::External, &prev_nonce, &gas_estimator); assert_eq!(res.unwrap(), TransactionImportResult::Future); assert_eq!(txq.status().future, 1); // now import second transaction to current - let res = txq.add(tx2.clone(), &default_account_details, TransactionOrigin::External); + let res = txq.add(tx2.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator); // and then there should be only one transaction in current (the one with higher gas_price) assert_eq!(res.unwrap(), TransactionImportResult::Current); @@ -1343,12 +1365,12 @@ mod test { !U256::zero() }; // First insert one transaction to future - let res = txq.add(tx.clone(), &prev_nonce, TransactionOrigin::External); + let res = txq.add(tx.clone(), TransactionOrigin::External, &prev_nonce, &gas_estimator); assert_eq!(res.unwrap(), TransactionImportResult::Future); assert_eq!(txq.status().future, 1); // now import second transaction to current - let res = txq.add(tx2.clone(), &default_account_details, TransactionOrigin::External); + let res = txq.add(tx2.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator); // then assert_eq!(res.unwrap(), TransactionImportResult::Current); @@ -1367,7 +1389,7 @@ mod test { let tx = new_tx_default(); // when - let res = txq.add(tx, &default_account_details, TransactionOrigin::External); + let res = txq.add(tx, TransactionOrigin::External, &default_account_details, &gas_estimator); // then assert_eq!(res.unwrap(), TransactionImportResult::Current); @@ -1386,10 +1408,10 @@ mod test { txq.set_minimal_gas_price(15.into()); // when - let res1 = txq.add(tx1, &default_account_details, TransactionOrigin::External); - let res2 = txq.add(tx2, &default_account_details, TransactionOrigin::External); - let res3 = txq.add(tx3, &default_account_details, TransactionOrigin::External); - let res4 = txq.add(tx4, &default_account_details, TransactionOrigin::External); + let res1 = txq.add(tx1, TransactionOrigin::External, &default_account_details, &gas_estimator); + let res2 = txq.add(tx2, TransactionOrigin::External, &default_account_details, &gas_estimator); + let res3 = txq.add(tx3, TransactionOrigin::External, &default_account_details, &gas_estimator); + let res4 = txq.add(tx4, TransactionOrigin::External, &default_account_details, &gas_estimator); // then assert_eq!(res1.unwrap(), TransactionImportResult::Current); @@ -1420,10 +1442,10 @@ mod test { txq.set_minimal_gas_price(15.into()); // when - let res1 = txq.add(tx1, &default_account_details, TransactionOrigin::External); - let res2 = txq.add(tx2, &default_account_details, TransactionOrigin::External); - let res3 = txq.add(tx3, &default_account_details, TransactionOrigin::External); - let res4 = txq.add(tx4, &default_account_details, TransactionOrigin::External); + let res1 = txq.add(tx1, TransactionOrigin::External, &default_account_details, &gas_estimator); + let res2 = txq.add(tx2, TransactionOrigin::External, &default_account_details, &gas_estimator); + let res3 = txq.add(tx3, TransactionOrigin::External, &default_account_details, &gas_estimator); + let res4 = txq.add(tx4, TransactionOrigin::External, &default_account_details, &gas_estimator); // then assert_eq!(res1.unwrap(), TransactionImportResult::Current); @@ -1466,7 +1488,7 @@ mod test { txq.set_gas_limit(limit); // when - let res = txq.add(tx, &default_account_details, TransactionOrigin::External); + let res = txq.add(tx, TransactionOrigin::External, &default_account_details, &gas_estimator); // then assert_eq!(unwrap_tx_err(res), TransactionError::GasLimitExceeded { @@ -1490,7 +1512,7 @@ mod test { }; // when - let res = txq.add(tx, &account, TransactionOrigin::External); + let res = txq.add(tx, TransactionOrigin::External, &account, &gas_estimator); // then assert_eq!(unwrap_tx_err(res), TransactionError::InsufficientBalance { @@ -1510,7 +1532,7 @@ mod test { txq.set_minimal_gas_price(tx.gas_price + U256::one()); // when - let res = txq.add(tx, &default_account_details, TransactionOrigin::External); + let res = txq.add(tx, TransactionOrigin::External, &default_account_details, &gas_estimator); // then assert_eq!(unwrap_tx_err(res), TransactionError::InsufficientGasPrice { @@ -1530,7 +1552,7 @@ mod test { txq.set_minimal_gas_price(tx.gas_price + U256::one()); // when - let res = txq.add(tx, &default_account_details, TransactionOrigin::Local); + let res = txq.add(tx, TransactionOrigin::Local, &default_account_details, &gas_estimator); // then assert_eq!(res.unwrap(), TransactionImportResult::Current); @@ -1560,7 +1582,7 @@ mod test { rlp::decode(s.as_raw()) }; // when - let res = txq.add(stx, &default_account_details, TransactionOrigin::External); + let res = txq.add(stx, TransactionOrigin::External, &default_account_details, &gas_estimator); // then assert!(res.is_err()); @@ -1574,8 +1596,8 @@ mod test { let (tx, tx2) = new_tx_pair_default(1.into(), 0.into()); // when - txq.add(tx.clone(), &default_account_details, TransactionOrigin::External).unwrap(); - txq.add(tx2.clone(), &default_account_details, TransactionOrigin::External).unwrap(); + txq.add(tx.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx2.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); // then let top = txq.top_transactions(); @@ -1594,9 +1616,9 @@ mod test { // when // first insert the one with higher gas price - txq.add(tx2.clone(), &default_account_details, TransactionOrigin::External).unwrap(); + txq.add(tx2.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); // then the one with lower gas price, but local - txq.add(tx.clone(), &default_account_details, TransactionOrigin::Local).unwrap(); + txq.add(tx.clone(), TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(); // then let top = txq.top_transactions(); @@ -1615,9 +1637,9 @@ mod test { // when // first insert local one with higher gas price - txq.add(tx2.clone(), &default_account_details, TransactionOrigin::Local).unwrap(); + txq.add(tx2.clone(), TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(); // then the one with lower gas price, but from retracted block - txq.add(tx.clone(), &default_account_details, TransactionOrigin::RetractedBlock).unwrap(); + txq.add(tx.clone(), TransactionOrigin::RetractedBlock, &default_account_details, &gas_estimator).unwrap(); // then let top = txq.top_transactions(); @@ -1633,8 +1655,8 @@ mod test { let (tx, tx2) = new_tx_pair_default(1.into(), 0.into()); // when - txq.add(tx.clone(), &default_account_details, TransactionOrigin::External).unwrap(); - txq.add(tx2.clone(), &default_account_details, TransactionOrigin::Local).unwrap(); + txq.add(tx.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx2.clone(), TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(); // then let top = txq.top_transactions(); @@ -1653,10 +1675,10 @@ mod test { let (tx1, tx2) = new_tx_pair_with_gas_price_increment(3.into()); // insert everything - txq.add(txa.clone(), &prev_nonce, TransactionOrigin::External).unwrap(); - txq.add(txb.clone(), &prev_nonce, TransactionOrigin::External).unwrap(); - txq.add(tx1.clone(), &prev_nonce, TransactionOrigin::External).unwrap(); - txq.add(tx2.clone(), &prev_nonce, TransactionOrigin::External).unwrap(); + txq.add(txa.clone(), TransactionOrigin::External, &prev_nonce, &gas_estimator).unwrap(); + txq.add(txb.clone(), TransactionOrigin::External, &prev_nonce, &gas_estimator).unwrap(); + txq.add(tx1.clone(), TransactionOrigin::External, &prev_nonce, &gas_estimator).unwrap(); + txq.add(tx2.clone(), TransactionOrigin::External, &prev_nonce, &gas_estimator).unwrap(); assert_eq!(txq.status().future, 4); @@ -1682,10 +1704,10 @@ mod test { let (tx1, tx2) = new_tx_pair_with_gas_price_increment(3.into()); // insert everything - txq.add(txa.clone(), &default_account_details, TransactionOrigin::External).unwrap(); - txq.add(txb.clone(), &default_account_details, TransactionOrigin::External).unwrap(); - txq.add(tx1.clone(), &default_account_details, TransactionOrigin::External).unwrap(); - txq.add(tx2.clone(), &default_account_details, TransactionOrigin::External).unwrap(); + txq.add(txa.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(txb.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx1.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx2.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); let top = txq.top_transactions(); assert_eq!(top[0], tx1); @@ -1714,8 +1736,8 @@ mod test { let (tx, tx2) = new_tx_pair_default(1.into(), 0.into()); // when - txq.add(tx.clone(), &default_account_details, TransactionOrigin::External).unwrap(); - txq.add(tx2.clone(), &default_account_details, TransactionOrigin::External).unwrap(); + txq.add(tx.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx2.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); // then let top = txq.pending_hashes(); @@ -1732,8 +1754,8 @@ mod test { let (tx, tx2) = new_tx_pair_default(2.into(), 0.into()); // when - let res1 = txq.add(tx.clone(), &default_account_details, TransactionOrigin::External).unwrap(); - let res2 = txq.add(tx2.clone(), &default_account_details, TransactionOrigin::External).unwrap(); + let res1 = txq.add(tx.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + let res2 = txq.add(tx2.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); // then assert_eq!(res1, TransactionImportResult::Current); @@ -1756,8 +1778,8 @@ mod test { let mut txq = TransactionQueue::default(); let (tx, tx2) = new_tx_pair_default(1.into(), 0.into()); - txq.add(tx.clone(), &prev_nonce, TransactionOrigin::External).unwrap(); - txq.add(tx2.clone(), &prev_nonce, TransactionOrigin::External).unwrap(); + txq.add(tx.clone(), TransactionOrigin::External, &prev_nonce, &gas_estimator).unwrap(); + txq.add(tx2.clone(), TransactionOrigin::External, &prev_nonce, &gas_estimator).unwrap(); assert_eq!(txq.status().future, 2); // when @@ -1779,13 +1801,13 @@ mod test { let tx1 = new_unsigned_tx(124.into(), default_gas_val(), 1.into()).sign(secret); let tx2 = new_unsigned_tx(125.into(), default_gas_val(), 1.into()).sign(secret); - txq.add(tx, &default_account_details, TransactionOrigin::External).unwrap(); + txq.add(tx, TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); assert_eq!(txq.status().pending, 1); - txq.add(tx2, &default_account_details, TransactionOrigin::External).unwrap(); + txq.add(tx2, TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); assert_eq!(txq.status().future, 1); // when - txq.add(tx1, &default_account_details, TransactionOrigin::External).unwrap(); + txq.add(tx1, TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); // then let stats = txq.status(); @@ -1801,8 +1823,8 @@ mod test { // given let mut txq2 = TransactionQueue::default(); let (tx, tx2) = new_tx_pair_default(3.into(), 0.into()); - txq2.add(tx.clone(), &default_account_details, TransactionOrigin::External).unwrap(); - txq2.add(tx2.clone(), &default_account_details, TransactionOrigin::External).unwrap(); + txq2.add(tx.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq2.add(tx2.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); assert_eq!(txq2.status().pending, 1); assert_eq!(txq2.status().future, 1); @@ -1823,10 +1845,10 @@ mod test { let mut txq = TransactionQueue::default(); let (tx, tx2) = new_tx_pair_default(1.into(), 0.into()); let tx3 = new_tx_default(); - txq.add(tx2.clone(), &default_account_details, TransactionOrigin::External).unwrap(); + txq.add(tx2.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); assert_eq!(txq.status().future, 1); - txq.add(tx3.clone(), &default_account_details, TransactionOrigin::External).unwrap(); - txq.add(tx.clone(), &default_account_details, TransactionOrigin::External).unwrap(); + txq.add(tx3.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); assert_eq!(txq.status().pending, 3); // when @@ -1845,8 +1867,8 @@ mod test { let (tx, tx2) = new_tx_pair_default(1.into(), 0.into()); // add - txq.add(tx2.clone(), &default_account_details, TransactionOrigin::External).unwrap(); - txq.add(tx.clone(), &default_account_details, TransactionOrigin::External).unwrap(); + txq.add(tx2.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); let stats = txq.status(); assert_eq!(stats.pending, 2); @@ -1865,11 +1887,11 @@ mod test { let (tx, tx2) = new_tx_pair_default(1.into(), 0.into()); let sender = tx.sender().unwrap(); let nonce = tx.nonce; - txq.add(tx.clone(), &default_account_details, TransactionOrigin::External).unwrap(); + txq.add(tx.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); assert_eq!(txq.status().pending, 1); // when - let res = txq.add(tx2.clone(), &default_account_details, TransactionOrigin::External); + let res = txq.add(tx2.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator); // then let t = txq.top_transactions(); @@ -1886,14 +1908,14 @@ mod test { txq.current.set_limit(10); let (tx1, tx2) = new_tx_pair_default(4.into(), 1.into()); let (tx3, tx4) = new_tx_pair_default(4.into(), 2.into()); - txq.add(tx1.clone(), &default_account_details, TransactionOrigin::External).unwrap(); - txq.add(tx3.clone(), &default_account_details, TransactionOrigin::External).unwrap(); + txq.add(tx1.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx3.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); assert_eq!(txq.status().pending, 2); // when - txq.add(tx2.clone(), &default_account_details, TransactionOrigin::External).unwrap(); + txq.add(tx2.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); assert_eq!(txq.status().future, 1); - txq.add(tx4.clone(), &default_account_details, TransactionOrigin::External).unwrap(); + txq.add(tx4.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); // then assert_eq!(txq.status().future, 1); @@ -1904,11 +1926,11 @@ mod test { let mut txq = TransactionQueue::with_limits(PrioritizationStrategy::GasPriceOnly, 100, default_gas_val() * U256::from(2), !U256::zero()); let (tx1, tx2) = new_tx_pair_default(U256::from(1), U256::from(1)); let (tx3, tx4) = new_tx_pair_default(U256::from(1), U256::from(2)); - txq.add(tx1.clone(), &default_account_details, TransactionOrigin::External).unwrap(); - txq.add(tx2.clone(), &default_account_details, TransactionOrigin::External).unwrap(); - txq.add(tx3.clone(), &default_account_details, TransactionOrigin::External).unwrap(); + txq.add(tx1.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx2.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx3.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); // limited by gas - txq.add(tx4.clone(), &default_account_details, TransactionOrigin::External).unwrap_err(); + txq.add(tx4.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap_err(); assert_eq!(txq.status().pending, 2); } @@ -1918,13 +1940,13 @@ mod test { let (tx1, tx2) = new_tx_pair_default(U256::from(1), U256::from(1)); let (tx3, tx4) = new_tx_pair_default(U256::from(1), U256::from(2)); let (tx5, tx6) = new_tx_pair_default(U256::from(1), U256::from(2)); - txq.add(tx1.clone(), &default_account_details, TransactionOrigin::Local).unwrap(); - txq.add(tx2.clone(), &default_account_details, TransactionOrigin::Local).unwrap(); - txq.add(tx5.clone(), &default_account_details, TransactionOrigin::External).unwrap(); + txq.add(tx1.clone(), TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx2.clone(), TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx5.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); // Not accepted because of limit - txq.add(tx6.clone(), &default_account_details, TransactionOrigin::External).unwrap_err(); - txq.add(tx3.clone(), &default_account_details, TransactionOrigin::Local).unwrap(); - txq.add(tx4.clone(), &default_account_details, TransactionOrigin::Local).unwrap(); + txq.add(tx6.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap_err(); + txq.add(tx3.clone(), TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx4.clone(), TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(); assert_eq!(txq.status().pending, 4); } @@ -1936,7 +1958,7 @@ mod test { let fetch_last_nonce = |_a: &Address| AccountDetails { nonce: last_nonce, balance: !U256::zero() }; // when - let res = txq.add(tx, &fetch_last_nonce, TransactionOrigin::External); + let res = txq.add(tx, TransactionOrigin::External, &fetch_last_nonce, &gas_estimator); // then assert_eq!(unwrap_tx_err(res), TransactionError::Old); @@ -1952,12 +1974,12 @@ mod test { balance: !U256::zero() }; let mut txq = TransactionQueue::default(); let (_tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); - txq.add(tx2.clone(), &default_account_details, TransactionOrigin::External).unwrap(); + txq.add(tx2.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); assert_eq!(txq.status().future, 1); assert_eq!(txq.status().pending, 0); // when - let res = txq.add(tx2.clone(), &nonce, TransactionOrigin::External); + let res = txq.add(tx2.clone(), TransactionOrigin::External, &nonce, &gas_estimator); // then assert_eq!(unwrap_tx_err(res), TransactionError::AlreadyImported); @@ -1971,15 +1993,15 @@ mod test { // given let mut txq = TransactionQueue::default(); let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); - txq.add(tx1.clone(), &default_account_details, TransactionOrigin::External).unwrap(); - txq.add(tx2.clone(), &default_account_details, TransactionOrigin::External).unwrap(); + txq.add(tx1.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx2.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); assert_eq!(txq.status().pending, 2); // when txq.remove_invalid(&tx1.hash(), &default_account_details); assert_eq!(txq.status().pending, 0); assert_eq!(txq.status().future, 1); - txq.add(tx1.clone(), &default_account_details, TransactionOrigin::External).unwrap(); + txq.add(tx1.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); // then let stats = txq.status(); @@ -1993,10 +2015,10 @@ mod test { let mut txq = TransactionQueue::default(); let (tx, tx2) = new_tx_pair_default(1.into(), 0.into()); let tx3 = new_tx_default(); - txq.add(tx2.clone(), &default_account_details, TransactionOrigin::External).unwrap(); + txq.add(tx2.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); assert_eq!(txq.status().future, 1); - txq.add(tx3.clone(), &default_account_details, TransactionOrigin::External).unwrap(); - txq.add(tx.clone(), &default_account_details, TransactionOrigin::External).unwrap(); + txq.add(tx3.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx.clone(), TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); assert_eq!(txq.status().pending, 3); // when @@ -2023,8 +2045,8 @@ mod test { }; // when - txq.add(tx, &default_account_details, TransactionOrigin::External).unwrap(); - txq.add(tx2, &default_account_details, TransactionOrigin::External).unwrap(); + txq.add(tx, TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx2, TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); // then let stats = txq.status(); @@ -2051,10 +2073,10 @@ mod test { }; // when - txq.add(tx1, &default_account_details, TransactionOrigin::External).unwrap(); - txq.add(tx2, &default_account_details, TransactionOrigin::External).unwrap(); + txq.add(tx1, TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx2, TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); assert_eq!(txq.status().future, 1); - txq.add(tx0, &default_account_details, TransactionOrigin::External).unwrap(); + txq.add(tx0, TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(); // then let stats = txq.status(); @@ -2072,8 +2094,8 @@ mod test { !U256::zero() }; let mut txq = TransactionQueue::default(); let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); - txq.add(tx1.clone(), &previous_nonce, TransactionOrigin::External).unwrap(); - txq.add(tx2, &previous_nonce, TransactionOrigin::External).unwrap(); + txq.add(tx1.clone(), TransactionOrigin::External, &previous_nonce, &gas_estimator).unwrap(); + txq.add(tx2, TransactionOrigin::External, &previous_nonce, &gas_estimator).unwrap(); assert_eq!(txq.status().future, 2); // when @@ -2104,7 +2126,7 @@ mod test { let details = |_a: &Address| AccountDetails { nonce: nonce, balance: !U256::zero() }; // when - txq.add(tx, &details, TransactionOrigin::External).unwrap(); + txq.add(tx, TransactionOrigin::External, &details, &gas_estimator).unwrap(); // then assert_eq!(txq.last_nonce(&from), Some(nonce)); @@ -2119,7 +2141,7 @@ mod test { let details1 = |_a: &Address| AccountDetails { nonce: nonce1, balance: !U256::zero() }; // Insert first transaction - txq.add(tx1, &details1, TransactionOrigin::External).unwrap(); + txq.add(tx1, TransactionOrigin::External, &details1, &gas_estimator).unwrap(); // when txq.remove_all(tx2.sender().unwrap(), nonce2 + U256::one()); @@ -2139,9 +2161,9 @@ mod test { // when // Insert first transaction - assert_eq!(txq.add(tx1, &details1, TransactionOrigin::External).unwrap(), TransactionImportResult::Current); + assert_eq!(txq.add(tx1, TransactionOrigin::External, &details1, &gas_estimator).unwrap(), TransactionImportResult::Current); // Second should go to future - assert_eq!(txq.add(tx2, &details1, TransactionOrigin::External).unwrap(), TransactionImportResult::Future); + assert_eq!(txq.add(tx2, TransactionOrigin::External, &details1, &gas_estimator).unwrap(), TransactionImportResult::Future); // Now block is imported txq.remove_all(sender, nonce2 - U256::from(1)); // tx2 should be not be promoted to current @@ -2160,9 +2182,9 @@ mod test { assert_eq!(txq.has_local_pending_transactions(), false); // when - assert_eq!(txq.add(tx1, &default_account_details, TransactionOrigin::External).unwrap(), TransactionImportResult::Current); + assert_eq!(txq.add(tx1, TransactionOrigin::External, &default_account_details, &gas_estimator).unwrap(), TransactionImportResult::Current); assert_eq!(txq.has_local_pending_transactions(), false); - assert_eq!(txq.add(tx2, &default_account_details, TransactionOrigin::Local).unwrap(), TransactionImportResult::Current); + assert_eq!(txq.add(tx2, TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(), TransactionImportResult::Current); // then assert_eq!(txq.has_local_pending_transactions(), true); @@ -2177,8 +2199,8 @@ mod test { default_account_details(a).balance }; // when - assert_eq!(txq.add(tx2, &prev_nonce, TransactionOrigin::External).unwrap(), TransactionImportResult::Future); - assert_eq!(txq.add(tx1.clone(), &prev_nonce, TransactionOrigin::External).unwrap(), TransactionImportResult::Future); + assert_eq!(txq.add(tx2, TransactionOrigin::External, &prev_nonce, &gas_estimator).unwrap(), TransactionImportResult::Future); + assert_eq!(txq.add(tx1.clone(), TransactionOrigin::External, &prev_nonce, &gas_estimator).unwrap(), TransactionImportResult::Future); // then assert_eq!(txq.future.by_priority.len(), 1); @@ -2203,14 +2225,14 @@ mod test { (tx.sign(secret), tx2.sign(secret), tx2_2.sign(secret), tx3.sign(secret)) }; let sender = tx1.sender().unwrap(); - txq.add(tx1, &default_account_details, TransactionOrigin::Local).unwrap(); - txq.add(tx2, &default_account_details, TransactionOrigin::Local).unwrap(); - txq.add(tx3, &default_account_details, TransactionOrigin::Local).unwrap(); + txq.add(tx1, TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx2, TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(); + txq.add(tx3, TransactionOrigin::Local, &default_account_details, &gas_estimator).unwrap(); assert_eq!(txq.future.by_priority.len(), 0); assert_eq!(txq.current.by_priority.len(), 3); // when - let res = txq.add(tx2_2, &default_account_details, TransactionOrigin::Local); + let res = txq.add(tx2_2, TransactionOrigin::Local, &default_account_details, &gas_estimator); // then assert_eq!(txq.last_nonce(&sender).unwrap(), 125.into()); @@ -2218,4 +2240,24 @@ mod test { assert_eq!(txq.current.by_priority.len(), 3); } + #[test] + fn should_reject_transactions_below_bas_gas() { + // given + let mut txq = TransactionQueue::default(); + let (tx1, tx2) = new_tx_pair_default(1.into(), 0.into()); + let high_gas = |_: &SignedTransaction| 100_001.into(); + + // when + let res1 = txq.add(tx1, TransactionOrigin::Local, &default_account_details, &gas_estimator); + let res2 = txq.add(tx2, TransactionOrigin::Local, &default_account_details, &high_gas); + + // then + assert_eq!(res1.unwrap(), TransactionImportResult::Current); + assert_eq!(unwrap_tx_err(res2), TransactionError::InsufficientGas { + minimal: 100_001.into(), + got: 100_000.into(), + }); + + } + } diff --git a/rpc/src/v1/helpers/errors.rs b/rpc/src/v1/helpers/errors.rs index 3f5dda2d3..475063832 100644 --- a/rpc/src/v1/helpers/errors.rs +++ b/rpc/src/v1/helpers/errors.rs @@ -221,6 +221,9 @@ pub fn from_transaction_error(error: EthcoreError) -> Error { LimitReached => { "There are too many transactions in the queue. Your transaction was dropped due to limit. Try increasing the fee.".into() }, + InsufficientGas { minimal, got } => { + format!("Transaction gas is too low. There is not enough gas to cover minimal cost of the transaction (minimal: {}, got: {}). Try increasing supplied gas.", minimal, got) + }, InsufficientGasPrice { minimal, got } => { format!("Transaction gas price is too low. It does not satisfy your node's minimal gas price (minimal: {}, got: {}). Try increasing the gas price.", minimal, got) }, From 6abd08f5b2843779a56ae29d98d4b7a4832ecf20 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Fri, 28 Oct 2016 16:46:11 +0200 Subject: [PATCH 43/47] Bring in styling queues from original Gavcoin (#2936) * Bring in styling queues from original * NewTranch background colour update --- js/assets/images/dapps/gavcoin-bg.jpg | Bin 0 -> 79447 bytes js/src/dapps/gavcoin/Accounts/accounts.css | 2 +- .../Actions/ActionBuyIn/actionBuyIn.js | 3 +++ .../Actions/ActionRefund/actionRefund.js | 3 +++ .../Actions/ActionTransfer/actionTransfer.js | 3 +++ js/src/dapps/gavcoin/Actions/actions.css | 19 ++++++++++++---- .../dapps/gavcoin/Application/application.css | 21 ++++++++++++++++++ .../dapps/gavcoin/Application/application.js | 9 +++++++- js/src/dapps/gavcoin/Events/events.css | 2 +- js/src/dapps/gavcoin/Status/status.css | 10 +++++---- 10 files changed, 61 insertions(+), 11 deletions(-) create mode 100644 js/assets/images/dapps/gavcoin-bg.jpg create mode 100644 js/src/dapps/gavcoin/Application/application.css diff --git a/js/assets/images/dapps/gavcoin-bg.jpg b/js/assets/images/dapps/gavcoin-bg.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c344f5979f655729fa9418be631ac9d9d32c36d1 GIT binary patch literal 79447 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

g$M7M0L0-57(kpq!#?35 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

    )eAW;LcbG7n*(e<>l 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 Z>y&m*dx<^WB&kY|Jjxa4SfIr literal 0 HcmV?d00001 diff --git a/js/src/dapps/gavcoin/Accounts/accounts.css b/js/src/dapps/gavcoin/Accounts/accounts.css index 7c4511710..a7fdcee73 100644 --- a/js/src/dapps/gavcoin/Accounts/accounts.css +++ b/js/src/dapps/gavcoin/Accounts/accounts.css @@ -22,7 +22,7 @@ .account { margin: 0.5em !important; - background: rgb(50, 100, 150) !important; + background: #430 !important; display: inline-block !important; } diff --git a/js/src/dapps/gavcoin/Actions/ActionBuyIn/actionBuyIn.js b/js/src/dapps/gavcoin/Actions/ActionBuyIn/actionBuyIn.js index f97e20a6c..42de205c1 100644 --- a/js/src/dapps/gavcoin/Actions/ActionBuyIn/actionBuyIn.js +++ b/js/src/dapps/gavcoin/Actions/ActionBuyIn/actionBuyIn.js @@ -73,6 +73,7 @@ export default class ActionBuyIn extends Component { if (complete) { return ( @@ -84,10 +85,12 @@ export default class ActionBuyIn extends Component { return ([ , @@ -82,10 +83,12 @@ export default class ActionRefund extends Component { return ([ , @@ -85,10 +86,12 @@ export default class ActionTransfer extends Component { return ([ , . +*/ + +.body { + background-size: cover; + background-repeat: no-repeat; +} diff --git a/js/src/dapps/gavcoin/Application/application.js b/js/src/dapps/gavcoin/Application/application.js index d5d26bc3e..1ae5d870a 100644 --- a/js/src/dapps/gavcoin/Application/application.js +++ b/js/src/dapps/gavcoin/Application/application.js @@ -32,6 +32,13 @@ import Events from '../Events'; import Loading from '../Loading'; import Status from '../Status'; +import styles from './application.css'; +import bgimage from '../../../../assets/images/dapps/gavcoin-bg.jpg'; + +const bgstyle = { + backgroundImage: `url(${bgimage})` +}; + const DIVISOR = 10 ** 6; export default class Application extends Component { @@ -70,7 +77,7 @@ export default class Application extends Component { } return ( -
    +
    { this.renderModals() } . */ .status { - background: rgba(25, 75, 125, 1); - color: rgba(255, 255, 255, 1); + background: rgba(255, 175, 0, 0.25); + color: #430; padding: 4em 0 2em 0; display: flex; flex-wrap: wrap; @@ -38,14 +38,16 @@ .byline { font-size: 1.25em; - color: rgba(255, 255, 255, 0.7); + color: #430; + opacity: 0.75; } .heading { text-transform: uppercase; letter-spacing: 0.25em; font-size: 1.5em; - color: rgba(255, 255, 255, 0.7); + color: #430; + opacity: 0.75; } .hero { From 8d66fc50e235a8cb88e9e5c2d90ff67171b18854 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Fri, 28 Oct 2016 16:46:25 +0200 Subject: [PATCH 44/47] Add import of raw private key RPCs (#2942) * Importing an account from raw secret * Add jsapi & jsonrpc for personal_newAccountFromSecret --- js/src/api/rpc/personal/personal.js | 8 +++++++- js/src/jsonrpc/interfaces/personal.js | 20 +++++++++++++++++++- rpc/src/v1/impls/personal_accounts.rs | 15 ++++++++++++++- rpc/src/v1/traits/personal.rs | 5 +++++ 4 files changed, 45 insertions(+), 3 deletions(-) diff --git a/js/src/api/rpc/personal/personal.js b/js/src/api/rpc/personal/personal.js index e35333102..ca7dbce9b 100644 --- a/js/src/api/rpc/personal/personal.js +++ b/js/src/api/rpc/personal/personal.js @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import { inAddress, inNumber10, inNumber16, inOptions } from '../../format/input'; +import { inAddress, inHex, inNumber10, inNumber16, inOptions } from '../../format/input'; import { outAccountInfo, outAddress, outSignerRequest } from '../../format/output'; export default class Personal { @@ -73,6 +73,12 @@ export default class Personal { .then(outAddress); } + newAccountFromSecret (secret, password) { + return this._transport + .execute('personal_newAccountFromSecret', inHex(secret), password) + .then(outAddress); + } + newAccountFromWallet (json, password) { return this._transport .execute('personal_newAccountFromWallet', json, password) diff --git a/js/src/jsonrpc/interfaces/personal.js b/js/src/jsonrpc/interfaces/personal.js index 748c8799f..2a9ce7c19 100644 --- a/js/src/jsonrpc/interfaces/personal.js +++ b/js/src/jsonrpc/interfaces/personal.js @@ -141,7 +141,7 @@ export default { }, newAccountFromPhrase: { - desc: 'Creates a new account from a brainwallet passphrase', + desc: 'Creates a new account from a recovery passphrase', params: [ { type: String, @@ -158,6 +158,24 @@ export default { } }, + newAccountFromSecret: { + desc: 'Creates a new account from a private ethstore secret key', + params: [ + { + type: Data, + desc: 'Secret, 32-byte hex' + }, + { + type: String, + desc: 'Password' + } + ], + returns: { + type: Address, + desc: 'The created address' + } + }, + newAccountFromWallet: { desc: 'Creates a new account from a JSON import', params: [ diff --git a/rpc/src/v1/impls/personal_accounts.rs b/rpc/src/v1/impls/personal_accounts.rs index 0fa236845..6e777f537 100644 --- a/rpc/src/v1/impls/personal_accounts.rs +++ b/rpc/src/v1/impls/personal_accounts.rs @@ -20,7 +20,7 @@ use util::{Address}; use jsonrpc_core::*; use ethkey::{Brain, Generator}; use v1::traits::PersonalAccounts; -use v1::types::{H160 as RpcH160, TransactionRequest}; +use v1::types::{H160 as RpcH160, H256 as RpcH256, TransactionRequest}; use v1::helpers::errors; use v1::helpers::params::expect_no_params; use v1::helpers::dispatch::sign_and_dispatch; @@ -95,6 +95,19 @@ impl PersonalAccounts for PersonalAccountsClient w ) } + fn new_account_from_secret(&self, params: Params) -> Result { + try!(self.active()); + from_params::<(RpcH256, String, )>(params).and_then( + |(secret, pass, )| { + let store = take_weak!(self.accounts); + match store.insert_account(secret.into(), &pass) { + Ok(address) => Ok(to_value(&RpcH160::from(address))), + Err(e) => Err(errors::account("Could not create account.", e)), + } + } + ) + } + fn unlock_account(&self, params: Params) -> Result { try!(self.active()); from_params::<(RpcH160, String, Option)>(params).and_then( diff --git a/rpc/src/v1/traits/personal.rs b/rpc/src/v1/traits/personal.rs index 92a9df6eb..8ad1b7ac6 100644 --- a/rpc/src/v1/traits/personal.rs +++ b/rpc/src/v1/traits/personal.rs @@ -52,6 +52,10 @@ pub trait PersonalAccounts: Sized + Send + Sync + 'static { /// Second parameter is password for the wallet and the new account. fn new_account_from_wallet(&self, params: Params) -> Result; + /// Creates new account from the given raw secret. + /// Second parameter is password for the new account. + fn new_account_from_secret(&self, params: Params) -> Result; + /// Unlocks specified account for use (can only be one unlocked account at one moment) fn unlock_account(&self, _: Params) -> Result; @@ -84,6 +88,7 @@ pub trait PersonalAccounts: Sized + Send + Sync + 'static { delegate.add_method("personal_newAccount", PersonalAccounts::new_account); delegate.add_method("personal_newAccountFromPhrase", PersonalAccounts::new_account_from_phrase); delegate.add_method("personal_newAccountFromWallet", PersonalAccounts::new_account_from_wallet); + delegate.add_method("personal_newAccountFromSecret", PersonalAccounts::new_account_from_secret); delegate.add_method("personal_unlockAccount", PersonalAccounts::unlock_account); delegate.add_method("personal_testPassword", PersonalAccounts::test_password); delegate.add_method("personal_changePassword", PersonalAccounts::change_password); From ef0ed5833af75127acc1065e4f3b9b114ad91151 Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Fri, 28 Oct 2016 15:51:18 +0000 Subject: [PATCH 45/47] [ci skip] js-precompiled 20161028-155009 --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 26fab3d49..7804269c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1213,7 +1213,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#3f31aca3dd0b71871f6439407c3e258c3ed91abf" +source = "git+https://github.com/ethcore/js-precompiled.git#72b966a36c0a063e96de823f92c07201ad7ebad5" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] From 1094dfbe6bd62c3fec7d4626a52b8fbe1f9ff9a6 Mon Sep 17 00:00:00 2001 From: "Denis S. Soldatov aka General-Beck" Date: Fri, 28 Oct 2016 23:04:00 +0700 Subject: [PATCH 46/47] Update gitlab-ci add darwin and windows tests --- .gitlab-ci.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3d822a9d5..e16350325 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -364,6 +364,28 @@ test-linux: - rust-test dependencies: - linux-stable +test-darwin: + stage: test + before_script: + - git submodule update --init --recursive + script: + - export RUST_BACKTRACE=1 + - ./test.sh --verbose + tags: + - osx + dependencies: + - darwin +test-windows: + stage: test + before_script: + - git submodule update --init --recursive + script: + - export RUST_BACKTRACE=1 + - ./test.sh --verbose + tags: + - rust-windows + dependencies: + - windows js-release: stage: build image: ethcore/javascript:latest From 8cd66f14d5e44471fba87e322644245b964bf952 Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Fri, 28 Oct 2016 16:06:51 +0000 Subject: [PATCH 47/47] [ci skip] js-precompiled 20161028-160553 --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 7804269c8..b9d989fbd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1213,7 +1213,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#72b966a36c0a063e96de823f92c07201ad7ebad5" +source = "git+https://github.com/ethcore/js-precompiled.git#ba726039185238d6fd604f092b089a7d52c0f436" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ]