From 0f16942186c20cba4354c4c2656a6292190b14d5 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 4 Nov 2016 15:13:51 +0100 Subject: [PATCH 01/62] EIP-155 update with Vitalik's new test vectors (#3166) * Vitalik's new test vectors. * Update to latest EIP155 spec. * Fix txs. * Another fix. --- ethcore/src/types/transaction.rs | 33 ++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/ethcore/src/types/transaction.rs b/ethcore/src/types/transaction.rs index e26f98cfa..8289c5864 100644 --- a/ethcore/src/types/transaction.rs +++ b/ethcore/src/types/transaction.rs @@ -159,7 +159,7 @@ impl Transaction { unsigned: self, r: sig.r().into(), s: sig.s().into(), - v: sig.v() + if let Some(n) = network_id { 1 + n * 2 } else { 27 }, + v: sig.v() + if let Some(n) = network_id { 35 + n * 2 } else { 27 }, hash: Cell::new(None), sender: Cell::new(None), } @@ -302,13 +302,13 @@ impl SignedTransaction { } /// 0 if `v` would have been 27 under "Electrum" notation, 1 if 28 or 4 if invalid. - pub fn standard_v(&self) -> u8 { match self.v { 0 => 4, v => (v - 1) & 1, } } + pub fn standard_v(&self) -> u8 { match self.v { v if v == 27 || v == 28 || v > 36 => (v - 1) % 2, _ => 4 } } /// The network ID, or `None` if this is a global transaction. pub fn network_id(&self) -> Option { match self.v { - 0 | 27 | 28 => None, - v => Some((v - 1) / 2), + v if v > 36 => Some((v - 35) / 2), + _ => None, } } @@ -456,21 +456,22 @@ fn should_recover_from_network_specific_signing() { #[test] fn should_agree_with_vitalik() { use rustc_serialize::hex::FromHex; - use std::str::FromStr; let test_vector = |tx_data: &str, address: &'static str| { let signed: SignedTransaction = decode(&FromHex::from_hex(tx_data).unwrap()); - assert_eq!(signed.sender().unwrap(), address.into()); + signed.check_low_s().unwrap(); + assert_eq!(signed.sender().unwrap(), address.into()); + flushln!("networkid: {:?}", signed.network_id()); }; - test_vector("f864808504a817c800825208943535353535353535353535353535353535353535808025a0044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116da0044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116d", "0xda39a520355857fdb37ecb527fe814230fa9962c"); - test_vector("f864018504a817c80182a410943535353535353535353535353535353535353535018025a0c89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc8a0c89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6", "0xd240215f30eafee2aaa5184d8f051ebb41c90b19"); - test_vector("f864028504a817c80282f618943535353535353535353535353535353535353535088025a0ad7c5bef027816a800da1736444fb58a807ef4c9603b7848673f7e3a68eb14a8a0ad7c5bef027816a800da1736444fb58a807ef4c9603b7848673f7e3a68eb14a5", "0x2f2822c55d894df1ba32961c43325dcb3d614ee8"); - test_vector("f865038504a817c803830148209435353535353535353535353535353535353535351b8025a02a80e1ef1d7842f27f2e6be0972bb708b9a135c38860dbe73c27c3486c34f4e0a02a80e1ef1d7842f27f2e6be0972bb708b9a135c38860dbe73c27c3486c34f4de", "0xc21df0434ceab6e18a1300d18206e54e807b4456"); - test_vector("f865048504a817c80483019a28943535353535353535353535353535353535353535408025a013600b294191fc92924bb3ce4b969c1e7e2bab8f4c93c3fc6d0a51733df3c063a013600b294191fc92924bb3ce4b969c1e7e2bab8f4c93c3fc6d0a51733df3c060", "0xf975cee81edae2ab5883f4e2fb2a7f2fd56f4131"); - test_vector("f865058504a817c8058301ec309435353535353535353535353535353535353535357d8025a0ceebf77a833b30520287ddd9478ff51abbdffa30aa90a8d655dba0e8a79ce0c1a0ceebf77a833b30520287ddd9478ff51abbdffa30aa90a8d655dba0e8a79ce0c1", "0xd477474c9f48dcbfde5d97f30646242ab7a17e06"); - test_vector("f866068504a817c80683023e3894353535353535353535353535353535353535353581d88025a0e455bf8ea6e7463a1046a0b52804526e119b4bf5136279614e0b1e8e296a4e2da0e455bf8ea6e7463a1046a0b52804526e119b4bf5136279614e0b1e8e296a4e2d", "0xb49948deb719ca21e38d29e3360f534b39db0e76"); - test_vector("f867078504a817c807830290409435353535353535353535353535353535353535358201578025a052f1a9b320cab38e5da8a8f97989383aab0a49165fc91c737310e4f7e9821021a052f1a9b320cab38e5da8a8f97989383aab0a49165fc91c737310e4f7e9821021", "0xbddfc81a8ce87b2360837049a6eda68ab2f58999"); - test_vector("f867088504a817c8088302e2489435353535353535353535353535353535353535358202008025a0e4b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c11a0e4b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c10", "0x8f2edcf67f329a146dd4cb1e6b3a072daff85b38"); - test_vector("f867098504a817c809830334509435353535353535353535353535353535353535358202d98025a0d2f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afba0d2f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afb", "0x4ec38d4782fd4a6ff85c1cde77ccf1ae3c54472c"); + test_vector("f864808504a817c800825208943535353535353535353535353535353535353535808025a0044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116da0044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116d", "0xf0f6f18bca1b28cd68e4357452947e021241e9ce") + test_vector("f864018504a817c80182a410943535353535353535353535353535353535353535018025a0489efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bcaa0489efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6", "0x23ef145a395ea3fa3deb533b8a9e1b4c6c25d112") + test_vector("f864028504a817c80282f618943535353535353535353535353535353535353535088025a02d7c5bef027816a800da1736444fb58a807ef4c9603b7848673f7e3a68eb14a5a02d7c5bef027816a800da1736444fb58a807ef4c9603b7848673f7e3a68eb14a5", "0x2e485e0c23b4c3c542628a5f672eeab0ad4888be") + test_vector("f865038504a817c803830148209435353535353535353535353535353535353535351b8025a02a80e1ef1d7842f27f2e6be0972bb708b9a135c38860dbe73c27c3486c34f4e0a02a80e1ef1d7842f27f2e6be0972bb708b9a135c38860dbe73c27c3486c34f4de", "0x82a88539669a3fd524d669e858935de5e5410cf0") + test_vector("f865048504a817c80483019a28943535353535353535353535353535353535353535408025a013600b294191fc92924bb3ce4b969c1e7e2bab8f4c93c3fc6d0a51733df3c063a013600b294191fc92924bb3ce4b969c1e7e2bab8f4c93c3fc6d0a51733df3c060", "0xf9358f2538fd5ccfeb848b64a96b743fcc930554") + test_vector("f865058504a817c8058301ec309435353535353535353535353535353535353535357d8025a04eebf77a833b30520287ddd9478ff51abbdffa30aa90a8d655dba0e8a79ce0c1a04eebf77a833b30520287ddd9478ff51abbdffa30aa90a8d655dba0e8a79ce0c1", "0xa8f7aba377317440bc5b26198a363ad22af1f3a4") + test_vector("f866068504a817c80683023e3894353535353535353535353535353535353535353581d88025a06455bf8ea6e7463a1046a0b52804526e119b4bf5136279614e0b1e8e296a4e2fa06455bf8ea6e7463a1046a0b52804526e119b4bf5136279614e0b1e8e296a4e2d", "0xf1f571dc362a0e5b2696b8e775f8491d3e50de35") + test_vector("f867078504a817c807830290409435353535353535353535353535353535353535358201578025a052f1a9b320cab38e5da8a8f97989383aab0a49165fc91c737310e4f7e9821021a052f1a9b320cab38e5da8a8f97989383aab0a49165fc91c737310e4f7e9821021", "0xd37922162ab7cea97c97a87551ed02c9a38b7332") + test_vector("f867088504a817c8088302e2489435353535353535353535353535353535353535358202008025a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c12a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c10", "0x9bddad43f934d313c2b79ca28a432dd2b7281029") + test_vector("f867098504a817c809830334509435353535353535353535353535353535353535358202d98025a052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afba052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afb", "0x3c24d7329e92f84f08556ceb6df1cdb0104ca49f") } \ No newline at end of file From caab72394d8c84a4fe2c5e59de7f2523147f4401 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Fri, 4 Nov 2016 15:58:44 +0100 Subject: [PATCH 02/62] Bump package.json version (1.5 is master) (#3193) * Bump package.json version (1.5 is master) * Updated maintainers --- js/package.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/js/package.json b/js/package.json index 36d5a4250..b9fa13f45 100644 --- a/js/package.json +++ b/js/package.json @@ -1,11 +1,13 @@ { "name": "parity.js", - "version": "0.1.63", + "version": "0.2.0", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", "maintainers": [ - "Jaco Greeff" + "Jaco Greeff", + "Nicolas Gotchac", + "Jannis Redmann" ], "contributors": [], "license": "GPL-3.0", From 145e766db2de4b20b208a9bb2478479b74f855fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Fri, 4 Nov 2016 16:59:42 +0100 Subject: [PATCH 03/62] Fixing possible race condition in ethcore_hashContent (#3191) --- rpc/src/v1/impls/ethcore.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/rpc/src/v1/impls/ethcore.rs b/rpc/src/v1/impls/ethcore.rs index 52aecb465..1130b8fb8 100644 --- a/rpc/src/v1/impls/ethcore.rs +++ b/rpc/src/v1/impls/ethcore.rs @@ -302,7 +302,9 @@ impl Ethcore for EthcoreClient where .map(Into::into); // Receive ready and invoke with result. - let ready: Ready = rx.try_recv().expect("When on_done is invoked ready object is always sent."); + let ready: Ready = rx.recv().expect( + "recv() fails when `tx` has been dropped, if this closure is invoked `tx` is not dropped (`res == Ok()`); qed" + ); ready.ready(result); })); @@ -310,7 +312,9 @@ impl Ethcore for EthcoreClient where if let Err(e) = res { ready.ready(Err(errors::from_fetch_error(e))); } else { - tx.send(ready).expect("Rx end is sent to on_done closure."); + tx.send(ready).expect( + "send() fails when `rx` end is dropped, if `res == Ok()`: `rx` is moved to the closure; qed" + ); } } } From 858962ca0d1aa6e6b475444656efff7aa863f722 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Fri, 4 Nov 2016 17:03:28 +0100 Subject: [PATCH 04/62] Remove dapp logos (GHH points to dapp-assets) (#3192) --- js/assets/images/dapps/coins-64x64.jpg | Bin 3151 -> 0 bytes js/assets/images/dapps/coins.jpg | Bin 47808 -> 0 bytes js/assets/images/dapps/hex-64x64.jpg | Bin 4658 -> 0 bytes js/assets/images/dapps/hex.jpg | Bin 25630 -> 0 bytes js/assets/images/dapps/interlock-64x64.png | Bin 9687 -> 0 bytes js/assets/images/dapps/interlock.png | Bin 45060 -> 0 bytes js/assets/images/dapps/link-64x64.jpg | Bin 2000 -> 0 bytes js/assets/images/dapps/link.jpg | Bin 7973 -> 0 bytes js/assets/images/dapps/register-64x64.jpg | Bin 8969 -> 0 bytes js/assets/images/dapps/register.jpg | Bin 65545 -> 0 bytes 10 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 js/assets/images/dapps/coins-64x64.jpg delete mode 100644 js/assets/images/dapps/coins.jpg delete mode 100644 js/assets/images/dapps/hex-64x64.jpg delete mode 100644 js/assets/images/dapps/hex.jpg delete mode 100644 js/assets/images/dapps/interlock-64x64.png delete mode 100644 js/assets/images/dapps/interlock.png delete mode 100644 js/assets/images/dapps/link-64x64.jpg delete mode 100644 js/assets/images/dapps/link.jpg delete mode 100644 js/assets/images/dapps/register-64x64.jpg delete mode 100644 js/assets/images/dapps/register.jpg diff --git a/js/assets/images/dapps/coins-64x64.jpg b/js/assets/images/dapps/coins-64x64.jpg deleted file mode 100644 index 987d7400efbc19054a194526625f07bfa1e6f458..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3151 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 H;$rGQt(p%c diff --git a/js/assets/images/dapps/hex.jpg b/js/assets/images/dapps/hex.jpg deleted file mode 100644 index 8e09f49aa27a47f13a2dfa0b14eba4af1d9e0109..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25630 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 diff --git a/js/assets/images/dapps/link-64x64.jpg b/js/assets/images/dapps/link-64x64.jpg deleted file mode 100644 index 14f9c11906e50ce3439bbd40bef30e79f1146ccf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2000 zcmb7^dpuNmAIHy`bH)tgat?#il(`5)TI2rcDT7vsGNF+dyGywxtw9!x%+}++#I}X9 z80FHIC^OGuDN*E-j9W~t%QHkTo6Ei;Wal~4<=OrB`JUhT=bYE?`+a{uzwhTKdnTIz z5LYJ`CjbONz!`f0nFMeIARJD9Z^w5RY@d7 z3qdu=nwpx*YT8@0$XnFOnq+wr5W@Drctt#3k*q>eA^+bYdjOzBfDN!A5Cw232%(^? z9iRXpfT4lFU!g#N@gPLRVSl$`Z9v%jw*VnL4wj7p%Ge$p0wDkZzQOv$0vzPly*T!A zc#OMHZ}Oz*%OBpb(z$i!o?R^w632VHSFZ^6C53-G!q;xHbuP}Qa)t%phx+RjMECxl z?Df;T8(SppeHuRnwy$(Kzid375+zSbz!-E9K+g9!Mi`HS6hJ_epw4DsBt#4l#N*ys zNp!v%aZEhTkm`5Ws7cM%h7z7`T{e@hP3lF4I_DSX3;mT|i7N~Zp5=JhKJr18mcRJp zvq6)_36sbFx={u?{#N^g;GbuI?k)=G5G7AOI&?}jA0wRB^t~1EOYbS?fq5ud%I9LIaW6MKM45Ku4Iv$}eYLFQ$wmpYm_8u2g z)DC>>9NoREj;H4X8Gs}uyG~h}R9z0Koo#F!K6>Z& z*>iFMF*X>J@2_LTVX&w=i%Mf_(BBi`A2EY|lB1w9Zsa|MB{joN1%z-A9wvS07XWb(0Hf;m8Z;K3&I#a~vGe*7GQ%N| z7nNToUi^4a3U;_v^(T?WuU0%Pdq<^I&+PbyYLl13j9@46i%o|gD?}#fbJMR=ftn+5 zQtQp?&ON!M8a;L58`c)bg*M*Okv#^pYs2Z>!j-FUPBvtehP_?Sreq8iKeE=e3tqYS z)Gt67c6vF-(XN*lLAk#H^(OEAJm_&gW#fc2d12l-eJL`?Gu2O;9C)+IVT@%cx@^XX z5j&Z4wh#yIm6Y5%gRi?9QdwAjz5af@#U?2ioDg=@H(8D7W=j(v;tMiLzx*|~`tU{% zr&gGyXxIk(HIy3IoqE@~Sn1HnuRQ3p8+MEGrtd0IX*Ohvcv{hEI)s{;)SF5R%In-@ z&97aDw%;+1lu!33R^tr^q69-W?Nil?~g@}FYI(fc_2TQsZ z-2$eTD;B~}`xb-)8P}%O2klQq6R-I`Ztt;>;JbCnHL+K{x;hKHS^Q#4<3rNd@Td$3 zBZYX7*R`v!x(O`zRX1#t+gO<^&kU;SAYWcuUpIz5;S-xm`m zP#>o5tE^2cvdH*_(V`a4u8-W)*esg$JwN+zvI5gT>{y~M#!SVSadHoSB$nR}C=Fq8 z7TNXzybqUx+=9qrQ3NR^E2}Rl!qmoe|6TtTJ}1=6jCuJCrDgT?-Z>Ye>7y3WNO{4Y zH0hc_`t!=qn~gY35cjLR2m`?!j>BPfK9vDc8Vj)J@B$E1Hb1ZKlLfIl@3sqzgShM< z3h3S1S{$t)wIIfY3lwQ}MPbk9jIm&2##AuHPk$+wesC>e80m~oWdo(4Wsl=QdN{)dnk(O ze`wPa7Lc9K4mB>#v<8qYX`b(ptCIvCHt5V!nE}P zN1|{3Y0Zg^V=-jZS}*)!tUaYROUJ`fKi#v9SPz30p|RP*K+&q5TZdtV#Q4J2m!vA~ zOZz+MAm>1k+tFZd)1B_l5{AZzma2GasJ5va1QZS)XBLrnTqq66EKGIJm258ANVD#i cEOh{JnI)Z*7gW#9irVHUs|e)_XJw=R1+QQ1B>(^b diff --git a/js/assets/images/dapps/link.jpg b/js/assets/images/dapps/link.jpg deleted file mode 100644 index 546c67e205a721e2588be2054899d499cd75ca09..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7973 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 R9)7#;Q^V*+bE%oG{{f(5K%W2r diff --git a/js/assets/images/dapps/register.jpg b/js/assets/images/dapps/register.jpg deleted file mode 100644 index cd47bd81ef6308794d8c9e3c23acd39fd1c25440..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 65545 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 From 75d100476eee3eeee4083069835d3b2dcd5a3061 Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Fri, 4 Nov 2016 16:25:41 +0000 Subject: [PATCH 05/62] [ci skip] js-precompiled 20161104-162426 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d4504a278..40debfa0b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1249,7 +1249,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#cb99e22b536486190f3e19b2760e1142096b5fd7" +source = "git+https://github.com/ethcore/js-precompiled.git#5b8f2816aef4f6fa970c5ca0947b07eda248a40d" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/js/package.json b/js/package.json index b9fa13f45..343463892 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.0", + "version": "0.2.1", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", From d37f4cc82f87cb75d43ebed7a32a2638f9dee6ec Mon Sep 17 00:00:00 2001 From: Arkadiy Paronyan Date: Fri, 4 Nov 2016 17:27:11 +0100 Subject: [PATCH 06/62] v1.5 (#3195) --- Cargo.lock | 80 ++++++++++++++++++++--------------------- Cargo.toml | 2 +- dapps/Cargo.toml | 2 +- ethcore/Cargo.toml | 2 +- logger/Cargo.toml | 2 +- nsis/installer.nsi | 2 +- rpc/Cargo.toml | 2 +- signer/Cargo.toml | 2 +- sync/Cargo.toml | 2 +- util/Cargo.toml | 2 +- util/io/Cargo.toml | 2 +- util/network/Cargo.toml | 2 +- 12 files changed, 51 insertions(+), 51 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 40debfa0b..e557eed33 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ [root] name = "parity" -version = "1.4.0" +version = "1.5.0" dependencies = [ "ansi_term 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", "clippy 0.0.96 (registry+https://github.com/rust-lang/crates.io-index)", @@ -8,21 +8,21 @@ dependencies = [ "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)", "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "ethcore 1.4.0", - "ethcore-dapps 1.4.0", + "ethcore 1.5.0", + "ethcore-dapps 1.5.0", "ethcore-devtools 1.4.0", - "ethcore-io 1.4.0", + "ethcore-io 1.5.0", "ethcore-ipc 1.4.0", "ethcore-ipc-codegen 1.4.0", "ethcore-ipc-hypervisor 1.2.0", "ethcore-ipc-nano 1.4.0", "ethcore-ipc-tests 0.1.0", - "ethcore-logger 1.4.0", - "ethcore-rpc 1.4.0", - "ethcore-signer 1.4.0", + "ethcore-logger 1.5.0", + "ethcore-rpc 1.5.0", + "ethcore-signer 1.5.0", "ethcore-stratum 1.4.0", - "ethcore-util 1.4.0", - "ethsync 1.4.0", + "ethcore-util 1.5.0", + "ethsync 1.5.0", "fdlimit 0.1.0", "hyper 0.9.10 (registry+https://github.com/rust-lang/crates.io-index)", "isatty 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -274,7 +274,7 @@ dependencies = [ [[package]] name = "ethcore" -version = "1.4.0" +version = "1.5.0" 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)", @@ -285,11 +285,11 @@ dependencies = [ "ethash 1.4.0", "ethcore-bloom-journal 0.1.0", "ethcore-devtools 1.4.0", - "ethcore-io 1.4.0", + "ethcore-io 1.5.0", "ethcore-ipc 1.4.0", "ethcore-ipc-codegen 1.4.0", "ethcore-ipc-nano 1.4.0", - "ethcore-util 1.4.0", + "ethcore-util 1.5.0", "ethjson 0.1.0", "ethkey 0.2.0", "ethstore 0.1.0", @@ -329,14 +329,14 @@ dependencies = [ [[package]] name = "ethcore-dapps" -version = "1.4.0" +version = "1.5.0" dependencies = [ "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", - "ethcore-rpc 1.4.0", - "ethcore-util 1.4.0", + "ethcore-rpc 1.5.0", + "ethcore-util 1.5.0", "fetch 0.1.0", "hyper 0.9.4 (git+https://github.com/ethcore/hyper)", "jsonrpc-core 3.0.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -367,7 +367,7 @@ dependencies = [ [[package]] name = "ethcore-io" -version = "1.4.0" +version = "1.5.0" dependencies = [ "crossbeam 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -381,7 +381,7 @@ name = "ethcore-ipc" version = "1.4.0" dependencies = [ "ethcore-devtools 1.4.0", - "ethcore-util 1.4.0", + "ethcore-util 1.5.0", "nanomsg 0.5.1 (git+https://github.com/ethcore/nanomsg.rs.git)", "semver 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -428,7 +428,7 @@ dependencies = [ "ethcore-ipc 1.4.0", "ethcore-ipc-codegen 1.4.0", "ethcore-ipc-nano 1.4.0", - "ethcore-util 1.4.0", + "ethcore-util 1.5.0", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "nanomsg 0.5.1 (git+https://github.com/ethcore/nanomsg.rs.git)", "semver 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -436,10 +436,10 @@ dependencies = [ [[package]] name = "ethcore-logger" -version = "1.4.0" +version = "1.5.0" dependencies = [ "env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "ethcore-util 1.4.0", + "ethcore-util 1.5.0", "isatty 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "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)", @@ -449,13 +449,13 @@ dependencies = [ [[package]] name = "ethcore-network" -version = "1.4.0" +version = "1.5.0" dependencies = [ "ansi_term 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", "bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "ethcore-devtools 1.4.0", - "ethcore-io 1.4.0", - "ethcore-util 1.4.0", + "ethcore-io 1.5.0", + "ethcore-util 1.5.0", "ethcrypto 0.1.0", "ethkey 0.2.0", "igd 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -474,20 +474,20 @@ dependencies = [ [[package]] name = "ethcore-rpc" -version = "1.4.0" +version = "1.5.0" dependencies = [ "clippy 0.0.96 (registry+https://github.com/rust-lang/crates.io-index)", "ethash 1.4.0", - "ethcore 1.4.0", + "ethcore 1.5.0", "ethcore-devtools 1.4.0", - "ethcore-io 1.4.0", + "ethcore-io 1.5.0", "ethcore-ipc 1.4.0", - "ethcore-util 1.4.0", + "ethcore-util 1.5.0", "ethcrypto 0.1.0", "ethjson 0.1.0", "ethkey 0.2.0", "ethstore 0.1.0", - "ethsync 1.4.0", + "ethsync 1.5.0", "fetch 0.1.0", "json-ipc-server 0.2.4 (git+https://github.com/ethcore/json-ipc-server.git)", "jsonrpc-core 3.0.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -504,14 +504,14 @@ dependencies = [ [[package]] name = "ethcore-signer" -version = "1.4.0" +version = "1.5.0" dependencies = [ "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", - "ethcore-rpc 1.4.0", - "ethcore-util 1.4.0", + "ethcore-io 1.5.0", + "ethcore-rpc 1.5.0", + "ethcore-util 1.5.0", "jsonrpc-core 3.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -530,7 +530,7 @@ dependencies = [ "ethcore-ipc 1.4.0", "ethcore-ipc-codegen 1.4.0", "ethcore-ipc-nano 1.4.0", - "ethcore-util 1.4.0", + "ethcore-util 1.5.0", "json-tcp-server 0.1.0 (git+https://github.com/ethcore/json-tcp-server)", "jsonrpc-core 3.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -541,7 +541,7 @@ dependencies = [ [[package]] name = "ethcore-util" -version = "1.4.0" +version = "1.5.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)", @@ -590,7 +590,7 @@ dependencies = [ name = "ethjson" version = "0.1.0" dependencies = [ - "ethcore-util 1.4.0", + "ethcore-util 1.5.0", "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", "serde 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)", "serde_codegen 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)", @@ -633,17 +633,17 @@ dependencies = [ [[package]] name = "ethsync" -version = "1.4.0" +version = "1.5.0" dependencies = [ "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", + "ethcore 1.5.0", + "ethcore-io 1.5.0", "ethcore-ipc 1.4.0", "ethcore-ipc-codegen 1.4.0", "ethcore-ipc-nano 1.4.0", - "ethcore-network 1.4.0", - "ethcore-util 1.4.0", + "ethcore-network 1.5.0", + "ethcore-util 1.5.0", "heapsize 0.3.6 (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.3.5 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index dc802a0fd..fe72d67ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] description = "Ethcore client." name = "parity" -version = "1.4.0" +version = "1.5.0" license = "GPL-3.0" authors = ["Ethcore "] build = "build.rs" diff --git a/dapps/Cargo.toml b/dapps/Cargo.toml index 274c7b87b..f6e9d102d 100644 --- a/dapps/Cargo.toml +++ b/dapps/Cargo.toml @@ -1,7 +1,7 @@ [package] description = "Parity Dapps crate" name = "ethcore-dapps" -version = "1.4.0" +version = "1.5.0" license = "GPL-3.0" authors = ["Ethcore "] build = "build.rs" diff --git a/logger/Cargo.toml b/logger/Cargo.toml index 61b7dfdc5..590eb0b2d 100644 --- a/logger/Cargo.toml +++ b/logger/Cargo.toml @@ -1,7 +1,7 @@ [package] description = "Ethcore client." name = "ethcore-logger" -version = "1.4.0" +version = "1.5.0" license = "GPL-3.0" authors = ["Ethcore "] diff --git a/nsis/installer.nsi b/nsis/installer.nsi index 5f7c98f60..5e24c342b 100644 --- a/nsis/installer.nsi +++ b/nsis/installer.nsi @@ -9,7 +9,7 @@ !define COMPANYNAME "Ethcore" !define DESCRIPTION "Fast, light, robust Ethereum implementation" !define VERSIONMAJOR 1 -!define VERSIONMINOR 4 +!define VERSIONMINOR 5 !define VERSIONBUILD 0 !define ARGS "--warp --mode=passive" diff --git a/rpc/Cargo.toml b/rpc/Cargo.toml index 9ce638ea6..4a8c4d76a 100644 --- a/rpc/Cargo.toml +++ b/rpc/Cargo.toml @@ -1,7 +1,7 @@ [package] description = "Ethcore jsonrpc" name = "ethcore-rpc" -version = "1.4.0" +version = "1.5.0" license = "GPL-3.0" authors = ["Ethcore "] build = "build.rs" diff --git a/sync/Cargo.toml b/sync/Cargo.toml index 95d738eb4..c7e30d6a5 100644 --- a/sync/Cargo.toml +++ b/sync/Cargo.toml @@ -1,7 +1,7 @@ [package] description = "Ethcore blockchain sync" name = "ethsync" -version = "1.4.0" +version = "1.5.0" license = "GPL-3.0" authors = ["Ethcore "] build = "build.rs" diff --git a/util/io/Cargo.toml b/util/io/Cargo.toml index 3cd51e656..8c7f46cb9 100644 --- a/util/io/Cargo.toml +++ b/util/io/Cargo.toml @@ -3,7 +3,7 @@ description = "Ethcore IO library" homepage = "http://ethcore.io" license = "GPL-3.0" name = "ethcore-io" -version = "1.4.0" +version = "1.5.0" authors = ["Ethcore "] [dependencies] diff --git a/util/network/Cargo.toml b/util/network/Cargo.toml index 1a79df0e0..02a54375c 100644 --- a/util/network/Cargo.toml +++ b/util/network/Cargo.toml @@ -3,7 +3,7 @@ description = "Ethcore network library" homepage = "http://ethcore.io" license = "GPL-3.0" name = "ethcore-network" -version = "1.4.0" +version = "1.5.0" authors = ["Ethcore "] [dependencies] From f31d42d0c58dfc50673e1bd2244fd155a769841b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Fri, 4 Nov 2016 17:35:02 +0100 Subject: [PATCH 07/62] Exposing engine extra info in block RPC (#3169) * Exposing extra info in RPC * Proper serialization and client trait API --- ethcore/src/client/client.rs | 14 +++- ethcore/src/client/test_client.rs | 12 +++ ethcore/src/client/traits.rs | 12 ++- ethcore/src/engines/basic_authority.rs | 2 +- ethcore/src/engines/mod.rs | 2 +- ethcore/src/ethereum/ethash.rs | 4 +- ethcore/src/types/ids.rs | 2 +- rpc/src/v1/impls/eth.rs | 110 +++++++++++++------------ rpc/src/v1/traits/eth.rs | 10 +-- rpc/src/v1/types/block.rs | 53 +++++++++++- rpc/src/v1/types/mod.rs.in | 2 +- 11 files changed, 153 insertions(+), 70 deletions(-) diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index ef36d356c..777d561e3 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -65,7 +65,7 @@ use evm::{Factory as EvmFactory, Schedule}; use miner::{Miner, MinerService}; use snapshot::{self, io as snapshot_io}; use factory::Factories; -use rlp::{View, UntrustedRlp}; +use rlp::{decode, View, UntrustedRlp}; use state_db::StateDB; use rand::OsRng; @@ -1189,6 +1189,18 @@ impl BlockChainClient for Client { fn signing_network_id(&self) -> Option { self.engine.signing_network_id(&self.latest_env_info()) } + + fn block_extra_info(&self, id: BlockID) -> Option> { + self.block_header(id) + .map(|block| decode(&block)) + .map(|header| self.engine.extra_info(&header)) + } + + fn uncle_extra_info(&self, id: UncleID) -> Option> { + self.uncle(id) + .map(|block| BlockView::new(&block).header()) + .map(|header| self.engine.extra_info(&header)) + } } impl MiningBlockChainClient for Client { diff --git a/ethcore/src/client/test_client.rs b/ethcore/src/client/test_client.rs index e7731b73d..434edd3e8 100644 --- a/ethcore/src/client/test_client.rs +++ b/ethcore/src/client/test_client.rs @@ -38,6 +38,7 @@ use evm::{Factory as EvmFactory, VMType, Schedule}; use miner::{Miner, MinerService, TransactionImportResult}; use spec::Spec; use types::mode::Mode; +use views::BlockView; use verification::queue::QueueInfo; use block::{OpenBlock, SealedBlock}; @@ -417,6 +418,10 @@ impl BlockChainClient for TestBlockChainClient { None // Simple default. } + fn uncle_extra_info(&self, _id: UncleID) -> Option> { + None + } + fn transaction_receipt(&self, id: TransactionID) -> Option { self.receipts.read().get(&id).cloned() } @@ -459,6 +464,13 @@ impl BlockChainClient for TestBlockChainClient { self.block_hash(id).and_then(|hash| self.blocks.read().get(&hash).cloned()) } + fn block_extra_info(&self, id: BlockID) -> Option> { + self.block(id) + .map(|block| BlockView::new(&block).header()) + .map(|header| self.spec.engine.extra_info(&header)) + } + + fn block_status(&self, id: BlockID) -> BlockStatus { match id { BlockID::Number(number) if (number as usize) < self.blocks.read().len() => BlockStatus::InChain, diff --git a/ethcore/src/client/traits.rs b/ethcore/src/client/traits.rs index 60be4ba1b..67092e986 100644 --- a/ethcore/src/client/traits.rs +++ b/ethcore/src/client/traits.rs @@ -29,13 +29,13 @@ use error::{ImportResult, CallError}; use receipt::LocalizedReceipt; use trace::LocalizedTrace; use evm::{Factory as EvmFactory, Schedule}; -use types::ids::*; -use types::trace_filter::Filter as TraceFilter; use executive::Executed; use env_info::LastHashes; -use types::call_analytics::CallAnalytics; use block_import_error::BlockImportError; use ipc::IpcConfig; +use types::ids::*; +use types::trace_filter::Filter as TraceFilter; +use types::call_analytics::CallAnalytics; use types::blockchain_info::BlockChainInfo; use types::block_status::BlockStatus; use types::mode::Mode; @@ -235,6 +235,12 @@ pub trait BlockChainClient : Sync + Send { /// Set the mode. fn set_mode(&self, mode: Mode); + + /// Returns engine-related extra info for `BlockID`. + fn block_extra_info(&self, id: BlockID) -> Option>; + + /// Returns engine-related extra info for `UncleID`. + fn uncle_extra_info(&self, id: UncleID) -> Option>; } /// Extended client interface used for mining diff --git a/ethcore/src/engines/basic_authority.rs b/ethcore/src/engines/basic_authority.rs index 815d2b43a..5a55c6210 100644 --- a/ethcore/src/engines/basic_authority.rs +++ b/ethcore/src/engines/basic_authority.rs @@ -81,7 +81,7 @@ impl Engine for BasicAuthority { fn builtins(&self) -> &BTreeMap { &self.builtins } /// Additional engine-specific information for the user/developer concerning `header`. - fn extra_info(&self, _header: &Header) -> HashMap { hash_map!["signature".to_owned() => "TODO".to_owned()] } + fn extra_info(&self, _header: &Header) -> BTreeMap { map!["signature".to_owned() => "TODO".to_owned()] } fn schedule(&self, _env_info: &EnvInfo) -> Schedule { Schedule::new_homestead() diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index 7af88790c..52812f45e 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -47,7 +47,7 @@ pub trait Engine : Sync + Send { fn seal_fields(&self) -> usize { 0 } /// Additional engine-specific information for the user/developer concerning `header`. - fn extra_info(&self, _header: &Header) -> HashMap { HashMap::new() } + fn extra_info(&self, _header: &Header) -> BTreeMap { BTreeMap::new() } /// Additional information. fn additional_params(&self) -> HashMap { HashMap::new() } diff --git a/ethcore/src/ethereum/ethash.rs b/ethcore/src/ethereum/ethash.rs index 7fbb408cc..f2468dc95 100644 --- a/ethcore/src/ethereum/ethash.rs +++ b/ethcore/src/ethereum/ethash.rs @@ -133,8 +133,8 @@ impl Engine for Ethash { } /// Additional engine-specific information for the user/developer concerning `header`. - fn extra_info(&self, header: &Header) -> HashMap { - hash_map!["nonce".to_owned() => format!("0x{}", header.nonce().hex()), "mixHash".to_owned() => format!("0x{}", header.mix_hash().hex())] + fn extra_info(&self, header: &Header) -> BTreeMap { + map!["nonce".to_owned() => format!("0x{}", header.nonce().hex()), "mixHash".to_owned() => format!("0x{}", header.mix_hash().hex())] } fn schedule(&self, env_info: &EnvInfo) -> Schedule { diff --git a/ethcore/src/types/ids.rs b/ethcore/src/types/ids.rs index d248a45bc..1fe81f392 100644 --- a/ethcore/src/types/ids.rs +++ b/ethcore/src/types/ids.rs @@ -55,7 +55,7 @@ pub struct TraceId { } /// Uniquely identifies Uncle. -#[derive(Debug, Binary)] +#[derive(Debug, PartialEq, Eq, Copy, Clone, Binary)] pub struct UncleID { /// Block id. pub block: BlockID, diff --git a/rpc/src/v1/impls/eth.rs b/rpc/src/v1/impls/eth.rs index 18f2cc37c..a6dc86bc2 100644 --- a/rpc/src/v1/impls/eth.rs +++ b/rpc/src/v1/impls/eth.rs @@ -44,7 +44,7 @@ use ethcore::snapshot::SnapshotService; use self::ethash::SeedHashCompute; use v1::traits::Eth; use v1::types::{ - Block, BlockTransactions, BlockNumber, Bytes, SyncStatus, SyncInfo, + RichBlock, Block, BlockTransactions, BlockNumber, Bytes, SyncStatus, SyncInfo, Transaction, CallRequest, Index, Filter, Log, Receipt, Work, H64 as RpcH64, H256 as RpcH256, H160 as RpcH160, U256 as RpcU256, }; @@ -53,6 +53,8 @@ use v1::helpers::dispatch::{default_gas_price, dispatch_transaction}; use v1::helpers::block_import::is_major_importing; use v1::helpers::auto_args::Trailing; +const EXTRA_INFO_PROOF: &'static str = "Object exists in in blockchain (fetched earlier), extra_info is always available if object exists; qed"; + /// Eth RPC options pub struct EthClientOptions { /// Returns receipt from pending blocks @@ -117,36 +119,39 @@ impl EthClient where } } - fn block(&self, id: BlockID, include_txs: bool) -> Result, Error> { + fn block(&self, id: BlockID, include_txs: bool) -> Result, Error> { let client = take_weak!(self.client); match (client.block(id.clone()), client.block_total_difficulty(id)) { (Some(bytes), Some(total_difficulty)) => { let block_view = BlockView::new(&bytes); let view = block_view.header_view(); - let block = Block { - hash: Some(view.sha3().into()), - size: Some(bytes.len().into()), - parent_hash: view.parent_hash().into(), - uncles_hash: view.uncles_hash().into(), - author: view.author().into(), - miner: view.author().into(), - state_root: view.state_root().into(), - transactions_root: view.transactions_root().into(), - receipts_root: view.receipts_root().into(), - number: Some(view.number().into()), - gas_used: view.gas_used().into(), - gas_limit: view.gas_limit().into(), - logs_bloom: view.log_bloom().into(), - timestamp: view.timestamp().into(), - difficulty: view.difficulty().into(), - total_difficulty: total_difficulty.into(), - seal_fields: view.seal().into_iter().map(|f| rlp::decode(&f)).map(Bytes::new).collect(), - uncles: block_view.uncle_hashes().into_iter().map(Into::into).collect(), - transactions: match include_txs { - true => BlockTransactions::Full(block_view.localized_transactions().into_iter().map(Into::into).collect()), - false => BlockTransactions::Hashes(block_view.transaction_hashes().into_iter().map(Into::into).collect()), + let block = RichBlock { + block: Block { + hash: Some(view.sha3().into()), + size: Some(bytes.len().into()), + parent_hash: view.parent_hash().into(), + uncles_hash: view.uncles_hash().into(), + author: view.author().into(), + miner: view.author().into(), + state_root: view.state_root().into(), + transactions_root: view.transactions_root().into(), + receipts_root: view.receipts_root().into(), + number: Some(view.number().into()), + gas_used: view.gas_used().into(), + gas_limit: view.gas_limit().into(), + logs_bloom: view.log_bloom().into(), + timestamp: view.timestamp().into(), + difficulty: view.difficulty().into(), + total_difficulty: total_difficulty.into(), + seal_fields: view.seal().into_iter().map(|f| rlp::decode(&f)).map(Bytes::new).collect(), + uncles: block_view.uncle_hashes().into_iter().map(Into::into).collect(), + transactions: match include_txs { + true => BlockTransactions::Full(block_view.localized_transactions().into_iter().map(Into::into).collect()), + false => BlockTransactions::Hashes(block_view.transaction_hashes().into_iter().map(Into::into).collect()), + }, + extra_data: Bytes::new(view.extra_data()), }, - extra_data: Bytes::new(view.extra_data()) + extra_info: client.block_extra_info(id.clone()).expect(EXTRA_INFO_PROOF), }; Ok(Some(block)) }, @@ -161,7 +166,7 @@ impl EthClient where } } - fn uncle(&self, id: UncleID) -> Result, Error> { + fn uncle(&self, id: UncleID) -> Result, Error> { let client = take_weak!(self.client); let uncle: BlockHeader = match client.uncle(id) { Some(rlp) => rlp::decode(&rlp), @@ -172,27 +177,30 @@ impl EthClient where None => { return Ok(None); } }; - let block = Block { - hash: Some(uncle.hash().into()), - size: None, - parent_hash: uncle.parent_hash().clone().into(), - uncles_hash: uncle.uncles_hash().clone().into(), - author: uncle.author().clone().into(), - miner: uncle.author().clone().into(), - state_root: uncle.state_root().clone().into(), - transactions_root: uncle.transactions_root().clone().into(), - number: Some(uncle.number().into()), - gas_used: uncle.gas_used().clone().into(), - gas_limit: uncle.gas_limit().clone().into(), - logs_bloom: uncle.log_bloom().clone().into(), - timestamp: uncle.timestamp().into(), - difficulty: uncle.difficulty().clone().into(), - total_difficulty: (uncle.difficulty().clone() + parent_difficulty).into(), - receipts_root: uncle.receipts_root().clone().into(), - extra_data: uncle.extra_data().clone().into(), - seal_fields: uncle.seal().clone().into_iter().map(|f| rlp::decode(&f)).map(Bytes::new).collect(), - uncles: vec![], - transactions: BlockTransactions::Hashes(vec![]), + let block = RichBlock { + block: Block { + hash: Some(uncle.hash().into()), + size: None, + parent_hash: uncle.parent_hash().clone().into(), + uncles_hash: uncle.uncles_hash().clone().into(), + author: uncle.author().clone().into(), + miner: uncle.author().clone().into(), + state_root: uncle.state_root().clone().into(), + transactions_root: uncle.transactions_root().clone().into(), + number: Some(uncle.number().into()), + gas_used: uncle.gas_used().clone().into(), + gas_limit: uncle.gas_limit().clone().into(), + logs_bloom: uncle.log_bloom().clone().into(), + timestamp: uncle.timestamp().into(), + difficulty: uncle.difficulty().clone().into(), + total_difficulty: (uncle.difficulty().clone() + parent_difficulty).into(), + receipts_root: uncle.receipts_root().clone().into(), + extra_data: uncle.extra_data().clone().into(), + seal_fields: uncle.seal().clone().into_iter().map(|f| rlp::decode(&f)).map(Bytes::new).collect(), + uncles: vec![], + transactions: BlockTransactions::Hashes(vec![]), + }, + extra_info: client.uncle_extra_info(id).expect(EXTRA_INFO_PROOF), }; Ok(Some(block)) } @@ -435,13 +443,13 @@ impl Eth for EthClient where } } - fn block_by_hash(&self, hash: RpcH256, include_txs: bool) -> Result, Error> { + fn block_by_hash(&self, hash: RpcH256, include_txs: bool) -> Result, Error> { try!(self.active()); self.block(BlockID::Hash(hash.into()), include_txs) } - fn block_by_number(&self, num: BlockNumber, include_txs: bool) -> Result, Error> { + fn block_by_number(&self, num: BlockNumber, include_txs: bool) -> Result, Error> { try!(self.active()); self.block(num.into(), include_txs) @@ -483,13 +491,13 @@ impl Eth for EthClient where } } - fn uncle_by_block_hash_and_index(&self, hash: RpcH256, index: Index) -> Result, Error> { + fn uncle_by_block_hash_and_index(&self, hash: RpcH256, index: Index) -> Result, Error> { try!(self.active()); self.uncle(UncleID { block: BlockID::Hash(hash.into()), position: index.value() }) } - fn uncle_by_block_number_and_index(&self, num: BlockNumber, index: Index) -> Result, Error> { + fn uncle_by_block_number_and_index(&self, num: BlockNumber, index: Index) -> Result, Error> { try!(self.active()); self.uncle(UncleID { block: num.into(), position: index.value() }) diff --git a/rpc/src/v1/traits/eth.rs b/rpc/src/v1/traits/eth.rs index 7d9aa47f3..49f433ffd 100644 --- a/rpc/src/v1/traits/eth.rs +++ b/rpc/src/v1/traits/eth.rs @@ -17,7 +17,7 @@ //! Eth rpc interface. use jsonrpc_core::*; -use v1::types::{Block, BlockNumber, Bytes, CallRequest, Filter, FilterChanges, Index}; +use v1::types::{RichBlock, BlockNumber, Bytes, CallRequest, Filter, FilterChanges, Index}; use v1::types::{Log, Receipt, SyncStatus, Transaction, Work}; use v1::types::{H64, H160, H256, U256}; @@ -68,11 +68,11 @@ build_rpc_trait! { /// Returns block with given hash. #[rpc(name = "eth_getBlockByHash")] - fn block_by_hash(&self, H256, bool) -> Result, Error>; + fn block_by_hash(&self, H256, bool) -> Result, Error>; /// Returns block with given number. #[rpc(name = "eth_getBlockByNumber")] - fn block_by_number(&self, BlockNumber, bool) -> Result, Error>; + fn block_by_number(&self, BlockNumber, bool) -> Result, Error>; /// Returns the number of transactions sent from given address at given time (block number). #[rpc(name = "eth_getTransactionCount")] @@ -128,11 +128,11 @@ build_rpc_trait! { /// Returns an uncles at given block and index. #[rpc(name = "eth_getUncleByBlockHashAndIndex")] - fn uncle_by_block_hash_and_index(&self, H256, Index) -> Result, Error>; + fn uncle_by_block_hash_and_index(&self, H256, Index) -> Result, Error>; /// Returns an uncles at given block and index. #[rpc(name = "eth_getUncleByBlockNumberAndIndex")] - fn uncle_by_block_number_and_index(&self, BlockNumber, Index) -> Result, Error>; + fn uncle_by_block_number_and_index(&self, BlockNumber, Index) -> Result, Error>; /// Returns available compilers. #[rpc(name = "eth_getCompilers")] diff --git a/rpc/src/v1/types/block.rs b/rpc/src/v1/types/block.rs index 53d8c3583..f52785e90 100644 --- a/rpc/src/v1/types/block.rs +++ b/rpc/src/v1/types/block.rs @@ -14,6 +14,8 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +use std::ops::Deref; +use std::collections::BTreeMap; use serde::{Serialize, Serializer}; use v1::types::{Bytes, Transaction, H160, H256, H2048, U256}; @@ -93,11 +95,45 @@ pub struct Block { pub size: Option, } +/// Block representation with additional info +#[derive(Debug)] +pub struct RichBlock { + /// Standard block + pub block: Block, + /// Engine-specific fields with additional description. + /// Should be included directly to serialized block object. + #[serde(skip_serializing)] + pub extra_info: BTreeMap, +} + +impl Deref for RichBlock { + type Target = Block; + fn deref(&self) -> &Self::Target { + &self.block + } +} + +impl Serialize for RichBlock { + fn serialize(&self, serializer: &mut S) -> Result<(), S::Error> where S: Serializer { + use serde_json::{to_value, Value}; + + let serialized = (to_value(&self.block), to_value(&self.extra_info)); + if let (Value::Object(mut block), Value::Object(extras)) = serialized { + // join two objects + block.extend(extras); + // and serialize + try!(block.serialize(serializer)); + } + Ok(()) + } +} + #[cfg(test)] mod tests { + use std::collections::BTreeMap; use serde_json; - use v1::types::{Transaction, H160, H256, H2048, Bytes, U256}; - use super::{Block, BlockTransactions}; + use v1::types::{Transaction, H64, H160, H256, H2048, Bytes, U256}; + use super::{Block, RichBlock, BlockTransactions}; #[test] fn test_serialize_block_transactions() { @@ -134,8 +170,17 @@ mod tests { transactions: BlockTransactions::Hashes(vec![].into()), size: Some(69.into()), }; + let serialized_block = serde_json::to_string(&block).unwrap(); + let rich_block = RichBlock { + block: block, + extra_info: map![ + "mixHash".into() => format!("0x{:?}", H256::default()), + "nonce".into() => format!("0x{:?}", H64::default()) + ], + }; + let serialized_rich_block = serde_json::to_string(&rich_block).unwrap(); - let serialized = serde_json::to_string(&block).unwrap(); - assert_eq!(serialized, r#"{"hash":"0x0000000000000000000000000000000000000000000000000000000000000000","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000","sha3Uncles":"0x0000000000000000000000000000000000000000000000000000000000000000","author":"0x0000000000000000000000000000000000000000","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","receiptsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","number":"0x0","gasUsed":"0x0","gasLimit":"0x0","extraData":"0x","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","timestamp":"0x0","difficulty":"0x0","totalDifficulty":"0x0","sealFields":["0x","0x"],"uncles":[],"transactions":[],"size":"0x45"}"#); + assert_eq!(serialized_block, r#"{"hash":"0x0000000000000000000000000000000000000000000000000000000000000000","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000","sha3Uncles":"0x0000000000000000000000000000000000000000000000000000000000000000","author":"0x0000000000000000000000000000000000000000","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","receiptsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","number":"0x0","gasUsed":"0x0","gasLimit":"0x0","extraData":"0x","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","timestamp":"0x0","difficulty":"0x0","totalDifficulty":"0x0","sealFields":["0x","0x"],"uncles":[],"transactions":[],"size":"0x45"}"#); + assert_eq!(serialized_rich_block, r#"{"author":"0x0000000000000000000000000000000000000000","difficulty":"0x0","extraData":"0x","gasLimit":"0x0","gasUsed":"0x0","hash":"0x0000000000000000000000000000000000000000000000000000000000000000","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","miner":"0x0000000000000000000000000000000000000000","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","number":"0x0","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000","receiptsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","sealFields":["0x","0x"],"sha3Uncles":"0x0000000000000000000000000000000000000000000000000000000000000000","size":"0x45","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","timestamp":"0x0","totalDifficulty":"0x0","transactions":[],"transactionsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","uncles":[]}"#); } } diff --git a/rpc/src/v1/types/mod.rs.in b/rpc/src/v1/types/mod.rs.in index 002fcecca..cba7487fc 100644 --- a/rpc/src/v1/types/mod.rs.in +++ b/rpc/src/v1/types/mod.rs.in @@ -35,7 +35,7 @@ mod work; mod histogram; pub use self::bytes::Bytes; -pub use self::block::{Block, BlockTransactions}; +pub use self::block::{RichBlock, Block, BlockTransactions}; pub use self::block_number::BlockNumber; pub use self::call_request::CallRequest; pub use self::confirmations::{ConfirmationPayload, ConfirmationRequest, TransactionModification}; From 25631893163a5bdde6791445256428af31a87b21 Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Fri, 4 Nov 2016 17:06:13 +0000 Subject: [PATCH 08/62] [ci skip] js-precompiled 20161104-170459 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e557eed33..b730302ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1249,7 +1249,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#5b8f2816aef4f6fa970c5ca0947b07eda248a40d" +source = "git+https://github.com/ethcore/js-precompiled.git#1c46ef690c59e67701c6e3f2f8b431f4288c0e47" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/js/package.json b/js/package.json index 343463892..16f875581 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.1", + "version": "0.2.2", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", From 501b2cdd18617db32f97f995a3c1f80e9a4ca49d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Fri, 4 Nov 2016 18:33:10 +0100 Subject: [PATCH 09/62] Add error for sendRawTransaction and estimateGas (#3194) * sendRawTransaction invalid RLP error * Returning proper error for estimate_gas --- rpc/src/v1/helpers/errors.rs | 9 +++++++++ rpc/src/v1/impls/eth.rs | 18 ++++++++++-------- rpc/src/v1/tests/mocked/eth.rs | 17 +++++++++++++++++ 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/rpc/src/v1/helpers/errors.rs b/rpc/src/v1/helpers/errors.rs index 8cbf26b7c..50c22b187 100644 --- a/rpc/src/v1/helpers/errors.rs +++ b/rpc/src/v1/helpers/errors.rs @@ -21,6 +21,7 @@ macro_rules! rpc_unimplemented { } use std::fmt; +use rlp::DecoderError; use ethcore::error::{Error as EthcoreError, CallError}; use ethcore::account_provider::{Error as AccountError}; use fetch::FetchError; @@ -271,6 +272,14 @@ pub fn from_transaction_error(error: EthcoreError) -> Error { } } +pub fn from_rlp_error(error: DecoderError) -> Error { + Error { + code: ErrorCode::InvalidParams, + message: "Invalid RLP.".into(), + data: Some(Value::String(format!("{:?}", error))), + } +} + pub fn from_call_error(error: CallError) -> Error { match error { CallError::StatePruned => state_pruned(), diff --git a/rpc/src/v1/impls/eth.rs b/rpc/src/v1/impls/eth.rs index a6dc86bc2..4e54078fb 100644 --- a/rpc/src/v1/impls/eth.rs +++ b/rpc/src/v1/impls/eth.rs @@ -611,7 +611,7 @@ impl Eth for EthClient where let raw_transaction = raw.to_vec(); match UntrustedRlp::new(&raw_transaction).as_val() { Ok(signed_transaction) => dispatch_transaction(&*take_weak!(self.client), &*take_weak!(self.miner), signed_transaction), - Err(_) => Ok(RpcH256::from(H256::from(0))), + Err(e) => Err(errors::from_rlp_error(e)), } } @@ -621,15 +621,15 @@ impl Eth for EthClient where let request = CallRequest::into(request); let signed = try!(self.sign_call(request)); - let r = match num.0 { + let result = match num.0 { BlockNumber::Pending => take_weak!(self.miner).call(&*take_weak!(self.client), &signed, Default::default()), num => take_weak!(self.client).call(&signed, num.into(), Default::default()), }; - match r { - Ok(b) => Ok(Bytes(b.output)), - Err(e) => Err(errors::from_call_error(e)), - } + + result + .map(|b| b.output.into()) + .map_err(errors::from_call_error) } fn estimate_gas(&self, request: CallRequest, num: Trailing) -> Result { @@ -637,12 +637,14 @@ impl Eth for EthClient where let request = CallRequest::into(request); let signed = try!(self.sign_call(request)); - let r = match num.0 { + let result = match num.0 { BlockNumber::Pending => take_weak!(self.miner).call(&*take_weak!(self.client), &signed, Default::default()), num => take_weak!(self.client).call(&signed, num.into(), Default::default()), }; - Ok(RpcU256::from(r.map(|res| res.gas_used + res.refunded).unwrap_or(From::from(0)))) + result + .map(|res| (res.gas_used + res.refunded).into()) + .map_err(errors::from_call_error) } fn compile_lll(&self, _: String) -> Result { diff --git a/rpc/src/v1/tests/mocked/eth.rs b/rpc/src/v1/tests/mocked/eth.rs index f69e73b62..d324715d6 100644 --- a/rpc/src/v1/tests/mocked/eth.rs +++ b/rpc/src/v1/tests/mocked/eth.rs @@ -805,6 +805,23 @@ fn rpc_eth_send_transaction_error() { assert_eq!(tester.io.handle_request_sync(&request), Some(response.into())); } +#[test] +fn rpc_eth_send_raw_transaction_error() { + let tester = EthTester::default(); + + let req = r#"{ + "jsonrpc": "2.0", + "method": "eth_sendRawTransaction", + "params": [ + "0x0123" + ], + "id": 1 + }"#; + let res = r#"{"jsonrpc":"2.0","error":{"code":-32602,"message":"Invalid RLP.","data":"RlpIncorrectListLen"},"id":1}"#.into(); + + assert_eq!(tester.io.handle_request_sync(&req), Some(res)); +} + #[test] fn rpc_eth_send_raw_transaction() { let tester = EthTester::default(); From 7f0310921d8f0d2d6ab3911ae7d056e71b186e6c Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Fri, 4 Nov 2016 17:52:19 +0000 Subject: [PATCH 10/62] [ci skip] js-precompiled 20161104-175104 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b730302ea..89840b121 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1249,7 +1249,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#1c46ef690c59e67701c6e3f2f8b431f4288c0e47" +source = "git+https://github.com/ethcore/js-precompiled.git#53bb41071b5b87d0d9207ccee426f9932ffbb941" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/js/package.json b/js/package.json index 16f875581..2ed463574 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.2", + "version": "0.2.3", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", From 83beedc27e3905ad5cc8924fe4eec4e4e4381f6d Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Fri, 4 Nov 2016 19:52:39 +0100 Subject: [PATCH 11/62] expose api as window.secureApi (#3207) --- js/src/index.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/js/src/index.js b/js/src/index.js index 2321b5cf6..a4b5d5c59 100644 --- a/js/src/index.js +++ b/js/src/index.js @@ -59,6 +59,8 @@ const store = initStore(api); store.dispatch({ type: 'initAll', api }); store.dispatch(setApi(api)); +window.secureApi = api; + const routerHistory = useRouterHistory(createHashHistory)({}); ReactDOM.render( From 9db28e12acb0af9d4093a9d9f94fb0ae371d19aa Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Fri, 4 Nov 2016 19:11:43 +0000 Subject: [PATCH 12/62] [ci skip] js-precompiled 20161104-191028 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 89840b121..64eea908c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1249,7 +1249,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#53bb41071b5b87d0d9207ccee426f9932ffbb941" +source = "git+https://github.com/ethcore/js-precompiled.git#388fd60f326f99782100b42c4ec28bc09c5ad44c" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/js/package.json b/js/package.json index 2ed463574..dabed9b9d 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.3", + "version": "0.2.4", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", From f9f37f1c8478101dd8dfc9b52782dd71da3e8271 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Fri, 4 Nov 2016 23:06:03 +0100 Subject: [PATCH 13/62] Add copy address button to Contract deploy (#3199) * Allow copy of deployed address * Pre-parse/clean JSON inputs * Revert cleanup --- js/src/modals/DeployContract/deployContract.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/js/src/modals/DeployContract/deployContract.js b/js/src/modals/DeployContract/deployContract.js index c62b968b5..8dd57ffd1 100644 --- a/js/src/modals/DeployContract/deployContract.js +++ b/js/src/modals/DeployContract/deployContract.js @@ -18,7 +18,7 @@ import React, { Component, PropTypes } from 'react'; import ActionDoneAll from 'material-ui/svg-icons/action/done-all'; import ContentClear from 'material-ui/svg-icons/content/clear'; -import { BusyStep, CompletedStep, Button, IdentityIcon, Modal, TxHash } from '../../ui'; +import { BusyStep, CompletedStep, CopyToClipboard, Button, IdentityIcon, Modal, TxHash } from '../../ui'; import { ERRORS, validateAbi, validateCode, validateName } from '../../util/validation'; import DetailsStep from './DetailsStep'; @@ -155,6 +155,7 @@ export default class DeployContract extends Component {

Your contract has been deployed at
+
{ address }
From c2e85dc4d5f15d81f527a850ad134fe955ab733a Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Fri, 4 Nov 2016 23:08:12 +0100 Subject: [PATCH 14/62] Use ethcore_dappsPort when constructing URLs (#3139) * Upon connect, retrieve the available api ports * Update dapps to load from dappsPort * Update dapps summary with dappsPort * Allow proxy to use dappsPort * Replace /api/ping with HEAD / * Dynamic port for available apps * Retrieve content images with dappsPort * Fix / * Transfer token dropdown image fix * IdentityIcon loads images via contentHash * Update apps fetch to cater for dev & prod * DRY up 127.0.0.1:${dappsPort} with ${dappsUrl} --- js/src/modals/Transfer/Details/details.js | 8 +++++- js/src/redux/providers/imagesReducer.js | 4 +-- js/src/redux/providers/status.js | 4 +-- js/src/secureApi.js | 32 +++++++++++++++++++++-- js/src/ui/Balance/balance.js | 8 +++++- js/src/ui/IdentityIcon/identityIcon.js | 5 ++-- js/src/views/Dapp/dapp.js | 18 +++++++++---- js/src/views/Dapps/Summary/summary.js | 11 +++++--- js/src/views/Dapps/registry.js | 15 ++++++----- js/src/views/Settings/Proxy/proxy.js | 9 +++++-- 10 files changed, 85 insertions(+), 29 deletions(-) diff --git a/js/src/modals/Transfer/Details/details.js b/js/src/modals/Transfer/Details/details.js index f7cc70ad4..decd69c3c 100644 --- a/js/src/modals/Transfer/Details/details.js +++ b/js/src/modals/Transfer/Details/details.js @@ -123,7 +123,13 @@ export default class Details extends Component { .map((balance, index) => { const token = balance.token; const isEth = index === 0; - const imagesrc = token.image || images[token.address] || imageUnknown; + let imagesrc = token.image; + if (!imagesrc) { + imagesrc = + images[token.address] + ? `${api.dappsUrl}${images[token.address]}` + : imageUnknown; + } let value = 0; if (isEth) { diff --git a/js/src/redux/providers/imagesReducer.js b/js/src/redux/providers/imagesReducer.js index 15745932d..396576cc8 100644 --- a/js/src/redux/providers/imagesReducer.js +++ b/js/src/redux/providers/imagesReducer.js @@ -17,8 +17,6 @@ import { handleActions } from 'redux-actions'; import { bytesToHex } from '../../api/util/format'; -import { parityNode } from '../../environment'; - const ZERO = '0x0000000000000000000000000000000000000000000000000000000000000000'; const initialState = { @@ -28,7 +26,7 @@ const initialState = { export function hashToImageUrl (hashArray) { const hash = hashArray ? bytesToHex(hashArray) : ZERO; - return hash === ZERO ? null : `${parityNode}/api/content/${hash.substr(2)}`; + return hash === ZERO ? null : `/api/content/${hash.substr(2)}`; } export default handleActions({ diff --git a/js/src/redux/providers/status.js b/js/src/redux/providers/status.js index 658f54197..21712f2a9 100644 --- a/js/src/redux/providers/status.js +++ b/js/src/redux/providers/status.js @@ -16,8 +16,6 @@ import { statusBlockNumber, statusCollection, statusLogs } from './statusActions'; -import { parityNode } from '../../environment'; - export default class Status { constructor (store, api) { this._api = api; @@ -65,7 +63,7 @@ export default class Status { setTimeout(this._pollPing, timeout); }; - fetch(`${parityNode}/api/ping`, { method: 'GET' }) + fetch('/', { method: 'HEAD' }) .then((response) => dispatch(!!response.ok)) .catch(() => dispatch(false)); } diff --git a/js/src/secureApi.js b/js/src/secureApi.js index 545cfd459..d577e7185 100644 --- a/js/src/secureApi.js +++ b/js/src/secureApi.js @@ -25,6 +25,8 @@ export default class SecureApi extends Api { this._isConnecting = true; this._connectState = 0; this._needsToken = false; + this._dappsPort = 8080; + this._signerPort = 8180; this._followConnection(); } @@ -50,7 +52,7 @@ export default class SecureApi extends Api { case 0: if (isConnected) { this._isConnecting = false; - return this.setToken(); + return this.connectSuccess(); } else if (lastError) { this.updateToken('initial', 1); } @@ -79,7 +81,7 @@ export default class SecureApi extends Api { case 2: if (isConnected) { this._isConnecting = false; - return this.setToken(); + return this.connectSuccess(); } else if (lastError) { return setManual(); } @@ -89,12 +91,38 @@ export default class SecureApi extends Api { nextTick(); } + connectSuccess () { + this.setToken(); + + Promise + .all([ + this.ethcore.dappsPort(), + this.ethcore.signerPort() + ]) + .then(([dappsPort, signerPort]) => { + this._dappsPort = dappsPort.toNumber(); + this._signerPort = signerPort.toNumber(); + }); + } + updateToken (token, connectedState = 0) { this._connectState = connectedState; this._transport.updateToken(token.replace(/[^a-zA-Z0-9]/g, '')); this._followConnection(); } + get dappsPort () { + return this._dappsPort; + } + + get dappsUrl () { + return `http://127.0.0.1:${this._dappsPort}`; + } + + get signerPort () { + return this._signerPort; + } + get isConnecting () { return this._isConnecting; } diff --git a/js/src/ui/Balance/balance.js b/js/src/ui/Balance/balance.js index b6f786648..22b9fc1ae 100644 --- a/js/src/ui/Balance/balance.js +++ b/js/src/ui/Balance/balance.js @@ -47,7 +47,13 @@ class Balance extends Component { const value = token.format ? new BigNumber(balance.value).div(new BigNumber(token.format)).toFormat(3) : api.util.fromWei(balance.value).toFormat(3); - const imagesrc = token.image || images[token.address] || unknownImage; + let imagesrc = token.image; + if (!imagesrc) { + imagesrc = + images[token.address] + ? `${api.dappsUrl}${images[token.address]}` + : unknownImage; + } return (
- :
 
; + let image =
 
; + + if (app.image) { + image = ; + } else if (app.iconUrl) { + image = ; + } return ( diff --git a/js/src/views/Dapps/registry.js b/js/src/views/Dapps/registry.js index 57668c651..8975f2091 100644 --- a/js/src/views/Dapps/registry.js +++ b/js/src/views/Dapps/registry.js @@ -16,8 +16,6 @@ import BigNumber from 'bignumber.js'; -import { parityNode } from '../../environment'; - const builtinApps = [ { id: '0xf9f2d620c2e08f83e45555247146c62185e4ab7cf82a4b9002a265a0d020348f', @@ -76,6 +74,12 @@ const networkApps = [ } ]; +function getHost (api) { + return process.env.NODE_ENV === 'production' + ? api.dappsUrl + : ''; +} + export function fetchAvailable (api) { // TODO: Since we don't have an extensive GithubHint app, get the value somehow // RESULT: 0x22cd66e1b05882c0fa17a16d252d3b3ee2238ccbac8153f69a35c83f02ca76ee @@ -84,8 +88,7 @@ export function fetchAvailable (api) { // .then((sha3) => { // console.log('archive', sha3); // }); - - return fetch(`${parityNode}/api/apps`) + return fetch(`${getHost(api)}/api/apps`) .then((response) => { return response.ok ? response.json() @@ -134,8 +137,8 @@ export function fetchAvailable (api) { }); } -export function fetchManifest (app, contentHash) { - return fetch(`${parityNode}/${contentHash}/manifest.json`) +export function fetchManifest (api, app, contentHash) { + return fetch(`${getHost(api)}/${contentHash}/manifest.json`) .then((response) => { return response.ok ? response.json() diff --git a/js/src/views/Settings/Proxy/proxy.js b/js/src/views/Settings/Proxy/proxy.js index 8c68a8cfe..db32d3461 100644 --- a/js/src/views/Settings/Proxy/proxy.js +++ b/js/src/views/Settings/Proxy/proxy.js @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import React, { Component } from 'react'; +import React, { Component, PropTypes } from 'react'; import { Container, ContainerTitle } from '../../../ui'; @@ -22,8 +22,13 @@ import layout from '../layout.css'; import styles from './proxy.css'; export default class Proxy extends Component { + static contextTypes = { + api: PropTypes.object.isRequired + } + render () { - const proxyurl = 'http://127.0.0.1:8080/proxy/proxy.pac'; + const { dappsUrl } = this.context.api; + const proxyurl = `${dappsUrl}/proxy/proxy.pac`; return ( From 2a19c33b8d31042e2b1f78f6108141da8140ff74 Mon Sep 17 00:00:00 2001 From: Igor Artamonov Date: Sat, 5 Nov 2016 04:09:23 +0600 Subject: [PATCH 15/62] delay bomb for Classic (ECIP-1010) (#3179) * delay bomb for classic (ECIP-1010) * formatting fix after core review, rel [e6b5093] --- ethcore/res/ethereum/classic.json | 4 +- ethcore/src/ethereum/ethash.rs | 146 ++++++++++++++++++++++++++++-- ethcore/src/tests/helpers.rs | 27 ++++++ json/src/spec/ethash.rs | 7 ++ 4 files changed, 177 insertions(+), 7 deletions(-) diff --git a/ethcore/res/ethereum/classic.json b/ethcore/res/ethereum/classic.json index d8749ba91..7c1e9454e 100644 --- a/ethcore/res/ethereum/classic.json +++ b/ethcore/res/ethereum/classic.json @@ -15,7 +15,9 @@ "eip155Transition": "0x7fffffffffffffff", "eip160Transition": "0x7fffffffffffffff", "eip161abcTransition": "0x7fffffffffffffff", - "eip161dTransition": "0x7fffffffffffffff" + "eip161dTransition": "0x7fffffffffffffff", + "ecip1010PauseTransition": "0x2dc6c0", + "ecip1010ContinueTransition": "0x4c4b40" } } }, diff --git a/ethcore/src/ethereum/ethash.rs b/ethcore/src/ethereum/ethash.rs index f2468dc95..8b9688569 100644 --- a/ethcore/src/ethereum/ethash.rs +++ b/ethcore/src/ethereum/ethash.rs @@ -70,6 +70,10 @@ pub struct EthashParams { pub eip161abc_transition: u64, /// Number of first block where EIP-161.d begins. pub eip161d_transition: u64, + /// Number of first block where ECIP-1010 begins. + pub ecip1010_pause_transition: u64, + /// Number of first block where ECIP-1010 ends. + pub ecip1010_continue_transition: u64 } impl From for EthashParams { @@ -94,6 +98,8 @@ impl From for EthashParams { eip160_transition: p.eip160_transition.map_or(0, Into::into), eip161abc_transition: p.eip161abc_transition.map_or(0, Into::into), eip161d_transition: p.eip161d_transition.map_or(0x7fffffffffffffff, Into::into), + ecip1010_pause_transition: p.ecip1010_pause_transition.map_or(0x7fffffffffffffff, Into::into), + ecip1010_continue_transition: p.ecip1010_continue_transition.map_or(0x7fffffffffffffff, Into::into), } } } @@ -353,9 +359,20 @@ impl Ethash { }; target = max(min_difficulty, target); if header.number() < self.ethash_params.bomb_defuse_transition { - let period = ((parent.number() + 1) / EXP_DIFF_PERIOD) as usize; - if period > 1 { - target = max(min_difficulty, target + (U256::from(1) << (period - 2))); + if header.number() < self.ethash_params.ecip1010_pause_transition { + let period = ((parent.number() + 1) / EXP_DIFF_PERIOD) as usize; + if period > 1 { + target = max(min_difficulty, target + (U256::from(1) << (period - 2))); + } + } + else if header.number() < self.ethash_params.ecip1010_continue_transition { + let fixed_difficulty = ((self.ethash_params.ecip1010_pause_transition / EXP_DIFF_PERIOD) - 2) as usize; + target = max(min_difficulty, target + (U256::from(1) << fixed_difficulty)); + } + else { + let period = ((parent.number() + 1) / EXP_DIFF_PERIOD) as usize; + let delay = ((self.ethash_params.ecip1010_continue_transition - self.ethash_params.ecip1010_pause_transition) / EXP_DIFF_PERIOD) as usize; + target = max(min_difficulty, target + (U256::from(1) << (period - delay - 2))); } } target @@ -414,8 +431,8 @@ mod tests { use env_info::EnvInfo; use error::{BlockError, Error}; use header::Header; - use super::super::new_morden; - use super::Ethash; + use super::super::{new_morden, new_homestead_test}; + use super::{Ethash, EthashParams}; use rlp; #[test] @@ -637,5 +654,122 @@ mod tests { assert_eq!(Ethash::difficulty_to_boundary(&U256::from(32)), H256::from_str("0800000000000000000000000000000000000000000000000000000000000000").unwrap()); } - // TODO: difficulty test + #[test] + fn difficulty_frontier() { + let spec = new_homestead_test(); + let ethparams = get_default_ethash_params(); + let ethash = Ethash::new(spec.params, ethparams, BTreeMap::new()); + + let mut parent_header = Header::default(); + parent_header.set_number(1000000); + parent_header.set_difficulty(U256::from_str("b69de81a22b").unwrap()); + parent_header.set_timestamp(1455404053); + let mut header = Header::default(); + header.set_number(parent_header.number() + 1); + header.set_timestamp(1455404058); + + let difficulty = ethash.calculate_difficulty(&header, &parent_header); + assert_eq!(U256::from_str("b6b4bbd735f").unwrap(), difficulty); + } + + #[test] + fn difficulty_homestead() { + let spec = new_homestead_test(); + let ethparams = get_default_ethash_params(); + let ethash = Ethash::new(spec.params, ethparams, BTreeMap::new()); + + let mut parent_header = Header::default(); + parent_header.set_number(1500000); + parent_header.set_difficulty(U256::from_str("1fd0fd70792b").unwrap()); + parent_header.set_timestamp(1463003133); + let mut header = Header::default(); + header.set_number(parent_header.number() + 1); + header.set_timestamp(1463003177); + + let difficulty = ethash.calculate_difficulty(&header, &parent_header); + assert_eq!(U256::from_str("1fc50f118efe").unwrap(), difficulty); + } + + #[test] + fn difficulty_classic_bomb_delay() { + let spec = new_homestead_test(); + let ethparams = EthashParams { + ecip1010_pause_transition: 3000000, + ..get_default_ethash_params() + }; + let ethash = Ethash::new(spec.params, ethparams, BTreeMap::new()); + + let mut parent_header = Header::default(); + parent_header.set_number(3500000); + parent_header.set_difficulty(U256::from_str("6F62EAF8D3C").unwrap()); + parent_header.set_timestamp(1452838500); + let mut header = Header::default(); + header.set_number(parent_header.number() + 1); + + header.set_timestamp(parent_header.timestamp() + 20); + assert_eq!( + U256::from_str("6F55FE9B74B").unwrap(), + ethash.calculate_difficulty(&header, &parent_header) + ); + header.set_timestamp(parent_header.timestamp() + 5); + assert_eq!( + U256::from_str("6F71D75632D").unwrap(), + ethash.calculate_difficulty(&header, &parent_header) + ); + header.set_timestamp(parent_header.timestamp() + 80); + assert_eq!( + U256::from_str("6F02746B3A5").unwrap(), + ethash.calculate_difficulty(&header, &parent_header) + ); + } + + #[test] + fn test_difficulty_bomb_continue() { + let spec = new_homestead_test(); + let ethparams = EthashParams { + ecip1010_pause_transition: 3000000, + ecip1010_continue_transition: 5000000, + ..get_default_ethash_params() + }; + let ethash = Ethash::new(spec.params, ethparams, BTreeMap::new()); + + let mut parent_header = Header::default(); + parent_header.set_number(5000102); + parent_header.set_difficulty(U256::from_str("14944397EE8B").unwrap()); + parent_header.set_timestamp(1513175023); + let mut header = Header::default(); + header.set_number(parent_header.number() + 1); + header.set_timestamp(parent_header.timestamp() + 6); + assert_eq!( + U256::from_str("1496E6206188").unwrap(), + ethash.calculate_difficulty(&header, &parent_header) + ); + parent_header.set_number(5100123); + parent_header.set_difficulty(U256::from_str("14D24B39C7CF").unwrap()); + parent_header.set_timestamp(1514609324); + header.set_number(parent_header.number() + 1); + header.set_timestamp(parent_header.timestamp() + 41); + assert_eq!( + U256::from_str("14CA9C5D9227").unwrap(), + ethash.calculate_difficulty(&header, &parent_header) + ); + parent_header.set_number(6150001); + parent_header.set_difficulty(U256::from_str("305367B57227").unwrap()); + parent_header.set_timestamp(1529664575); + header.set_number(parent_header.number() + 1); + header.set_timestamp(parent_header.timestamp() + 105); + assert_eq!( + U256::from_str("309D09E0C609").unwrap(), + ethash.calculate_difficulty(&header, &parent_header) + ); + parent_header.set_number(8000000); + parent_header.set_difficulty(U256::from_str("1180B36D4CE5B6A").unwrap()); + parent_header.set_timestamp(1535431724); + header.set_number(parent_header.number() + 1); + header.set_timestamp(parent_header.timestamp() + 420); + assert_eq!( + U256::from_str("5126FFD5BCBB9E7").unwrap(), + ethash.calculate_difficulty(&header, &parent_header) + ); + } } diff --git a/ethcore/src/tests/helpers.rs b/ethcore/src/tests/helpers.rs index ac8ce1885..96d5f8366 100644 --- a/ethcore/src/tests/helpers.rs +++ b/ethcore/src/tests/helpers.rs @@ -28,6 +28,7 @@ use evm::Schedule; use engines::Engine; use env_info::EnvInfo; use ethereum; +use ethereum::ethash::EthashParams; use devtools::*; use miner::Miner; use header::Header; @@ -421,3 +422,29 @@ pub fn get_bad_state_dummy_block() -> Bytes { create_test_block(&block_header) } + +pub fn get_default_ethash_params() -> EthashParams{ + EthashParams { + gas_limit_bound_divisor: U256::from(1024), + minimum_difficulty: U256::from(131072), + difficulty_bound_divisor: U256::from(2048), + difficulty_increment_divisor: 10, + duration_limit: 13, + block_reward: U256::from(0), + registrar: "0000000000000000000000000000000000000001".into(), + homestead_transition: 1150000, + dao_hardfork_transition: 0x7fffffffffffffff, + dao_hardfork_beneficiary: "0000000000000000000000000000000000000001".into(), + dao_hardfork_accounts: vec![], + difficulty_hardfork_transition: 0x7fffffffffffffff, + difficulty_hardfork_bound_divisor: U256::from(0), + bomb_defuse_transition: 0x7fffffffffffffff, + eip150_transition: 0x7fffffffffffffff, + eip155_transition: 0x7fffffffffffffff, + eip160_transition: 0x7fffffffffffffff, + eip161abc_transition: 0x7fffffffffffffff, + eip161d_transition: 0x7fffffffffffffff, + ecip1010_pause_transition: 0x7fffffffffffffff, + ecip1010_continue_transition: 0x7fffffffffffffff + } +} diff --git a/json/src/spec/ethash.rs b/json/src/spec/ethash.rs index f8412bb97..10f0c6293 100644 --- a/json/src/spec/ethash.rs +++ b/json/src/spec/ethash.rs @@ -85,6 +85,13 @@ pub struct EthashParams { /// See main EthashParams docs. #[serde(rename="eip161dTransition")] pub eip161d_transition: Option, + + /// See main EthashParams docs. + #[serde(rename="ecip1010PauseTransition")] + pub ecip1010_pause_transition: Option, + /// See main EthashParams docs. + #[serde(rename="ecip1010ContinueTransition")] + pub ecip1010_continue_transition: Option, } /// Ethash engine deserialization. From 20bb0e5f59c60f45efb6d6b158997fc99fa12a4e Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Fri, 4 Nov 2016 22:24:43 +0000 Subject: [PATCH 16/62] [ci skip] js-precompiled 20161104-222329 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 64eea908c..780405594 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1249,7 +1249,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#388fd60f326f99782100b42c4ec28bc09c5ad44c" +source = "git+https://github.com/ethcore/js-precompiled.git#acb1fca2b94bae60bb2ad5dccc838c0546260000" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/js/package.json b/js/package.json index dabed9b9d..92c01db6e 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.4", + "version": "0.2.5", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", From 67715da82760161442646d7c8a63cb538f7e64f8 Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Fri, 4 Nov 2016 22:28:04 +0000 Subject: [PATCH 17/62] [ci skip] js-precompiled 20161104-222646 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 780405594..45f8603c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1249,7 +1249,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#acb1fca2b94bae60bb2ad5dccc838c0546260000" +source = "git+https://github.com/ethcore/js-precompiled.git#43870a913c292d258df8bc2a4ad5c18a8e6a3f78" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/js/package.json b/js/package.json index 92c01db6e..ae68ef370 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.5", + "version": "0.2.6", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", From 08d92fe3aa40267430e91121f158647d1a5be020 Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Fri, 4 Nov 2016 22:31:01 +0000 Subject: [PATCH 18/62] [ci skip] js-precompiled 20161104-222946 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 45f8603c8..986754370 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1249,7 +1249,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#43870a913c292d258df8bc2a4ad5c18a8e6a3f78" +source = "git+https://github.com/ethcore/js-precompiled.git#ff854bd835211d845b833c46285d08d34701168c" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/js/package.json b/js/package.json index ae68ef370..84a512168 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.6", + "version": "0.2.7", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", From 0e9d70a85ddc991798c76c8b45da8dc42260bb25 Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Sat, 5 Nov 2016 05:34:04 +0000 Subject: [PATCH 19/62] [ci skip] js-precompiled 20161105-053249 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 986754370..0219e1023 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1249,7 +1249,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#ff854bd835211d845b833c46285d08d34701168c" +source = "git+https://github.com/ethcore/js-precompiled.git#04810ed1239a9e12170ec68c762706c61c0b0ecd" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/js/package.json b/js/package.json index 84a512168..3569531e6 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.7", + "version": "0.2.8", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", From 8351c5d500554c78a913b1ed1ad2d79b4605f17e Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Sat, 5 Nov 2016 05:38:51 +0000 Subject: [PATCH 20/62] [ci skip] js-precompiled 20161105-053734 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0219e1023..986b78b86 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1249,7 +1249,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#04810ed1239a9e12170ec68c762706c61c0b0ecd" +source = "git+https://github.com/ethcore/js-precompiled.git#e74c88d14eb28e829288cc2f702b110e364332d6" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/js/package.json b/js/package.json index 3569531e6..5edd47f26 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.8", + "version": "0.2.9", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", From 744501c45460098be5ad41d9b7783c84fc92a0d4 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 5 Nov 2016 10:38:00 +0100 Subject: [PATCH 21/62] Mode improvements for UI (#3109) * `--mode=off` now works. * Add Mode::Off as a persistent CLI option. * "last" not "auto" as default. * Commit accidentally unsaved file. * Whitespace [ci:skip] * Mode CLI parse fix * or offline * Save mode when it gets changed. * Fix Offline mode * Fix up mode operations. * Make passive default, but not overriding. * Fix test * Maybe not everyone wants to run an archive node... --- ethcore/src/client/client.rs | 40 +++++++++++++++++++++++----- ethcore/src/client/config.rs | 12 +++++++++ ethcore/src/types/mode.rs | 2 +- js/src/jsonrpc/interfaces/ethcore.js | 4 +-- js/src/views/Status/data/rpc.json | 18 +++++++++++++ mac/post-install.sh | 8 +++--- parity/blockchain.rs | 6 ++--- parity/cli/config.full.toml | 2 +- parity/cli/mod.rs | 7 ++--- parity/cli/usage.txt | 6 ++--- parity/configuration.rs | 21 ++------------- parity/helpers.rs | 5 ++-- parity/params.rs | 5 ++++ parity/run.rs | 33 ++++++++++++++++------- parity/snapshot.rs | 3 +-- parity/user_defaults.rs | 38 +++++++++++++++++++++++++- rpc/src/v1/impls/ethcore.rs | 2 +- rpc/src/v1/impls/ethcore_set.rs | 2 +- rpc/src/v1/traits/ethcore.rs | 2 +- rpc/src/v1/traits/ethcore_set.rs | 4 +-- 20 files changed, 154 insertions(+), 66 deletions(-) diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 777d561e3..b1a70454d 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -147,6 +147,7 @@ pub struct Client { factories: Factories, history: u64, rng: Mutex, + on_mode_change: Mutex>>, } impl Client { @@ -211,7 +212,7 @@ impl Client { let panic_handler = PanicHandler::new_in_arc(); panic_handler.forward_from(&block_queue); - let awake = match config.mode { Mode::Dark(..) => false, _ => true }; + let awake = match config.mode { Mode::Dark(..) | Mode::Off => false, _ => true }; let factories = Factories { vm: EvmFactory::new(config.vm_type.clone(), config.jump_table_size), @@ -243,6 +244,7 @@ impl Client { factories: factories, history: history, rng: Mutex::new(try!(OsRng::new().map_err(::util::UtilError::StdIo))), + on_mode_change: Mutex::new(None), }; Ok(Arc::new(client)) } @@ -260,6 +262,11 @@ impl Client { } } + /// Register an action to be done if a mode change happens. + pub fn on_mode_change(&self, f: F) where F: 'static + FnMut(&Mode) + Send { + *self.on_mode_change.lock() = Some(Box::new(f)); + } + /// Flush the block import queue. pub fn flush_queue(&self) { self.block_queue.flush(); @@ -856,18 +863,37 @@ impl BlockChainClient for Client { } fn keep_alive(&self) { - let mode = self.mode.lock().clone(); - if mode != Mode::Active { + let should_wake = match &*self.mode.lock() { + &Mode::Dark(..) | &Mode::Passive(..) => true, + _ => false, + }; + if should_wake { self.wake_up(); (*self.sleep_state.lock()).last_activity = Some(Instant::now()); } } - fn mode(&self) -> IpcMode { self.mode.lock().clone().into() } + fn mode(&self) -> IpcMode { + let r = self.mode.lock().clone().into(); + trace!(target: "mode", "Asked for mode = {:?}. returning {:?}", &*self.mode.lock(), r); + r + } - fn set_mode(&self, mode: IpcMode) { - *self.mode.lock() = mode.clone().into(); - match mode { + fn set_mode(&self, new_mode: IpcMode) { + trace!(target: "mode", "Client::set_mode({:?})", new_mode); + { + let mut mode = self.mode.lock(); + *mode = new_mode.clone().into(); + trace!(target: "mode", "Mode now {:?}", &*mode); + match *self.on_mode_change.lock() { + Some(ref mut f) => { + trace!(target: "mode", "Making callback..."); + f(&*mode) + }, + _ => {} + } + } + match new_mode { IpcMode::Active => self.wake_up(), IpcMode::Off => self.sleep(), _ => {(*self.sleep_state.lock()).last_activity = Some(Instant::now()); } diff --git a/ethcore/src/client/config.rs b/ethcore/src/client/config.rs index c4aeba8a4..045b8ee05 100644 --- a/ethcore/src/client/config.rs +++ b/ethcore/src/client/config.rs @@ -16,6 +16,7 @@ use std::str::FromStr; use std::path::Path; +use std::fmt::{Display, Formatter, Error as FmtError}; pub use std::time::Duration; pub use blockchain::Config as BlockChainConfig; pub use trace::Config as TraceConfig; @@ -86,6 +87,17 @@ impl Default for Mode { } } +impl Display for Mode { + fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> { + match *self { + Mode::Active => write!(f, "active"), + Mode::Passive(..) => write!(f, "passive"), + Mode::Dark(..) => write!(f, "dark"), + Mode::Off => write!(f, "offline"), + } + } +} + /// Client configuration. Includes configs for all sub-systems. #[derive(Debug, PartialEq, Default)] pub struct ClientConfig { diff --git a/ethcore/src/types/mode.rs b/ethcore/src/types/mode.rs index b48a92a89..58f652c6c 100644 --- a/ethcore/src/types/mode.rs +++ b/ethcore/src/types/mode.rs @@ -20,7 +20,7 @@ pub use std::time::Duration; use client::Mode as ClientMode; /// IPC-capable shadow-type for client::config::Mode -#[derive(Clone, Binary)] +#[derive(Clone, Binary, Debug)] pub enum Mode { /// Same as ClientMode::Off. Off, diff --git a/js/src/jsonrpc/interfaces/ethcore.js b/js/src/jsonrpc/interfaces/ethcore.js index ce1761382..310276cc8 100644 --- a/js/src/jsonrpc/interfaces/ethcore.js +++ b/js/src/jsonrpc/interfaces/ethcore.js @@ -166,7 +166,7 @@ export default { }, mode: { - desc: 'Get the mode. Results one of: "active", "passive", "dark", "off".', + desc: 'Get the mode. Results one of: "active", "passive", "dark", "offline".', params: [], returns: { type: String, @@ -330,7 +330,7 @@ export default { params: [ { type: String, - desc: 'The mode to set, one of "active", "passive", "dark", "off"' + desc: 'The mode to set, one of "active", "passive", "dark", "offline"' } ], returns: { diff --git a/js/src/views/Status/data/rpc.json b/js/src/views/Status/data/rpc.json index 97c7e4055..ebb2800e8 100644 --- a/js/src/views/Status/data/rpc.json +++ b/js/src/views/Status/data/rpc.json @@ -926,6 +926,16 @@ ], "outputFormatter": null }, + { + "name": "ethcore_setMode", + "desc": "Changes current operating mode", + "params": [ + "`DATA`- Mode name" + ], + "returns": "`Boolean` - whether the call was successful", + "inputFormatters": null, + "outputFormatter": null + }, { "name": "ethcore_setMinGasPrice", "desc": "Changes minimal gas price for transaction to be accepted to the queue.", @@ -1030,6 +1040,14 @@ "inputFormatters": [], "outputFormatter": null }, + { + "name": "ethcore_mode", + "desc": "Returns current mode", + "params": [], + "returns": "`DATA` - Mode", + "inputFormatters": [], + "outputFormatter": null + }, { "name": "trace_filter", "desc": "Returns traces matching given filter", diff --git a/mac/post-install.sh b/mac/post-install.sh index 2a6b7e538..62e011054 100755 --- a/mac/post-install.sh +++ b/mac/post-install.sh @@ -11,8 +11,6 @@ cat > $HOME/Library/LaunchAgents/io.parity.ethereum.plist <ProgramArguments /usr/local/libexec/parity - --mode - passive --warp KeepAlive @@ -26,9 +24,11 @@ cat > $HOME/Library/LaunchAgents/io.parity.ethereum.plist < EOF -mkdir -p $HOME/.parity -chown $USER $HOME/.parity $HOME/Library/LaunchAgents $HOME/Library/LaunchAgents/io.parity.ethereum.plist +mkdir -p $HOME/.parity/906a34e69aec8c0d +echo -n '{"fat_db":false,"mode":"passive","pruning":"fast","tracing":false}' > $HOME/.parity/906a34e69aec8c0d/user_defaults + +chown -R $USER $HOME/.parity $HOME/Library/LaunchAgents $HOME/Library/LaunchAgents/io.parity.ethereum.plist su $USER -c "launchctl load $HOME/Library/LaunchAgents/io.parity.ethereum.plist" sleep 1 diff --git a/parity/blockchain.rs b/parity/blockchain.rs index 8e84c488c..9575b293a 100644 --- a/parity/blockchain.rs +++ b/parity/blockchain.rs @@ -78,7 +78,6 @@ pub struct ImportBlockchain { pub pruning_history: u64, pub compaction: DatabaseCompactionProfile, pub wal: bool, - pub mode: Mode, pub tracing: Switch, pub fat_db: Switch, pub vm_type: VMType, @@ -97,7 +96,6 @@ pub struct ExportBlockchain { pub pruning_history: u64, pub compaction: DatabaseCompactionProfile, pub wal: bool, - pub mode: Mode, pub fat_db: Switch, pub tracing: Switch, pub from_block: BlockID, @@ -155,7 +153,7 @@ fn execute_import(cmd: ImportBlockchain) -> Result { try!(execute_upgrades(&db_dirs, algorithm, cmd.compaction.compaction_profile(db_dirs.fork_path().as_path()))); // prepare client config - let client_config = to_client_config(&cmd.cache_config, cmd.mode, tracing, fat_db, cmd.compaction, cmd.wal, cmd.vm_type, "".into(), algorithm, cmd.pruning_history, cmd.check_seal); + let client_config = to_client_config(&cmd.cache_config, Mode::Active, tracing, fat_db, cmd.compaction, cmd.wal, cmd.vm_type, "".into(), algorithm, cmd.pruning_history, cmd.check_seal); // build client let service = try!(ClientService::start( @@ -303,7 +301,7 @@ fn execute_export(cmd: ExportBlockchain) -> Result { try!(execute_upgrades(&db_dirs, algorithm, cmd.compaction.compaction_profile(db_dirs.fork_path().as_path()))); // prepare client config - let client_config = to_client_config(&cmd.cache_config, cmd.mode, tracing, fat_db, cmd.compaction, cmd.wal, VMType::default(), "".into(), algorithm, cmd.pruning_history, cmd.check_seal); + let client_config = to_client_config(&cmd.cache_config, Mode::Active, tracing, fat_db, cmd.compaction, cmd.wal, VMType::default(), "".into(), algorithm, cmd.pruning_history, cmd.check_seal); let service = try!(ClientService::start( client_config, diff --git a/parity/cli/config.full.toml b/parity/cli/config.full.toml index 088c9d4b7..520adef4b 100644 --- a/parity/cli/config.full.toml +++ b/parity/cli/config.full.toml @@ -1,5 +1,5 @@ [parity] -mode = "active" +mode = "last" mode_timeout = 300 mode_alarm = 3600 chain = "homestead" diff --git a/parity/cli/mod.rs b/parity/cli/mod.rs index c0e7241b0..ad9e8dd8b 100644 --- a/parity/cli/mod.rs +++ b/parity/cli/mod.rs @@ -74,7 +74,7 @@ usage! { } { // -- Operating Options - flag_mode: String = "active", or |c: &Config| otry!(c.parity).mode.clone(), + flag_mode: String = "last", or |c: &Config| otry!(c.parity).mode.clone(), flag_mode_timeout: u64 = 300u64, or |c: &Config| otry!(c.parity).mode_timeout.clone(), flag_mode_alarm: u64 = 3600u64, or |c: &Config| otry!(c.parity).mode_alarm.clone(), flag_chain: String = "homestead", or |c: &Config| otry!(c.parity).chain.clone(), @@ -104,8 +104,6 @@ usage! { flag_signer_no_validation: bool = false, or |_| None, // -- Networking Options - flag_no_network: bool = false, - or |c: &Config| otry!(c.network).disable.clone(), flag_warp: bool = false, or |c: &Config| otry!(c.network).warp.clone(), flag_port: u16 = 30303u16, @@ -500,7 +498,7 @@ mod tests { arg_path: vec![], // -- Operating Options - flag_mode: "active".into(), + flag_mode: "last".into(), flag_mode_timeout: 300u64, flag_mode_alarm: 3600u64, flag_chain: "xyz".into(), @@ -521,7 +519,6 @@ mod tests { flag_signer_no_validation: false, // -- Networking Options - flag_no_network: false, flag_warp: true, flag_port: 30303u16, flag_min_peers: 25u16, diff --git a/parity/cli/usage.txt b/parity/cli/usage.txt index bf7e82561..b8734f7c2 100644 --- a/parity/cli/usage.txt +++ b/parity/cli/usage.txt @@ -18,11 +18,12 @@ Usage: Operating Options: --mode MODE Set the operating mode. MODE can be one of: + last - Uses the last-used mode, active if none. active - Parity continuously syncs the chain. passive - Parity syncs initially, then sleeps and wakes regularly to resync. - dark - Parity syncs only when an external interface - is active. (default: {flag_mode}). + dark - Parity syncs only when the RPC is active. + offline - Parity doesn't sync. (default: {flag_mode}). --mode-timeout SECS Specify the number of seconds before inactivity timeout occurs when mode is dark or passive (default: {flag_mode_timeout}). @@ -66,7 +67,6 @@ Account Options: development. (default: {flag_signer_no_validation}) Networking Options: - --no-network Disable p2p networking. (default: {flag_no_network}) --warp Enable syncing from the snapshot over the network. (default: {flag_warp}) --port PORT Override the port on which the node should listen (default: {flag_port}). diff --git a/parity/configuration.rs b/parity/configuration.rs index 3484f8a4b..b71404339 100644 --- a/parity/configuration.rs +++ b/parity/configuration.rs @@ -23,7 +23,7 @@ use cli::{Args, ArgsError}; 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::client::VMType; use ethcore::miner::{MinerOptions, Banning}; use rpc::{IpcConfiguration, HttpConfiguration}; @@ -80,7 +80,7 @@ impl Configuration { let pruning = try!(self.args.flag_pruning.parse()); let pruning_history = self.args.flag_pruning_history; let vm_type = try!(self.vm_type()); - let mode = try!(to_mode(&self.args.flag_mode, self.args.flag_mode_timeout, self.args.flag_mode_alarm)); + let mode = match self.args.flag_mode.as_ref() { "last" => None, mode => Some(try!(to_mode(&mode, self.args.flag_mode_timeout, self.args.flag_mode_alarm))), }; let miner_options = try!(self.miner_options()); let logger_config = self.logger_config(); let http_conf = try!(self.http_config()); @@ -93,7 +93,6 @@ impl Configuration { let fat_db = try!(self.args.flag_fat_db.parse()); let compaction = try!(self.args.flag_db_compaction.parse()); let wal = !self.args.flag_fast_and_loose; - let enable_network = self.enable_network(&mode); let warp_sync = self.args.flag_warp; let geth_compatibility = self.args.flag_geth; let signer_port = self.signer_port(); @@ -156,7 +155,6 @@ impl Configuration { pruning_history: pruning_history, compaction: compaction, wal: wal, - mode: mode, tracing: tracing, fat_db: fat_db, vm_type: vm_type, @@ -175,7 +173,6 @@ impl Configuration { pruning_history: pruning_history, compaction: compaction, wal: wal, - mode: mode, tracing: tracing, fat_db: fat_db, from_block: try!(to_block_id(&self.args.flag_from)), @@ -190,7 +187,6 @@ impl Configuration { spec: spec, pruning: pruning, pruning_history: pruning_history, - mode: mode, tracing: tracing, fat_db: fat_db, compaction: compaction, @@ -207,7 +203,6 @@ impl Configuration { spec: spec, pruning: pruning, pruning_history: pruning_history, - mode: mode, tracing: tracing, fat_db: fat_db, compaction: compaction, @@ -246,7 +241,6 @@ impl Configuration { compaction: compaction, wal: wal, vm_type: vm_type, - enable_network: enable_network, warp_sync: warp_sync, geth_compatibility: geth_compatibility, signer_port: signer_port, @@ -268,13 +262,6 @@ impl Configuration { }) } - fn enable_network(&self, mode: &Mode) -> bool { - match *mode { - Mode::Dark(_) => false, - _ => !self.args.flag_no_network, - } - } - fn vm_type(&self) -> Result { if self.args.flag_jitvm { VMType::jit().ok_or("Parity is built without the JIT EVM.".into()) @@ -767,7 +754,6 @@ mod tests { pruning_history: 64, compaction: Default::default(), wal: true, - mode: Default::default(), tracing: Default::default(), fat_db: Default::default(), vm_type: VMType::Interpreter, @@ -790,7 +776,6 @@ mod tests { format: Default::default(), compaction: Default::default(), wal: true, - mode: Default::default(), tracing: Default::default(), fat_db: Default::default(), from_block: BlockID::Number(1), @@ -813,7 +798,6 @@ mod tests { format: Some(DataFormat::Hex), compaction: Default::default(), wal: true, - mode: Default::default(), tracing: Default::default(), fat_db: Default::default(), from_block: BlockID::Number(1), @@ -858,7 +842,6 @@ mod tests { compaction: Default::default(), wal: true, vm_type: Default::default(), - enable_network: true, geth_compatibility: false, signer_port: Some(8180), net_settings: Default::default(), diff --git a/parity/helpers.rs b/parity/helpers.rs index 5d6859f5b..761a148e4 100644 --- a/parity/helpers.rs +++ b/parity/helpers.rs @@ -46,7 +46,7 @@ fn to_seconds(s: &str) -> Result { "hourly" | "1hour" | "1 hour" | "hour" => Ok(60 * 60), "daily" | "1day" | "1 day" | "day" => Ok(24 * 60 * 60), x if x.ends_with("seconds") => x[0..x.len() - 7].parse().map_err(bad), - x if x.ends_with("minutes") => x[0..x.len() -7].parse::().map_err(bad).map(|x| x * 60), + x if x.ends_with("minutes") => x[0..x.len() - 7].parse::().map_err(bad).map(|x| x * 60), x if x.ends_with("hours") => x[0..x.len() - 5].parse::().map_err(bad).map(|x| x * 60 * 60), x if x.ends_with("days") => x[0..x.len() - 4].parse::().map_err(bad).map(|x| x * 24 * 60 * 60), x => x.parse().map_err(bad), @@ -58,7 +58,8 @@ pub fn to_mode(s: &str, timeout: u64, alarm: u64) -> Result { "active" => Ok(Mode::Active), "passive" => Ok(Mode::Passive(Duration::from_secs(timeout), Duration::from_secs(alarm))), "dark" => Ok(Mode::Dark(Duration::from_secs(timeout))), - _ => Err(format!("{}: Invalid address for --mode. Must be one of active, passive or dark.", s)), + "offline" => Ok(Mode::Off), + _ => Err(format!("{}: Invalid value for --mode. Must be one of active, passive, dark or offline.", s)), } } diff --git a/parity/params.rs b/parity/params.rs index faba029b2..5a81fba7f 100644 --- a/parity/params.rs +++ b/parity/params.rs @@ -20,6 +20,7 @@ use util::{Address, U256, version_data}; use util::journaldb::Algorithm; use ethcore::spec::Spec; use ethcore::ethereum; +use ethcore::client::Mode; use ethcore::miner::{GasPricer, GasPriceCalibratorOptions}; use user_defaults::UserDefaults; @@ -264,6 +265,10 @@ pub fn fatdb_switch_to_bool(switch: Switch, user_defaults: &UserDefaults, algori result } +pub fn mode_switch_to_bool(switch: Option, user_defaults: &UserDefaults) -> Result { + Ok(switch.unwrap_or(user_defaults.mode.clone())) +} + #[cfg(test)] mod tests { use util::journaldb::Algorithm; diff --git a/parity/run.rs b/parity/run.rs index 9c3b00737..d94eafc55 100644 --- a/parity/run.rs +++ b/parity/run.rs @@ -37,7 +37,7 @@ use dapps::WebappServer; use io_handler::ClientIoHandler; use params::{ SpecType, Pruning, AccountsConfig, GasPricerConfig, MinerExtras, Switch, - tracing_switch_to_bool, fatdb_switch_to_bool, + tracing_switch_to_bool, fatdb_switch_to_bool, mode_switch_to_bool }; use helpers::{to_client_config, execute_upgrades, passwords_from_files}; use dir::Directories; @@ -75,13 +75,12 @@ pub struct RunCmd { pub acc_conf: AccountsConfig, pub gas_pricer: GasPricerConfig, pub miner_extras: MinerExtras, - pub mode: Mode, + pub mode: Option, pub tracing: Switch, pub fat_db: Switch, pub compaction: DatabaseCompactionProfile, pub wal: bool, pub vm_type: VMType, - pub enable_network: bool, pub geth_compatibility: bool, pub signer_port: Option, pub net_settings: NetworkSettings, @@ -137,6 +136,11 @@ pub fn execute(cmd: RunCmd, logger: Arc) -> Result<(), String> { // check if fatdb is on let fat_db = try!(fatdb_switch_to_bool(cmd.fat_db, &user_defaults, algorithm)); + // get the mode + let mode = try!(mode_switch_to_bool(cmd.mode, &user_defaults)); + trace!(target: "mode", "mode is {:?}", mode); + let network_enabled = match &mode { &Mode::Dark(_) | &Mode::Off => false, _ => true, }; + // prepare client and snapshot paths. let client_path = db_dirs.client_path(algorithm); let snapshot_path = db_dirs.snapshot_path(); @@ -162,6 +166,7 @@ pub fn execute(cmd: RunCmd, logger: Arc) -> Result<(), String> { false => "".to_owned(), } ); + info!("Operating mode: {}", Colour::White.bold().paint(format!("{}", mode))); // display warning about using experimental journaldb alorithm if !algorithm.is_stable() { @@ -196,7 +201,7 @@ pub fn execute(cmd: RunCmd, logger: Arc) -> Result<(), String> { // create client config let client_config = to_client_config( &cmd.cache_config, - cmd.mode, + mode, tracing, fat_db, cmd.compaction, @@ -248,7 +253,7 @@ pub fn execute(cmd: RunCmd, logger: Arc) -> Result<(), String> { service.add_notify(chain_notify.clone()); // start network - if cmd.enable_network { + if network_enabled { chain_notify.start(); } @@ -321,6 +326,19 @@ pub fn execute(cmd: RunCmd, logger: Arc) -> Result<(), String> { }); service.register_io_handler(io_handler.clone()).expect("Error registering IO handler"); + // save user defaults + user_defaults.pruning = algorithm; + user_defaults.tracing = tracing; + try!(user_defaults.save(&user_defaults_path)); + + let on_mode_change = move |mode: &Mode| { + user_defaults.mode = mode.clone(); + let _ = user_defaults.save(&user_defaults_path); // discard failures - there's nothing we can do + }; + + // tell client how to save the default mode if it gets changed. + client.on_mode_change(on_mode_change); + // the watcher must be kept alive. let _watcher = match cmd.no_periodic_snapshot { true => None, @@ -347,11 +365,6 @@ pub fn execute(cmd: RunCmd, logger: Arc) -> Result<(), String> { url::open(&format!("http://{}:{}/", cmd.dapps_conf.interface, cmd.dapps_conf.port)); } - // save user defaults - user_defaults.pruning = algorithm; - user_defaults.tracing = tracing; - try!(user_defaults.save(&user_defaults_path)); - // Handle exit wait_for_exit(panic_handler, http_server, ipc_server, dapps_server, signer_server); diff --git a/parity/snapshot.rs b/parity/snapshot.rs index 85e1f90eb..09a180a67 100644 --- a/parity/snapshot.rs +++ b/parity/snapshot.rs @@ -54,7 +54,6 @@ pub struct SnapshotCommand { pub spec: SpecType, pub pruning: Pruning, pub pruning_history: u64, - pub mode: Mode, pub tracing: Switch, pub fat_db: Switch, pub compaction: DatabaseCompactionProfile, @@ -158,7 +157,7 @@ impl SnapshotCommand { try!(execute_upgrades(&db_dirs, algorithm, self.compaction.compaction_profile(db_dirs.fork_path().as_path()))); // prepare client config - let client_config = to_client_config(&self.cache_config, self.mode, tracing, fat_db, self.compaction, self.wal, VMType::default(), "".into(), algorithm, self.pruning_history, true); + let client_config = to_client_config(&self.cache_config, Mode::Active, tracing, fat_db, self.compaction, self.wal, VMType::default(), "".into(), algorithm, self.pruning_history, true); let service = try!(ClientService::start( client_config, diff --git a/parity/user_defaults.rs b/parity/user_defaults.rs index b7fc3d929..a1078b634 100644 --- a/parity/user_defaults.rs +++ b/parity/user_defaults.rs @@ -18,6 +18,7 @@ use std::fs::File; use std::io::Write; use std::path::Path; use std::collections::BTreeMap; +use std::time::Duration; use serde::{Serialize, Serializer, Error, Deserialize, Deserializer}; use serde::de::{Visitor, MapVisitor}; use serde::de::impls::BTreeMapVisitor; @@ -25,12 +26,14 @@ use serde_json::Value; use serde_json::de::from_reader; use serde_json::ser::to_string; use util::journaldb::Algorithm; +use ethcore::client::Mode; pub struct UserDefaults { pub is_first_launch: bool, pub pruning: Algorithm, pub tracing: bool, pub fat_db: bool, + pub mode: Mode, } impl Serialize for UserDefaults { @@ -40,6 +43,21 @@ impl Serialize for UserDefaults { map.insert("pruning".into(), Value::String(self.pruning.as_str().into())); map.insert("tracing".into(), Value::Bool(self.tracing)); map.insert("fat_db".into(), Value::Bool(self.fat_db)); + let mode_str = match self.mode { + Mode::Off => "offline", + Mode::Dark(timeout) => { + map.insert("mode.timeout".into(), Value::U64(timeout.as_secs())); + "dark" + }, + Mode::Passive(timeout, alarm) => { + map.insert("mode.timeout".into(), Value::U64(timeout.as_secs())); + map.insert("mode.alarm".into(), Value::U64(alarm.as_secs())); + "passive" + }, + Mode::Active => "active", + }; + map.insert("mode".into(), Value::String(mode_str.into())); + map.serialize(serializer) } } @@ -67,11 +85,28 @@ impl Visitor for UserDefaultsVisitor { let fat_db: Value = map.remove("fat_db".into()).unwrap_or_else(|| Value::Bool(false)); let fat_db = try!(fat_db.as_bool().ok_or_else(|| Error::custom("invalid fat_db value"))); + let mode: Value = map.remove("mode".into()).unwrap_or_else(|| Value::String("active".to_owned())); + let mode = match try!(mode.as_str().ok_or_else(|| Error::custom("invalid mode value"))) { + "offline" => Mode::Off, + "dark" => { + let timeout = try!(map.remove("mode.timeout".into()).and_then(|v| v.as_u64()).ok_or_else(|| Error::custom("invalid/missing mode.timeout value"))); + Mode::Dark(Duration::from_secs(timeout)) + }, + "passive" => { + let timeout = try!(map.remove("mode.timeout".into()).and_then(|v| v.as_u64()).ok_or_else(|| Error::custom("invalid/missing mode.timeout value"))); + let alarm = try!(map.remove("mode.alarm".into()).and_then(|v| v.as_u64()).ok_or_else(|| Error::custom("invalid/missing mode.alarm value"))); + Mode::Passive(Duration::from_secs(timeout), Duration::from_secs(alarm)) + }, + "active" => Mode::Active, + _ => { return Err(Error::custom("invalid mode value")); }, + }; + let user_defaults = UserDefaults { is_first_launch: false, pruning: pruning, tracing: tracing, fat_db: fat_db, + mode: mode, }; Ok(user_defaults) @@ -85,6 +120,7 @@ impl Default for UserDefaults { pruning: Algorithm::default(), tracing: false, fat_db: false, + mode: Mode::Active, } } } @@ -97,7 +133,7 @@ impl UserDefaults { } } - pub fn save

(self, path: P) -> Result<(), String> where P: AsRef { + pub fn save

(&self, path: P) -> Result<(), String> where P: AsRef { let mut file: File = try!(File::create(path).map_err(|_| "Cannot create user defaults file".to_owned())); file.write_all(to_string(&self).unwrap().as_bytes()).map_err(|_| "Failed to save user defaults".to_owned()) } diff --git a/rpc/src/v1/impls/ethcore.rs b/rpc/src/v1/impls/ethcore.rs index 1130b8fb8..84d159f80 100644 --- a/rpc/src/v1/impls/ethcore.rs +++ b/rpc/src/v1/impls/ethcore.rs @@ -351,7 +351,7 @@ impl Ethcore for EthcoreClient where fn mode(&self) -> Result { Ok(match take_weak!(self.client).mode() { - Mode::Off => "off", + Mode::Off => "offline", Mode::Dark(..) => "dark", Mode::Passive(..) => "passive", Mode::Active => "active", diff --git a/rpc/src/v1/impls/ethcore_set.rs b/rpc/src/v1/impls/ethcore_set.rs index 27464cff3..8889ffa44 100644 --- a/rpc/src/v1/impls/ethcore_set.rs +++ b/rpc/src/v1/impls/ethcore_set.rs @@ -151,7 +151,7 @@ impl EthcoreSet for EthcoreSetClient where fn set_mode(&self, mode: String) -> Result { take_weak!(self.client).set_mode(match mode.as_str() { - "off" => Mode::Off, + "offline" => Mode::Off, "dark" => Mode::Dark(300), "passive" => Mode::Passive(300, 3600), "active" => Mode::Active, diff --git a/rpc/src/v1/traits/ethcore.rs b/rpc/src/v1/traits/ethcore.rs index 8e9f58fb3..b7ef2d151 100644 --- a/rpc/src/v1/traits/ethcore.rs +++ b/rpc/src/v1/traits/ethcore.rs @@ -130,7 +130,7 @@ build_rpc_trait! { #[rpc(name = "ethcore_nextNonce")] fn next_nonce(&self, H160) -> Result; - /// Get the mode. Results one of: "active", "passive", "dark", "off". + /// Get the mode. Results one of: "active", "passive", "dark", "offline". #[rpc(name = "ethcore_mode")] fn mode(&self) -> Result; diff --git a/rpc/src/v1/traits/ethcore_set.rs b/rpc/src/v1/traits/ethcore_set.rs index 9c0d3d5d4..ad31a6e64 100644 --- a/rpc/src/v1/traits/ethcore_set.rs +++ b/rpc/src/v1/traits/ethcore_set.rs @@ -76,11 +76,11 @@ build_rpc_trait! { /// Stop the network. /// - /// Deprecated. Use `set_mode("off")` instead. + /// Deprecated. Use `set_mode("offline")` instead. #[rpc(name = "ethcore_stopNetwork")] fn stop_network(&self) -> Result; - /// Set the mode. Argument must be one of: "active", "passive", "dark", "off". + /// Set the mode. Argument must be one of: "active", "passive", "dark", "offline". #[rpc(name = "ethcore_setMode")] fn set_mode(&self, String) -> Result; } From 56fd88d1e8af40022fbb3f9974a2283546063297 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Sat, 5 Nov 2016 10:39:15 +0100 Subject: [PATCH 22/62] ethash unsafety cleanup (#3210) --- ethcore/src/ethereum/ethash.rs | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/ethcore/src/ethereum/ethash.rs b/ethcore/src/ethereum/ethash.rs index 8b9688569..6436e3531 100644 --- a/ethcore/src/ethereum/ethash.rs +++ b/ethcore/src/ethereum/ethash.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -use ethash::{quick_get_difficulty, slow_get_seedhash, EthashManager, H256 as EH256}; +use ethash::{quick_get_difficulty, slow_get_seedhash, EthashManager}; use util::*; use block::*; use builtin::Builtin; @@ -243,10 +243,10 @@ impl Engine for Ethash { return Err(From::from(BlockError::DifficultyOutOfBounds(OutOfBounds { min: Some(min_difficulty), max: None, found: header.difficulty().clone() }))) } - let difficulty = Ethash::boundary_to_difficulty(&Ethash::from_ethash(quick_get_difficulty( - &Ethash::to_ethash(header.bare_hash()), + let difficulty = Ethash::boundary_to_difficulty(&H256(quick_get_difficulty( + &header.bare_hash().0, header.nonce().low_u64(), - &Ethash::to_ethash(header.mix_hash()) + &header.mix_hash().0 ))); if &difficulty < header.difficulty() { return Err(From::from(BlockError::InvalidProofOfWork(OutOfBounds { min: Some(header.difficulty().clone()), max: None, found: difficulty }))); @@ -271,10 +271,10 @@ impl Engine for Ethash { Mismatch { expected: self.seal_fields(), found: header.seal().len() } ))); } - let result = self.pow.compute_light(header.number() as u64, &Ethash::to_ethash(header.bare_hash()), header.nonce().low_u64()); - let mix = Ethash::from_ethash(result.mix_hash); - let difficulty = Ethash::boundary_to_difficulty(&Ethash::from_ethash(result.value)); - trace!(target: "miner", "num: {}, seed: {}, h: {}, non: {}, mix: {}, res: {}" , header.number() as u64, Ethash::from_ethash(slow_get_seedhash(header.number() as u64)), header.bare_hash(), header.nonce().low_u64(), Ethash::from_ethash(result.mix_hash), Ethash::from_ethash(result.value)); + let result = self.pow.compute_light(header.number() as u64, &header.bare_hash().0, header.nonce().low_u64()); + let mix = H256(result.mix_hash); + let difficulty = Ethash::boundary_to_difficulty(&H256(result.value)); + trace!(target: "miner", "num: {}, seed: {}, h: {}, non: {}, mix: {}, res: {}" , header.number() as u64, H256(slow_get_seedhash(header.number() as u64)), header.bare_hash(), header.nonce().low_u64(), H256(result.mix_hash), H256(result.value)); if mix != header.mix_hash() { return Err(From::from(BlockError::MismatchedH256SealElement(Mismatch { expected: mix, found: header.mix_hash() }))); } @@ -323,7 +323,7 @@ impl Engine for Ethash { } } -#[cfg_attr(feature="dev", allow(wrong_self_convention))] // to_ethash should take self +#[cfg_attr(feature="dev", allow(wrong_self_convention))] impl Ethash { fn calculate_difficulty(&self, header: &Header, parent: &Header) -> U256 { const EXP_DIFF_PERIOD: u64 = 100000; @@ -396,14 +396,6 @@ impl Ethash { (((U256::one() << 255) / *difficulty) << 1).into() } } - - fn to_ethash(hash: H256) -> EH256 { - unsafe { mem::transmute(hash) } - } - - fn from_ethash(hash: EH256) -> H256 { - unsafe { mem::transmute(hash) } - } } impl Header { From 7b043047f0ed2152dd1e8403f38c8d526eecaf61 Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Sat, 5 Nov 2016 09:55:44 +0000 Subject: [PATCH 23/62] [ci skip] js-precompiled 20161105-095429 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 986b78b86..acf5603d3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1249,7 +1249,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#e74c88d14eb28e829288cc2f702b110e364332d6" +source = "git+https://github.com/ethcore/js-precompiled.git#d9de93f17d0fff7c12cd63dde914ed4973fe6701" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/js/package.json b/js/package.json index 5edd47f26..c18d544b7 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.9", + "version": "0.2.10", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", From 458ee4cbadd27777c07eccd43b4bdf18e9b8ff9c Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Sat, 5 Nov 2016 12:08:14 +0100 Subject: [PATCH 24/62] Parity configuration settings, i.e. mode (#3212) * Add initial page * Add parity icon * opacity for parity icon * Mode selector * Actually set mode when value changes --- js/src/index.js | 3 +- js/src/views/Settings/Parity/index.js | 17 ++++ js/src/views/Settings/Parity/parity.js | 116 +++++++++++++++++++++++++ js/src/views/Settings/index.js | 2 + js/src/views/Settings/settings.css | 10 +++ js/src/views/Settings/settings.js | 59 +++++-------- js/src/views/index.js | 3 +- 7 files changed, 171 insertions(+), 39 deletions(-) create mode 100644 js/src/views/Settings/Parity/index.js create mode 100644 js/src/views/Settings/Parity/parity.js diff --git a/js/src/index.js b/js/src/index.js index a4b5d5c59..a8cb8ac96 100644 --- a/js/src/index.js +++ b/js/src/index.js @@ -31,7 +31,7 @@ import ContractInstances from './contracts'; import { initStore } from './redux'; import { ContextProvider, muiTheme } from './ui'; -import { Accounts, Account, Addresses, Address, Application, Contract, Contracts, Dapp, Dapps, Settings, SettingsBackground, SettingsProxy, SettingsViews, Signer, Status } from './views'; +import { Accounts, Account, Addresses, Address, Application, Contract, Contracts, Dapp, Dapps, Settings, SettingsBackground, SettingsParity, SettingsProxy, SettingsViews, Signer, Status } from './views'; import { setApi } from './redux/providers/apiActions'; @@ -81,6 +81,7 @@ ReactDOM.render( + diff --git a/js/src/views/Settings/Parity/index.js b/js/src/views/Settings/Parity/index.js new file mode 100644 index 000000000..38f08f725 --- /dev/null +++ b/js/src/views/Settings/Parity/index.js @@ -0,0 +1,17 @@ +// 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 . + +export default from './parity'; diff --git a/js/src/views/Settings/Parity/parity.js b/js/src/views/Settings/Parity/parity.js new file mode 100644 index 000000000..6a3ba631f --- /dev/null +++ b/js/src/views/Settings/Parity/parity.js @@ -0,0 +1,116 @@ +// 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 . + +import React, { Component, PropTypes } from 'react'; +import { MenuItem } from 'material-ui'; + +import { Select, Container, ContainerTitle } from '../../../ui'; + +import layout from '../layout.css'; + +const MODES = { + 'active': 'Parity continuously syncs the chain', + 'passive': 'Parity syncs initially, then sleeps and wakes regularly to resync', + 'dark': 'Parity syncs only when the RPC is active', + 'offline': 'Parity doesn\'t sync' +}; + +export default class Parity extends Component { + static contextTypes = { + api: PropTypes.object.isRequired + } + + state = { + mode: 'active' + } + + componentWillMount () { + this.loadMode(); + } + + render () { + return ( + + +

+
+
Control the Parity node settings and mode of operation via this interface.
+
+
+ { this.renderModes() } +
+
+
+ ); + } + + renderModes () { + const modes = Object + .keys(MODES) + .map((mode) => { + const description = MODES[mode]; + + return ( + + { description } + + ); + }); + + const { mode } = this.state; + + return ( + + ); + } + + onChangeMode = (event, index, mode) => { + const { api } = this.context; + + api.ethcore + .setMode(mode) + .then((result) => { + if (result) { + this.setState({ mode }); + } + }) + .catch((error) => { + console.warn('onChangeMode', error); + }); + } + + loadMode () { + const { api } = this.context; + + api.ethcore + .mode() + .then((mode) => { + this.setState({ mode }); + }) + .catch((error) => { + console.warn('loadMode', error); + }); + } +} diff --git a/js/src/views/Settings/index.js b/js/src/views/Settings/index.js index 6971db224..4c5414f71 100644 --- a/js/src/views/Settings/index.js +++ b/js/src/views/Settings/index.js @@ -17,6 +17,7 @@ import settingsReducer from './reducers'; import { toggleView, updateBackground } from './actions'; import SettingsBackground from './Background'; +import SettingsParity from './Parity'; import SettingsProxy from './Proxy'; import SettingsViews, { defaultViews } from './Views'; @@ -24,6 +25,7 @@ export default from './settings'; export { SettingsBackground, + SettingsParity, SettingsProxy, SettingsViews, defaultViews, diff --git a/js/src/views/Settings/settings.css b/js/src/views/Settings/settings.css index ebd7b4ec0..91138db68 100644 --- a/js/src/views/Settings/settings.css +++ b/js/src/views/Settings/settings.css @@ -61,3 +61,13 @@ vertical-align: top; display: inline-block; } + +.imageIcon { + height: 20px; + margin: 2px 0.5em 2px 0 !important; + opacity: 0.5; +} + +.tabactive .imageIcon { + opacity: 1; +} diff --git a/js/src/views/Settings/settings.js b/js/src/views/Settings/settings.js index 2b9579993..4b38b1ccf 100644 --- a/js/src/views/Settings/settings.js +++ b/js/src/views/Settings/settings.js @@ -14,8 +14,6 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -// 0xecf69634885f27a8f78161e530f15a8d3b57d39e755c222c92cf297b6e25aaaa - import React, { Component, PropTypes } from 'react'; import { Tab, Tabs } from 'material-ui'; import ActionSettingsEthernet from 'material-ui/svg-icons/action/settings-ethernet'; @@ -23,6 +21,7 @@ import ImageBlurOn from 'material-ui/svg-icons/image/blur-on'; import ImageRemoveRedEye from 'material-ui/svg-icons/image/remove-red-eye'; import { Actionbar, Page } from '../../ui'; +import imagesEthcoreBlock from '../../../assets/images/parity-logo-white-no-text.svg'; import styles from './settings.css'; @@ -37,11 +36,23 @@ export default class Settings extends Component { render () { const { children } = this.props; + const hash = (window.location.hash || '').split('?')[0].split('/')[2]; + const isProxied = window.location.hostname.indexOf('.parity') !== -1; + let proxy = null; + + if (!isProxied) { + proxy = this.renderTab(hash, 'proxy', ); + } return (
- { this.renderTabs() } + + { this.renderTab(hash, 'views', ) } + { this.renderTab(hash, 'background', ) } + { proxy } + { this.renderTab(hash, 'parity', ) } + { children } @@ -50,41 +61,15 @@ export default class Settings extends Component { ); } - renderTabs () { - const hash = (window.location.hash || '').split('?')[0].split('/')[2]; - const isProxied = window.location.hostname.indexOf('.parity') !== -1; - let proxy = null; - - if (!isProxied) { - proxy = ( - } - label={
proxy
} - onActive={ this.onActivate('proxy') } /> - ); - } - + renderTab (hash, section, icon) { return ( - - } - label={
views
} - onActive={ this.onActivate('views') } /> - } - label={
background
} - onActive={ this.onActivate('background') } /> - { proxy } -
+ { section }
} + onActive={ this.onActivate(section) } /> ); } diff --git a/js/src/views/index.js b/js/src/views/index.js index c40219c49..b102d389a 100644 --- a/js/src/views/index.js +++ b/js/src/views/index.js @@ -24,7 +24,7 @@ import Contracts from './Contracts'; import Dapp from './Dapp'; import Dapps from './Dapps'; import ParityBar from './ParityBar'; -import Settings, { SettingsBackground, SettingsProxy, SettingsViews } from './Settings'; +import Settings, { SettingsBackground, SettingsParity, SettingsProxy, SettingsViews } from './Settings'; import Signer from './Signer'; import Status from './Status'; @@ -41,6 +41,7 @@ export { ParityBar, Settings, SettingsBackground, + SettingsParity, SettingsProxy, SettingsViews, Signer, From 2cb41f96f52e00c1b606c6fcd42afaddfb07e016 Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Sat, 5 Nov 2016 11:26:40 +0000 Subject: [PATCH 25/62] [ci skip] js-precompiled 20161105-112525 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index acf5603d3..4f90ebd5d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1249,7 +1249,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#d9de93f17d0fff7c12cd63dde914ed4973fe6701" +source = "git+https://github.com/ethcore/js-precompiled.git#47781945c617e631094c0b4175e2b0ea597b09f3" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/js/package.json b/js/package.json index c18d544b7..689aaf822 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.10", + "version": "0.2.11", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", From 77136b8f84daa676eedf78fe1baba982019f1f32 Mon Sep 17 00:00:00 2001 From: Arkadiy Paronyan Date: Sat, 5 Nov 2016 14:38:04 +0100 Subject: [PATCH 26/62] Set passive mode for first run only (#3214) --- nsis/installer.nsi | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/nsis/installer.nsi b/nsis/installer.nsi index 5e24c342b..b6ecfe27c 100644 --- a/nsis/installer.nsi +++ b/nsis/installer.nsi @@ -11,7 +11,8 @@ !define VERSIONMAJOR 1 !define VERSIONMINOR 5 !define VERSIONBUILD 0 -!define ARGS "--warp --mode=passive" +!define ARGS "--warp" +!define FIRST_START_ARGS "--warp --mode=passive" !addplugindir .\ @@ -133,7 +134,7 @@ section "install" WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANYNAME} ${APPNAME}" "EstimatedSize" ${INSTALLSIZE} WriteRegStr HKEY_LOCAL_MACHINE "Software\Microsoft\Windows\CurrentVersion\Run" ${APPNAME} "$INSTDIR\ptray.exe ${ARGS}" - ExecShell "" "$INSTDIR\ptray.exe" "${ARGS}" + ExecShell "" "$INSTDIR\ptray.exe" "${FIRST_START_ARGS}" sectionEnd # Uninstaller From f2faf3609b934a6c21170146466d7a7a185a51f3 Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Sat, 5 Nov 2016 14:49:31 +0000 Subject: [PATCH 27/62] [ci skip] js-precompiled 20161105-144817 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4f90ebd5d..1d3675140 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1249,7 +1249,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#47781945c617e631094c0b4175e2b0ea597b09f3" +source = "git+https://github.com/ethcore/js-precompiled.git#1be902da9b630fcbf81b089a677df0527b3adb10" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/js/package.json b/js/package.json index 689aaf822..092713fe1 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.11", + "version": "0.2.12", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", From cb6003f40dfa766dd54b109a12be030e917344b8 Mon Sep 17 00:00:00 2001 From: "Denis S. Soldatov aka General-Beck" Date: Sat, 5 Nov 2016 22:39:51 +0700 Subject: [PATCH 28/62] Update gitlab-ci $(git --no-pager diff --name-only HEAD HEAD@{1} | grep \.js | wc -l) --- .gitlab-ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9921af75e..da89a16ba 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -8,6 +8,7 @@ variables: RUST_BACKTRACE: "1" RUSTFLAGS: "" CARGOFLAGS: "" + variables[]=value cache: key: "$CI_BUILD_REF_NAME" untracked: true @@ -390,7 +391,7 @@ test-rust-stable: image: ethcore/rust:stable before_script: - git submodule update --init --recursive - - export JS_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF_NAME $(git merge-base $CI_BUILD_REF_NAME master) | grep \.js | wc -l) + - export JS_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF_NAME $CI_BUILD_REF_NAME@{1} | grep \.js | wc -l) - echo $JS_FILES_MODIFIED - if [ -z $JS_FILES_MODIFIED ]; then echo "skip js test"; fi script: From 022a718b6f1ef8bfc0230fec63c09cf91f7fd1f2 Mon Sep 17 00:00:00 2001 From: "Denis S. Soldatov aka General-Beck" Date: Sun, 6 Nov 2016 00:02:44 +0700 Subject: [PATCH 29/62] Update gitlab-ci --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index da89a16ba..733c77dd3 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -391,7 +391,7 @@ test-rust-stable: image: ethcore/rust:stable before_script: - git submodule update --init --recursive - - export JS_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF_NAME $CI_BUILD_REF_NAME@{1} | grep \.js | wc -l) + - export JS_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF $(git merge-base $CI_BUILD_REF master) | grep \.js | wc -l) - echo $JS_FILES_MODIFIED - if [ -z $JS_FILES_MODIFIED ]; then echo "skip js test"; fi script: From 19406fb0fa33b4f486f51368da8732609778dd11 Mon Sep 17 00:00:00 2001 From: "Denis S. Soldatov aka General-Beck" Date: Sun, 6 Nov 2016 00:07:08 +0700 Subject: [PATCH 30/62] Update gitlab-ci add $NIGTHLY to var --- .gitlab-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 733c77dd3..7190d887b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -8,7 +8,7 @@ variables: RUST_BACKTRACE: "1" RUSTFLAGS: "" CARGOFLAGS: "" - variables[]=value + NIGHTLY: "nigtly" cache: key: "$CI_BUILD_REF_NAME" untracked: true @@ -391,7 +391,7 @@ test-rust-stable: image: ethcore/rust:stable before_script: - git submodule update --init --recursive - - export JS_FILES_MODIFIED=$(git --no-pager diff --name-only $CI_BUILD_REF $(git merge-base $CI_BUILD_REF master) | grep \.js | wc -l) + - export JS_FILES_MODIFIED=$(git --no-pager diff --name-only CI_BUILD_REF CI_BUILD_REF@{1} | grep \.js | wc -l) - echo $JS_FILES_MODIFIED - if [ -z $JS_FILES_MODIFIED ]; then echo "skip js test"; fi script: From 78f2d88182b3b294a698dee92dcc2cf5f613399a Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Sat, 5 Nov 2016 17:31:24 +0000 Subject: [PATCH 31/62] [ci skip] js-precompiled 20161105-173004 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1d3675140..4826c68cd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1249,7 +1249,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#1be902da9b630fcbf81b089a677df0527b3adb10" +source = "git+https://github.com/ethcore/js-precompiled.git#1ffffece9a3c1ebdab7a00c9137b1dafd2c2b0f2" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/js/package.json b/js/package.json index 092713fe1..eabdcfa7e 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.12", + "version": "0.2.13", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", From 9c4979681c7b90665b4ebbd523278c0522b42683 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Sun, 6 Nov 2016 12:51:53 +0100 Subject: [PATCH 32/62] Cleaning up polluted namespaces (#3143) * Renaming ethcore_ to parity_ * Renaming files * Renaming poluted EthSigning * Tidy up the namespaces * Renaming files to match new structure * Splitting EthSigning into separate traits * jsapi move ethcore.* -> parity.* * Move jsonrpc parity definitions * Update UI API calls for parity interfaces * Move jsapi signer interfaces from personal to signer * Update UI to use signer.* where applicable * Updsate jsapi subscriptions for signer * Fix dodgy merge. * Update README. * Fix some tests. * Move parity-only personal.* to parity.* * Update UI for personal -> parity API moves * Update subscription APIs after personal -> parity move * personal. generateAuthorizationToken -> parity. generateAuthorizationToken (UI) * enode, dappsPort & signerPort (UI) * Update subscription tests (accountsInfo) * subscription update * personal -> parity * Additional error logging on method failures * move postTransaction to parity * Additional debug info with method failures * Fix personal tests. * Console wrning shows parameters, error object does not * Include parity_ signing methods. * Console log http transport info * Fix failing tests * Add RPC stubs for parity_accounts. * Allow some secure built-in dapps * Use parity_accounts in place of accountsInfo * Improve error reporting * Cleanup GHH error handling --- README.md | 22 +- js/src/api/README.md | 3 +- js/src/api/api.js | 13 +- js/src/api/contract/contract.js | 6 +- js/src/api/contract/contract.spec.js | 22 +- js/src/api/rpc/eth/eth.js | 10 - js/src/api/rpc/ethcore/ethcore.js | 201 ------------- js/src/api/rpc/index.js | 3 +- js/src/api/rpc/{ethcore => parity}/index.js | 2 +- .../ethcore.e2e.js => parity/parity.e2e.js} | 14 +- js/src/api/rpc/parity/parity.js | 273 ++++++++++++++++++ .../ethcore.spec.js => parity/parity.spec.js} | 40 ++- js/src/api/rpc/personal/personal.js | 86 +----- js/src/api/rpc/personal/personal.spec.js | 22 -- js/src/api/rpc/signer/index.js | 17 ++ js/src/api/rpc/signer/signer.js | 50 ++++ js/src/api/subscriptions/manager.js | 6 +- js/src/api/subscriptions/personal.js | 20 +- js/src/api/subscriptions/personal.spec.js | 17 +- js/src/api/subscriptions/signer.js | 6 +- js/src/api/transport/http/http.js | 6 +- js/src/api/transport/ws/ws.js | 4 +- js/src/contracts/registry.js | 2 +- .../basiccoin/Application/application.js | 2 +- .../basiccoin/Deploy/Deployment/deployment.js | 2 +- js/src/dapps/basiccoin/Transfer/Send/send.js | 2 +- js/src/dapps/basiccoin/services.js | 4 +- js/src/dapps/githubhint.html | 1 - .../githubhint/Application/application.js | 49 +++- js/src/dapps/githubhint/parity.js | 2 +- js/src/dapps/githubhint/services.js | 4 +- js/src/dapps/registry/actions.js | 2 +- js/src/dapps/registry/addresses/actions.js | 2 +- js/src/dapps/signaturereg/services.js | 4 +- js/src/dapps/tokenreg/Accounts/actions.js | 2 +- js/src/dapps/tokenreg/Status/actions.js | 2 +- js/src/index.js | 2 +- js/src/jsonrpc/index.js | 20 +- js/src/jsonrpc/interfaces/eth.js | 30 -- .../interfaces/{ethcore.js => parity.js} | 189 ++++++++++++ js/src/jsonrpc/interfaces/personal.js | 199 ------------- js/src/jsonrpc/interfaces/signer.js | 82 ++++++ js/src/modals/AddAddress/addAddress.js | 4 +- js/src/modals/AddContract/addContract.js | 4 +- .../CreateAccount/NewAccount/newAccount.js | 12 +- .../modals/CreateAccount/NewGeth/newGeth.js | 2 +- js/src/modals/CreateAccount/createAccount.js | 22 +- .../modals/DeployContract/deployContract.js | 4 +- js/src/modals/EditMeta/editMeta.js | 4 +- .../modals/ExecuteContract/executeContract.js | 2 +- js/src/modals/FirstRun/firstRun.js | 4 +- .../modals/PasswordManager/passwordManager.js | 8 +- js/src/modals/Transfer/transfer.js | 6 +- js/src/redux/providers/balances.js | 4 +- js/src/redux/providers/personal.js | 4 +- js/src/redux/providers/signer.js | 18 +- js/src/redux/providers/signerMiddleware.js | 4 +- js/src/redux/providers/status.js | 26 +- js/src/secureApi.js | 6 +- js/src/views/Account/Header/header.js | 2 +- js/src/views/Address/Delete/delete.js | 2 +- js/src/views/Application/application.js | 4 +- js/src/views/Dapp/dapp.js | 66 ++++- js/src/views/Dapps/Summary/summary.js | 11 +- js/src/views/Dapps/registry.js | 18 +- js/src/views/Settings/Parity/parity.js | 4 +- .../components/SignRequest/SignRequest.js | 2 +- .../TransactionFinished.js | 2 +- .../TransactionPending/TransactionPending.js | 2 +- .../MiningSettings/MiningSettings.js | 8 +- parity/cli/config.full.toml | 4 +- parity/cli/mod.rs | 8 +- parity/cli/usage.txt | 4 +- parity/configuration.rs | 14 +- parity/rpc_apis.rs | 128 +++++--- rpc/src/v1/impls/mod.rs | 22 +- rpc/src/v1/impls/{ethcore.rs => parity.rs} | 120 +++----- ...ersonal_accounts.rs => parity_accounts.rs} | 81 ++---- .../impls/{ethcore_set.rs => parity_set.rs} | 95 +++++- rpc/src/v1/impls/personal.rs | 76 +++-- .../impls/{personal_signer.rs => signer.rs} | 6 +- .../v1/impls/{eth_signing.rs => signing.rs} | 154 +++------- rpc/src/v1/impls/signing_unsafe.rs | 110 +++++++ rpc/src/v1/mod.rs | 2 +- rpc/src/v1/tests/eth.rs | 4 +- rpc/src/v1/tests/mocked/eth.rs | 4 +- rpc/src/v1/tests/mocked/mod.rs | 9 +- .../v1/tests/mocked/{ethcore.rs => parity.rs} | 104 +++---- rpc/src/v1/tests/mocked/parity_accounts.rs | 118 ++++++++ .../mocked/{ethcore_set.rs => parity_set.rs} | 67 +++-- rpc/src/v1/tests/mocked/personal.rs | 66 +---- .../mocked/{personal_signer.rs => signer.rs} | 20 +- .../mocked/{eth_signing.rs => signing.rs} | 41 +-- rpc/src/v1/traits/eth_signing.rs | 23 -- rpc/src/v1/traits/mod.rs | 16 +- rpc/src/v1/traits/{ethcore.rs => parity.rs} | 71 ++--- rpc/src/v1/traits/parity_accounts.rs | 77 +++++ .../traits/{ethcore_set.rs => parity_set.rs} | 46 +-- rpc/src/v1/traits/parity_signing.rs | 52 ++++ rpc/src/v1/traits/personal.rs | 79 +---- rpc/src/v1/traits/signer.rs | 44 +++ 101 files changed, 1899 insertions(+), 1466 deletions(-) delete mode 100644 js/src/api/rpc/ethcore/ethcore.js rename js/src/api/rpc/{ethcore => parity}/index.js (95%) rename js/src/api/rpc/{ethcore/ethcore.e2e.js => parity/parity.e2e.js} (82%) create mode 100644 js/src/api/rpc/parity/parity.js rename js/src/api/rpc/{ethcore/ethcore.spec.js => parity/parity.spec.js} (66%) create mode 100644 js/src/api/rpc/signer/index.js create mode 100644 js/src/api/rpc/signer/signer.js rename js/src/jsonrpc/interfaces/{ethcore.js => parity.js} (66%) create mode 100644 js/src/jsonrpc/interfaces/signer.js rename rpc/src/v1/impls/{ethcore.rs => parity.rs} (74%) rename rpc/src/v1/impls/{personal_accounts.rs => parity_accounts.rs} (68%) rename rpc/src/v1/impls/{ethcore_set.rs => parity_set.rs} (62%) rename rpc/src/v1/impls/{personal_signer.rs => signer.rs} (94%) rename rpc/src/v1/impls/{eth_signing.rs => signing.rs} (72%) create mode 100644 rpc/src/v1/impls/signing_unsafe.rs rename rpc/src/v1/tests/mocked/{ethcore.rs => parity.rs} (73%) create mode 100644 rpc/src/v1/tests/mocked/parity_accounts.rs rename rpc/src/v1/tests/mocked/{ethcore_set.rs => parity_set.rs} (57%) rename rpc/src/v1/tests/mocked/{personal_signer.rs => signer.rs} (91%) rename rpc/src/v1/tests/mocked/{eth_signing.rs => signing.rs} (90%) rename rpc/src/v1/traits/{ethcore.rs => parity.rs} (74%) create mode 100644 rpc/src/v1/traits/parity_accounts.rs rename rpc/src/v1/traits/{ethcore_set.rs => parity_set.rs} (70%) create mode 100644 rpc/src/v1/traits/parity_signing.rs create mode 100644 rpc/src/v1/traits/signer.rs diff --git a/README.md b/README.md index 4861d7ac5..fc5cd9762 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,12 @@ # [Parity](https://ethcore.io/parity.html) ### Fast, light, and robust Ethereum implementation -[![Join the chat at https://gitter.im/ethcore/parity.js](https://badges.gitter.im/ethcore/parity.js.svg)](https://gitter.im/ethcore/parity.js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![Build Status][travis-image]][travis-url] [![build status](https://gitlab.ethcore.io/Mirrors/ethcore-parity/badges/master/build.svg)](https://gitlab.ethcore.io/Mirrors/ethcore-parity/commits/master) [![Coverage Status][coveralls-image]][coveralls-url] [![GPLv3][license-image]][license-url] -[![Build Status][travis-image]][travis-url] [![build status](https://gitlab.ethcore.io/Mirrors/ethcore-parity/badges/master/build.svg)](https://gitlab.ethcore.io/Mirrors/ethcore-parity/commits/master) [![Coverage Status][coveralls-image]][coveralls-url] [![Join the chat at https://gitter.im/ethcore/parity][gitter-image]][gitter-url] [![GPLv3][license-image]][license-url] +### Join the chat! + +Parity [![Join the chat at https://gitter.im/ethcore/parity][gitter-image]][gitter-url] and +parity.js [![Join the chat at https://gitter.im/ethcore/parity.js](https://badges.gitter.im/ethcore/parity.js.svg)](https://gitter.im/ethcore/parity.js?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [Internal Documentation][doc-url] @@ -21,7 +24,7 @@ Be sure to check out [our wiki][wiki-url] for more information. [doc-url]: https://ethcore.github.io/parity/ethcore/index.html [wiki-url]: https://github.com/ethcore/parity/wiki -**Requires Rust version 1.12.0 to build** +**Parity requires Rust version 1.12.0 to build** ---- @@ -31,12 +34,15 @@ Be sure to check out [our wiki][wiki-url] for more information. Parity's goal is to be the fastest, lightest, and most secure Ethereum client. We are developing Parity using the sophisticated and cutting-edge Rust programming language. Parity is licensed under the GPLv3, and can be used for all your Ethereum needs. -By default, Parity will run a JSONRPC server on `127.0.0.1:8545`. This is fully configurable and supports a number -of RPC APIs. +Parity comes with a built-in wallet. To access [Parity Wallet](http://127.0.0.1:8080/) this simply go to http://127.0.0.1:8080/. It +includes various functionality allowing you to: +- create and manage your Ethereum accounts; +- manage your Ether and any Ethereum tokens; +- create and register your own tokens; +- and much more. -Parity also runs a server for running decentralized apps, or "Dapps", on `http://127.0.0.1:8080`. -This includes a few useful Dapps, including Ethereum Wallet, Maker OTC, and a node status page. -In a near-future release, it will be easy to install Dapps and use them through this web interface. +By default, Parity will also run a JSONRPC server on `127.0.0.1:8545`. This is fully configurable and supports a number +of RPC APIs. If you run into an issue while using parity, feel free to file one in this repository or hop on our [gitter chat room][gitter-url] to ask a question. We are glad to help! diff --git a/js/src/api/README.md b/js/src/api/README.md index 691a24cca..e28c6c2a1 100644 --- a/js/src/api/README.md +++ b/js/src/api/README.md @@ -135,10 +135,11 @@ APIs implement the calls as exposed in the [Ethcore JSON Ethereum RPC](https://g - [ethapi.db](https://github.com/ethcore/ethereum-rpc-json/blob/master/interfaces.md#db) - [ethapi.eth](https://github.com/ethcore/ethereum-rpc-json/blob/master/interfaces.md#eth) -- [ethapi.ethcore](https://github.com/ethcore/ethereum-rpc-json/blob/master/interfaces.md#ethcore) +- [ethapi.parity](https://github.com/ethcore/ethereum-rpc-json/blob/master/interfaces.md#parity) - [ethapi.net](https://github.com/ethcore/ethereum-rpc-json/blob/master/interfaces.md#net) - [ethapi.personal](https://github.com/ethcore/ethereum-rpc-json/blob/master/interfaces.md#personal) - [ethapi.shh](https://github.com/ethcore/ethereum-rpc-json/blob/master/interfaces.md#shh) +- [ethapi.signer](https://github.com/ethcore/ethereum-rpc-json/blob/master/interfaces.md#signer) - [ethapi.trace](https://github.com/ethcore/ethereum-rpc-json/blob/master/interfaces.md#trace) - [ethapi.web3](https://github.com/ethcore/ethereum-rpc-json/blob/master/interfaces.md#web3) diff --git a/js/src/api/api.js b/js/src/api/api.js index 9768b9acb..75d4392d0 100644 --- a/js/src/api/api.js +++ b/js/src/api/api.js @@ -17,7 +17,7 @@ import { Http, Ws } from './transport'; import Contract from './contract'; -import { Db, Eth, Ethcore, Net, Personal, Shh, Trace, Web3 } from './rpc'; +import { Db, Eth, Parity, Net, Personal, Shh, Signer, Trace, Web3 } from './rpc'; import Subscriptions from './subscriptions'; import util from './util'; import { isFunction } from './util/types'; @@ -32,10 +32,11 @@ export default class Api { this._db = new Db(transport); this._eth = new Eth(transport); - this._ethcore = new Ethcore(transport); this._net = new Net(transport); + this._parity = new Parity(transport); this._personal = new Personal(transport); this._shh = new Shh(transport); + this._signer = new Signer(transport); this._trace = new Trace(transport); this._web3 = new Web3(transport); @@ -50,8 +51,8 @@ export default class Api { return this._eth; } - get ethcore () { - return this._ethcore; + get parity () { + return this._parity; } get net () { @@ -66,6 +67,10 @@ export default class Api { return this._shh; } + get signer () { + return this._signer; + } + get trace () { return this._trace; } diff --git a/js/src/api/contract/contract.js b/js/src/api/contract/contract.js index cef75eda7..bb6c15d8d 100644 --- a/js/src/api/contract/contract.js +++ b/js/src/api/contract/contract.js @@ -102,7 +102,7 @@ export default class Contract { options.gas = gas.toFixed(0); setState({ state: 'postTransaction', gas }); - return this._api.eth.postTransaction(this._encodeOptions(this.constructors[0], options, values)); + return this._api.parity.postTransaction(this._encodeOptions(this.constructors[0], options, values)); }) .then((requestId) => { setState({ state: 'checkRequest', requestId }); @@ -166,7 +166,7 @@ export default class Contract { } _pollCheckRequest = (requestId) => { - return this._api.pollMethod('eth_checkRequest', requestId); + return this._api.pollMethod('parity_checkRequest', requestId); } _pollTransactionReceipt = (txhash, gas) => { @@ -208,7 +208,7 @@ export default class Contract { if (!func.constant) { func.postTransaction = (options, values = []) => { - return this._api.eth + return this._api.parity .postTransaction(this._encodeOptions(func, this._addOptionsTo(options), values)); }; diff --git a/js/src/api/contract/contract.spec.js b/js/src/api/contract/contract.spec.js index 0d6169e26..9065b4fad 100644 --- a/js/src/api/contract/contract.spec.js +++ b/js/src/api/contract/contract.spec.js @@ -249,9 +249,9 @@ describe('api/contract/Contract', () => { before(() => { scope = mockHttp([ { method: 'eth_estimateGas', reply: { result: 1000 } }, - { method: 'eth_postTransaction', reply: { result: '0x678' } }, - { method: 'eth_checkRequest', reply: { result: null } }, - { method: 'eth_checkRequest', reply: { result: '0x890' } }, + { method: 'parity_postTransaction', reply: { result: '0x678' } }, + { method: 'parity_checkRequest', reply: { result: null } }, + { method: 'parity_checkRequest', reply: { result: '0x890' } }, { method: 'eth_getTransactionReceipt', reply: { result: null } }, { method: 'eth_getTransactionReceipt', reply: { result: RECEIPT_PEND } }, { method: 'eth_getTransactionReceipt', reply: { result: RECEIPT_DONE } }, @@ -266,7 +266,7 @@ describe('api/contract/Contract', () => { }); it('passes the options through to postTransaction (incl. gas calculation)', () => { - expect(scope.body.eth_postTransaction.params).to.deep.equal([ + expect(scope.body.parity_postTransaction.params).to.deep.equal([ { data: '0x123', gas: '0x4b0' } ]); }); @@ -280,8 +280,8 @@ describe('api/contract/Contract', () => { it('fails when gasUsed == gas', () => { mockHttp([ { method: 'eth_estimateGas', reply: { result: 1000 } }, - { method: 'eth_postTransaction', reply: { result: '0x678' } }, - { method: 'eth_checkRequest', reply: { result: '0x789' } }, + { method: 'parity_postTransaction', reply: { result: '0x678' } }, + { method: 'parity_checkRequest', reply: { result: '0x789' } }, { method: 'eth_getTransactionReceipt', reply: { result: RECEIPT_EXCP } } ]); @@ -295,8 +295,8 @@ describe('api/contract/Contract', () => { it('fails when no code was deployed', () => { mockHttp([ { method: 'eth_estimateGas', reply: { result: 1000 } }, - { method: 'eth_postTransaction', reply: { result: '0x678' } }, - { method: 'eth_checkRequest', reply: { result: '0x789' } }, + { method: 'parity_postTransaction', reply: { result: '0x678' } }, + { method: 'parity_checkRequest', reply: { result: '0x789' } }, { method: 'eth_getTransactionReceipt', reply: { result: RECEIPT_DONE } }, { method: 'eth_getCode', reply: { result: '0x' } } ]); @@ -360,15 +360,15 @@ describe('api/contract/Contract', () => { describe('postTransaction', () => { beforeEach(() => { - scope = mockHttp([{ method: 'eth_postTransaction', reply: { result: ['hashId'] } }]); + scope = mockHttp([{ method: 'parity_postTransaction', reply: { result: ['hashId'] } }]); }); - it('encodes options and mades an eth_postTransaction call', () => { + it('encodes options and mades an parity_postTransaction call', () => { return func .postTransaction({ someExtras: 'foo' }, VALUES) .then(() => { expect(scope.isDone()).to.be.true; - expect(scope.body.eth_postTransaction.params[0]).to.deep.equal({ + expect(scope.body.parity_postTransaction.params[0]).to.deep.equal({ someExtras: 'foo', to: ADDR, data: ENCODED diff --git a/js/src/api/rpc/eth/eth.js b/js/src/api/rpc/eth/eth.js index 703f3ed11..43f8025e1 100644 --- a/js/src/api/rpc/eth/eth.js +++ b/js/src/api/rpc/eth/eth.js @@ -39,11 +39,6 @@ export default class Eth { .execute('eth_call', inOptions(options), inBlockNumber(blockNumber)); } - checkRequest (requestId) { - return this._transport - .execute('eth_checkRequest', inNumber16(requestId)); - } - coinbase () { return this._transport .execute('eth_coinbase') @@ -267,11 +262,6 @@ export default class Eth { .execute('eth_pendingTransactions'); } - postTransaction (options) { - return this._transport - .execute('eth_postTransaction', inOptions(options)); - } - protocolVersion () { return this._transport .execute('eth_protocolVersion'); diff --git a/js/src/api/rpc/ethcore/ethcore.js b/js/src/api/rpc/ethcore/ethcore.js deleted file mode 100644 index e21c83193..000000000 --- a/js/src/api/rpc/ethcore/ethcore.js +++ /dev/null @@ -1,201 +0,0 @@ -// 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 . - -import { inAddress, inData, inNumber16 } from '../../format/input'; -import { outAddress, outHistogram, outNumber, outPeers } from '../../format/output'; - -export default class Ethcore { - constructor (transport) { - this._transport = transport; - } - - acceptNonReservedPeers () { - return this._transport - .execute('ethcore_acceptNonReservedPeers'); - } - - addReservedPeer (encode) { - return this._transport - .execute('ethcore_addReservedPeer', encode); - } - - dappsPort () { - return this._transport - .execute('ethcore_dappsPort') - .then(outNumber); - } - - defaultExtraData () { - return this._transport - .execute('ethcore_defaultExtraData'); - } - - devLogs () { - return this._transport - .execute('ethcore_devLogs'); - } - - devLogsLevels () { - return this._transport - .execute('ethcore_devLogsLevels'); - } - - dropNonReservedPeers () { - return this._transport - .execute('ethcore_dropNonReservedPeers'); - } - - enode () { - return this._transport - .execute('ethcore_enode'); - } - - extraData () { - return this._transport - .execute('ethcore_extraData'); - } - - gasFloorTarget () { - return this._transport - .execute('ethcore_gasFloorTarget') - .then(outNumber); - } - - gasPriceHistogram () { - return this._transport - .execute('ethcore_gasPriceHistogram') - .then(outHistogram); - } - - generateSecretPhrase () { - return this._transport - .execute('ethcore_generateSecretPhrase'); - } - - hashContent (url) { - return this._transport - .execute('ethcore_hashContent', url); - } - - minGasPrice () { - return this._transport - .execute('ethcore_minGasPrice') - .then(outNumber); - } - - mode () { - return this._transport - .execute('ethcore_mode'); - } - - netChain () { - return this._transport - .execute('ethcore_netChain'); - } - - netPeers () { - return this._transport - .execute('ethcore_netPeers') - .then(outPeers); - } - - netMaxPeers () { - return this._transport - .execute('ethcore_netMaxPeers') - .then(outNumber); - } - - netPort () { - return this._transport - .execute('ethcore_netPort') - .then(outNumber); - } - - nodeName () { - return this._transport - .execute('ethcore_nodeName'); - } - - phraseToAddress (phrase) { - return this._transport - .execute('ethcore_phraseToAddress', phrase) - .then(outAddress); - } - - registryAddress () { - return this._transport - .execute('ethcore_registryAddress') - .then(outAddress); - } - - removeReservedPeer (encode) { - return this._transport - .execute('ethcore_removeReservedPeer', encode); - } - - rpcSettings () { - return this._transport - .execute('ethcore_rpcSettings'); - } - - setAuthor (address) { - return this._transport - .execute('ethcore_setAuthor', inAddress(address)); - } - - setExtraData (data) { - return this._transport - .execute('ethcore_setExtraData', inData(data)); - } - - setGasFloorTarget (quantity) { - return this._transport - .execute('ethcore_setGasFloorTarget', inNumber16(quantity)); - } - - setMinGasPrice (quantity) { - return this._transport - .execute('ethcore_setMinGasPrice', inNumber16(quantity)); - } - - setMode (mode) { - return this._transport - .execute('ethcore_setMode', mode); - } - - setTransactionsLimit (quantity) { - return this._transport - .execute('ethcore_setTransactionsLimit', inNumber16(quantity)); - } - - signerPort () { - return this._transport - .execute('ethcore_signerPort') - .then(outNumber); - } - - transactionsLimit () { - return this._transport - .execute('ethcore_transactionsLimit') - .then(outNumber); - } - - unsignedTransactionsCount () { - return this._transport - .execute('ethcore_unsignedTransactionsCount') - .then(outNumber); - } -} diff --git a/js/src/api/rpc/index.js b/js/src/api/rpc/index.js index e7e94b9ed..7961da81c 100644 --- a/js/src/api/rpc/index.js +++ b/js/src/api/rpc/index.js @@ -16,9 +16,10 @@ export Db from './db'; export Eth from './eth'; -export Ethcore from './ethcore'; +export Parity from './parity'; export Net from './net'; export Personal from './personal'; export Shh from './shh'; +export Signer from './signer'; export Trace from './trace'; export Web3 from './web3'; diff --git a/js/src/api/rpc/ethcore/index.js b/js/src/api/rpc/parity/index.js similarity index 95% rename from js/src/api/rpc/ethcore/index.js rename to js/src/api/rpc/parity/index.js index 2372a2171..38f08f725 100644 --- a/js/src/api/rpc/ethcore/index.js +++ b/js/src/api/rpc/parity/index.js @@ -14,4 +14,4 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -export default from './ethcore'; +export default from './parity'; diff --git a/js/src/api/rpc/ethcore/ethcore.e2e.js b/js/src/api/rpc/parity/parity.e2e.js similarity index 82% rename from js/src/api/rpc/ethcore/ethcore.e2e.js rename to js/src/api/rpc/parity/parity.e2e.js index aae7108e7..91e01ab6a 100644 --- a/js/src/api/rpc/ethcore/ethcore.e2e.js +++ b/js/src/api/rpc/parity/parity.e2e.js @@ -16,12 +16,12 @@ import { createHttpApi } from '../../../../test/e2e/ethapi'; -describe('ethapi.ethcore', () => { +describe('ethapi.parity', () => { const ethapi = createHttpApi(); describe('gasFloorTarget', () => { it('returns and translates the target', () => { - return ethapi.ethcore.gasFloorTarget().then((value) => { + return ethapi.parity.gasFloorTarget().then((value) => { expect(value.gt(0)).to.be.true; }); }); @@ -29,7 +29,7 @@ describe('ethapi.ethcore', () => { describe('gasPriceHistogram', () => { it('returns and translates the target', () => { - return ethapi.ethcore.gasPriceHistogram().then((result) => { + return ethapi.parity.gasPriceHistogram().then((result) => { expect(Object.keys(result)).to.deep.equal(['bucketBounds', 'counts']); expect(result.bucketBounds.length > 0).to.be.true; expect(result.counts.length > 0).to.be.true; @@ -39,7 +39,7 @@ describe('ethapi.ethcore', () => { describe('netChain', () => { it('returns and the chain', () => { - return ethapi.ethcore.netChain().then((value) => { + return ethapi.parity.netChain().then((value) => { expect(value).to.equal('morden'); }); }); @@ -47,7 +47,7 @@ describe('ethapi.ethcore', () => { describe('netPort', () => { it('returns and translates the port', () => { - return ethapi.ethcore.netPort().then((value) => { + return ethapi.parity.netPort().then((value) => { expect(value.gt(0)).to.be.true; }); }); @@ -55,7 +55,7 @@ describe('ethapi.ethcore', () => { describe('transactionsLimit', () => { it('returns and translates the limit', () => { - return ethapi.ethcore.transactionsLimit().then((value) => { + return ethapi.parity.transactionsLimit().then((value) => { expect(value.gt(0)).to.be.true; }); }); @@ -63,7 +63,7 @@ describe('ethapi.ethcore', () => { describe('rpcSettings', () => { it('returns and translates the settings', () => { - return ethapi.ethcore.rpcSettings().then((value) => { + return ethapi.parity.rpcSettings().then((value) => { expect(value).to.be.ok; }); }); diff --git a/js/src/api/rpc/parity/parity.js b/js/src/api/rpc/parity/parity.js new file mode 100644 index 000000000..f1739f848 --- /dev/null +++ b/js/src/api/rpc/parity/parity.js @@ -0,0 +1,273 @@ +// 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 . + +import { inAddress, inData, inHex, inNumber16, inOptions } from '../../format/input'; +import { outAccountInfo, outAddress, outHistogram, outNumber, outPeers } from '../../format/output'; + +export default class Parity { + constructor (transport) { + this._transport = transport; + } + + acceptNonReservedPeers () { + return this._transport + .execute('parity_acceptNonReservedPeers'); + } + + accounts () { + return this._transport + .execute('parity_accounts') + .then(outAccountInfo); + } + + accountsInfo () { + return this._transport + .execute('parity_accountsInfo') + .then(outAccountInfo); + } + + addReservedPeer (encode) { + return this._transport + .execute('parity_addReservedPeer', encode); + } + + changePassword (account, password, newPassword) { + return this._transport + .execute('parity_changePassword', inAddress(account), password, newPassword); + } + + checkRequest (requestId) { + return this._transport + .execute('parity_checkRequest', inNumber16(requestId)); + } + + dappsPort () { + return this._transport + .execute('parity_dappsPort') + .then(outNumber); + } + + defaultExtraData () { + return this._transport + .execute('parity_defaultExtraData'); + } + + devLogs () { + return this._transport + .execute('parity_devLogs'); + } + + devLogsLevels () { + return this._transport + .execute('parity_devLogsLevels'); + } + + dropNonReservedPeers () { + return this._transport + .execute('parity_dropNonReservedPeers'); + } + + enode () { + return this._transport + .execute('parity_enode'); + } + + extraData () { + return this._transport + .execute('parity_extraData'); + } + + gasFloorTarget () { + return this._transport + .execute('parity_gasFloorTarget') + .then(outNumber); + } + + gasPriceHistogram () { + return this._transport + .execute('parity_gasPriceHistogram') + .then(outHistogram); + } + + generateSecretPhrase () { + return this._transport + .execute('parity_generateSecretPhrase'); + } + + hashContent (url) { + return this._transport + .execute('parity_hashContent', url); + } + + listGethAccounts () { + return this._transport + .execute('parity_listGethAccounts') + .then((accounts) => (accounts || []).map(outAddress)); + } + + importGethAccounts (accounts) { + return this._transport + .execute('parity_importGethAccounts', (accounts || []).map(inAddress)) + .then((accounts) => (accounts || []).map(outAddress)); + } + + minGasPrice () { + return this._transport + .execute('parity_minGasPrice') + .then(outNumber); + } + + mode () { + return this._transport + .execute('parity_mode'); + } + + netChain () { + return this._transport + .execute('parity_netChain'); + } + + netPeers () { + return this._transport + .execute('parity_netPeers') + .then(outPeers); + } + + netMaxPeers () { + return this._transport + .execute('parity_netMaxPeers') + .then(outNumber); + } + + netPort () { + return this._transport + .execute('parity_netPort') + .then(outNumber); + } + + newAccountFromPhrase (phrase, password) { + return this._transport + .execute('parity_newAccountFromPhrase', phrase, password) + .then(outAddress); + } + + newAccountFromSecret (secret, password) { + return this._transport + .execute('parity_newAccountFromSecret', inHex(secret), password) + .then(outAddress); + } + + newAccountFromWallet (json, password) { + return this._transport + .execute('parity_newAccountFromWallet', json, password) + .then(outAddress); + } + + nodeName () { + return this._transport + .execute('parity_nodeName'); + } + + phraseToAddress (phrase) { + return this._transport + .execute('parity_phraseToAddress', phrase) + .then(outAddress); + } + + postTransaction (options) { + return this._transport + .execute('parity_postTransaction', inOptions(options)); + } + + registryAddress () { + return this._transport + .execute('parity_registryAddress') + .then(outAddress); + } + + removeReservedPeer (encode) { + return this._transport + .execute('parity_removeReservedPeer', encode); + } + + rpcSettings () { + return this._transport + .execute('parity_rpcSettings'); + } + + setAccountName (address, name) { + return this._transport + .execute('parity_setAccountName', inAddress(address), name); + } + + setAccountMeta (address, meta) { + return this._transport + .execute('parity_setAccountMeta', inAddress(address), JSON.stringify(meta)); + } + + setAuthor (address) { + return this._transport + .execute('parity_setAuthor', inAddress(address)); + } + + setExtraData (data) { + return this._transport + .execute('parity_setExtraData', inData(data)); + } + + setGasFloorTarget (quantity) { + return this._transport + .execute('parity_setGasFloorTarget', inNumber16(quantity)); + } + + setMinGasPrice (quantity) { + return this._transport + .execute('parity_setMinGasPrice', inNumber16(quantity)); + } + + setMode (mode) { + return this._transport + .execute('parity_setMode', mode); + } + + setTransactionsLimit (quantity) { + return this._transport + .execute('parity_setTransactionsLimit', inNumber16(quantity)); + } + + signerPort () { + return this._transport + .execute('parity_signerPort') + .then(outNumber); + } + + testPassword (account, password) { + return this._transport + .execute('parity_testPassword', inAddress(account), password); + } + + transactionsLimit () { + return this._transport + .execute('parity_transactionsLimit') + .then(outNumber); + } + + unsignedTransactionsCount () { + return this._transport + .execute('parity_unsignedTransactionsCount') + .then(outNumber); + } +} diff --git a/js/src/api/rpc/ethcore/ethcore.spec.js b/js/src/api/rpc/parity/parity.spec.js similarity index 66% rename from js/src/api/rpc/ethcore/ethcore.spec.js rename to js/src/api/rpc/parity/parity.spec.js index fd34550a7..ea0dd8d8c 100644 --- a/js/src/api/rpc/ethcore/ethcore.spec.js +++ b/js/src/api/rpc/parity/parity.spec.js @@ -18,14 +18,36 @@ import { TEST_HTTP_URL, mockHttp } from '../../../../test/mockRpc'; import { isBigNumber } from '../../../../test/types'; import Http from '../../transport/http'; -import Ethcore from './ethcore'; +import Parity from './parity'; -const instance = new Ethcore(new Http(TEST_HTTP_URL)); +const instance = new Parity(new Http(TEST_HTTP_URL)); + +describe('api/rpc/parity', () => { + describe('accountsInfo', () => { + it('retrieves the available account info', () => { + mockHttp([{ method: 'parity_accountsInfo', reply: { + result: { + '0x63cf90d3f0410092fc0fca41846f596223979195': { + name: 'name', uuid: 'uuid', meta: '{"data":"data"}' + } + } + } }]); + + return instance.accountsInfo().then((result) => { + expect(result).to.deep.equal({ + '0x63Cf90D3f0410092FC0fca41846f596223979195': { + name: 'name', uuid: 'uuid', meta: { + data: 'data' + } + } + }); + }); + }); + }); -describe('api/rpc/Ethcore', () => { describe('gasFloorTarget', () => { it('returns the gasfloor, formatted', () => { - mockHttp([{ method: 'ethcore_gasFloorTarget', reply: { result: '0x123456' } }]); + mockHttp([{ method: 'parity_gasFloorTarget', reply: { result: '0x123456' } }]); return instance.gasFloorTarget().then((count) => { expect(isBigNumber(count)).to.be.true; @@ -36,7 +58,7 @@ describe('api/rpc/Ethcore', () => { describe('minGasPrice', () => { it('returns the min gasprice, formatted', () => { - mockHttp([{ method: 'ethcore_minGasPrice', reply: { result: '0x123456' } }]); + mockHttp([{ method: 'parity_minGasPrice', reply: { result: '0x123456' } }]); return instance.minGasPrice().then((count) => { expect(isBigNumber(count)).to.be.true; @@ -47,7 +69,7 @@ describe('api/rpc/Ethcore', () => { describe('netMaxPeers', () => { it('returns the max peers, formatted', () => { - mockHttp([{ method: 'ethcore_netMaxPeers', reply: { result: 25 } }]); + mockHttp([{ method: 'parity_netMaxPeers', reply: { result: 25 } }]); return instance.netMaxPeers().then((count) => { expect(isBigNumber(count)).to.be.true; @@ -58,7 +80,7 @@ describe('api/rpc/Ethcore', () => { describe('newPeers', () => { it('returns the peer structure, formatted', () => { - mockHttp([{ method: 'ethcore_netPeers', reply: { result: { active: 123, connected: 456, max: 789 } } }]); + mockHttp([{ method: 'parity_netPeers', reply: { result: { active: 123, connected: 456, max: 789 } } }]); return instance.netPeers().then((peers) => { expect(peers.active.eq(123)).to.be.true; @@ -70,7 +92,7 @@ describe('api/rpc/Ethcore', () => { describe('netPort', () => { it('returns the connected port, formatted', () => { - mockHttp([{ method: 'ethcore_netPort', reply: { result: 33030 } }]); + mockHttp([{ method: 'parity_netPort', reply: { result: 33030 } }]); return instance.netPort().then((count) => { expect(isBigNumber(count)).to.be.true; @@ -81,7 +103,7 @@ describe('api/rpc/Ethcore', () => { describe('transactionsLimit', () => { it('returns the tx limit, formatted', () => { - mockHttp([{ method: 'ethcore_transactionsLimit', reply: { result: 1024 } }]); + mockHttp([{ method: 'parity_transactionsLimit', reply: { result: 1024 } }]); return instance.transactionsLimit().then((count) => { expect(isBigNumber(count)).to.be.true; diff --git a/js/src/api/rpc/personal/personal.js b/js/src/api/rpc/personal/personal.js index ca7dbce9b..db9a71d23 100644 --- a/js/src/api/rpc/personal/personal.js +++ b/js/src/api/rpc/personal/personal.js @@ -14,113 +14,31 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import { inAddress, inHex, inNumber10, inNumber16, inOptions } from '../../format/input'; -import { outAccountInfo, outAddress, outSignerRequest } from '../../format/output'; +import { inAddress, inNumber10, inOptions } from '../../format/input'; +import { outAddress } from '../../format/output'; export default class Personal { constructor (transport) { this._transport = transport; } - accountsInfo () { - return this._transport - .execute('personal_accountsInfo') - .then(outAccountInfo); - } - - confirmRequest (requestId, options, password) { - return this._transport - .execute('personal_confirmRequest', inNumber16(requestId), options, password); - } - - changePassword (account, password, newPassword) { - return this._transport - .execute('personal_changePassword', inAddress(account), password, newPassword); - } - - generateAuthorizationToken () { - return this._transport - .execute('personal_generateAuthorizationToken'); - } - listAccounts () { return this._transport .execute('personal_listAccounts') .then((accounts) => (accounts || []).map(outAddress)); } - listGethAccounts () { - return this._transport - .execute('personal_listGethAccounts') - .then((accounts) => (accounts || []).map(outAddress)); - } - - importGethAccounts (accounts) { - return this._transport - .execute('personal_importGethAccounts', (accounts || []).map(inAddress)) - .then((accounts) => (accounts || []).map(outAddress)); - } - newAccount (password) { return this._transport .execute('personal_newAccount', password) .then(outAddress); } - newAccountFromPhrase (phrase, password) { - return this._transport - .execute('personal_newAccountFromPhrase', phrase, password) - .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) - .then(outAddress); - } - - rejectRequest (requestId) { - return this._transport - .execute('personal_rejectRequest', inNumber16(requestId)); - } - - requestsToConfirm () { - return this._transport - .execute('personal_requestsToConfirm') - .then((requests) => (requests || []).map(outSignerRequest)); - } - - setAccountName (address, name) { - return this._transport - .execute('personal_setAccountName', inAddress(address), name); - } - - setAccountMeta (address, meta) { - return this._transport - .execute('personal_setAccountMeta', inAddress(address), JSON.stringify(meta)); - } - signAndSendTransaction (options, password) { return this._transport .execute('personal_signAndSendTransaction', inOptions(options), password); } - signerEnabled () { - return this._transport - .execute('personal_signerEnabled'); - } - - testPassword (account, password) { - return this._transport - .execute('personal_testPassword', inAddress(account), password); - } - unlockAccount (account, password, duration = 1) { return this._transport .execute('personal_unlockAccount', inAddress(account), password, inNumber10(duration)); diff --git a/js/src/api/rpc/personal/personal.spec.js b/js/src/api/rpc/personal/personal.spec.js index 70734c7ee..a9bf4f644 100644 --- a/js/src/api/rpc/personal/personal.spec.js +++ b/js/src/api/rpc/personal/personal.spec.js @@ -26,28 +26,6 @@ describe('rpc/Personal', () => { const checksum = '0x63Cf90D3f0410092FC0fca41846f596223979195'; let scope; - describe('accountsInfo', () => { - it('retrieves the available account info', () => { - scope = mockHttp([{ method: 'personal_accountsInfo', reply: { - result: { - '0x63cf90d3f0410092fc0fca41846f596223979195': { - name: 'name', uuid: 'uuid', meta: '{"data":"data"}' - } - } - } }]); - - return instance.accountsInfo().then((result) => { - expect(result).to.deep.equal({ - '0x63Cf90D3f0410092FC0fca41846f596223979195': { - name: 'name', uuid: 'uuid', meta: { - data: 'data' - } - } - }); - }); - }); - }); - describe('listAccounts', () => { it('retrieves a list of available accounts', () => { scope = mockHttp([{ method: 'personal_listAccounts', reply: { result: [account] } }]); diff --git a/js/src/api/rpc/signer/index.js b/js/src/api/rpc/signer/index.js new file mode 100644 index 000000000..6426bdc06 --- /dev/null +++ b/js/src/api/rpc/signer/index.js @@ -0,0 +1,17 @@ +// 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 . + +export default from './signer'; diff --git a/js/src/api/rpc/signer/signer.js b/js/src/api/rpc/signer/signer.js new file mode 100644 index 000000000..7f905cf50 --- /dev/null +++ b/js/src/api/rpc/signer/signer.js @@ -0,0 +1,50 @@ +// 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 . + +import { inNumber16 } from '../../format/input'; +import { outSignerRequest } from '../../format/output'; + +export default class Signer { + constructor (transport) { + this._transport = transport; + } + + confirmRequest (requestId, options, password) { + return this._transport + .execute('signer_confirmRequest', inNumber16(requestId), options, password); + } + + generateAuthorizationToken () { + return this._transport + .execute('signer_generateAuthorizationToken'); + } + + rejectRequest (requestId) { + return this._transport + .execute('signer_rejectRequest', inNumber16(requestId)); + } + + requestsToConfirm () { + return this._transport + .execute('signer_requestsToConfirm') + .then((requests) => (requests || []).map(outSignerRequest)); + } + + signerEnabled () { + return this._transport + .execute('signer_signerEnabled'); + } +} diff --git a/js/src/api/subscriptions/manager.js b/js/src/api/subscriptions/manager.js index 61e06499e..08f1a9e53 100644 --- a/js/src/api/subscriptions/manager.js +++ b/js/src/api/subscriptions/manager.js @@ -24,9 +24,9 @@ import Signer from './signer'; const events = { 'logging': { module: 'logging' }, 'eth_blockNumber': { module: 'eth' }, - 'personal_accountsInfo': { module: 'personal' }, - 'personal_listAccounts': { module: 'personal' }, - 'personal_requestsToConfirm': { module: 'signer' } + 'parity_accountsInfo': { module: 'personal' }, + 'eth_accounts': { module: 'personal' }, + 'signer_requestsToConfirm': { module: 'signer' } }; export default class Manager { diff --git a/js/src/api/subscriptions/personal.js b/js/src/api/subscriptions/personal.js index d65419962..58428895b 100644 --- a/js/src/api/subscriptions/personal.js +++ b/js/src/api/subscriptions/personal.js @@ -37,18 +37,18 @@ export default class Personal { } _listAccounts = () => { - return this._api.personal - .listAccounts() + return this._api.eth + .accounts() .then((accounts) => { - this._updateSubscriptions('personal_listAccounts', null, accounts); + this._updateSubscriptions('eth_accounts', null, accounts); }); } _accountsInfo = () => { - return this._api.personal + return this._api.parity .accountsInfo() .then((info) => { - this._updateSubscriptions('personal_accountsInfo', null, info); + this._updateSubscriptions('parity_accountsInfo', null, info); }); } @@ -59,16 +59,16 @@ export default class Personal { } switch (data.method) { - case 'personal_importGethAccounts': + case 'parity_importGethAccounts': case 'personal_newAccount': - case 'personal_newAccountFromPhrase': - case 'personal_newAccountFromWallet': + case 'parity_newAccountFromPhrase': + case 'parity_newAccountFromWallet': this._listAccounts(); this._accountsInfo(); return; - case 'personal_setAccountName': - case 'personal_setAccountMeta': + case 'parity_setAccountName': + case 'parity_setAccountMeta': this._accountsInfo(); return; } diff --git a/js/src/api/subscriptions/personal.spec.js b/js/src/api/subscriptions/personal.spec.js index 1a77b5f61..d6fd2b203 100644 --- a/js/src/api/subscriptions/personal.spec.js +++ b/js/src/api/subscriptions/personal.spec.js @@ -34,14 +34,15 @@ function stubApi (accounts, info) { return { _calls, - personal: { + parity: { accountsInfo: () => { const stub = sinon.stub().resolves(info || TEST_INFO)(); _calls.accountsInfo.push(stub); return stub; - }, - - listAccounts: () => { + } + }, + eth: { + accounts: () => { const stub = sinon.stub().resolves(accounts || TEST_LIST)(); _calls.listAccounts.push(stub); return stub; @@ -85,17 +86,17 @@ describe('api/subscriptions/personal', () => { expect(personal.isStarted).to.be.true; }); - it('calls personal_accountsInfo', () => { + it('calls parity_accountsInfo', () => { expect(api._calls.accountsInfo.length).to.be.ok; }); - it('calls personal_listAccounts', () => { + it('calls eth_accounts', () => { expect(api._calls.listAccounts.length).to.be.ok; }); it('updates subscribers', () => { - expect(cb.firstCall).to.have.been.calledWith('personal_listAccounts', null, TEST_LIST); - expect(cb.secondCall).to.have.been.calledWith('personal_accountsInfo', null, TEST_INFO); + expect(cb.firstCall).to.have.been.calledWith('eth_accounts', null, TEST_LIST); + expect(cb.secondCall).to.have.been.calledWith('parity_accountsInfo', null, TEST_INFO); }); }); diff --git a/js/src/api/subscriptions/signer.js b/js/src/api/subscriptions/signer.js index af745261b..4413fe432 100644 --- a/js/src/api/subscriptions/signer.js +++ b/js/src/api/subscriptions/signer.js @@ -49,10 +49,10 @@ export default class Signer { return; } - return this._api.personal + return this._api.signer .requestsToConfirm() .then((requests) => { - this._updateSubscriptions('personal_requestsToConfirm', null, requests); + this._updateSubscriptions('signer_requestsToConfirm', null, requests); nextTimeout(); }) .catch(nextTimeout); @@ -65,7 +65,7 @@ export default class Signer { } switch (data.method) { - case 'eth_postTransaction': + case 'parity_postTransaction': case 'eth_sendTranasction': case 'eth_sendRawTransaction': this._listRequests(false); diff --git a/js/src/api/transport/http/http.js b/js/src/api/transport/http/http.js index 65ba089cc..08d9422f8 100644 --- a/js/src/api/transport/http/http.js +++ b/js/src/api/transport/http/http.js @@ -56,6 +56,8 @@ export default class Http extends JsonRpcBase { if (response.status !== 200) { this._connected = false; this.error(JSON.stringify({ status: response.status, statusText: response.statusText })); + console.error(`${method}(${JSON.stringify(params)}): ${response.status}: ${response.statusText}`); + throw new Error(`${response.status}: ${response.statusText}`); } @@ -66,7 +68,9 @@ export default class Http extends JsonRpcBase { if (response.error) { this.error(JSON.stringify(response)); - throw new Error(`${response.error.code}: ${response.error.message}`); + console.error(`${method}(${JSON.stringify(params)}): ${response.error.code}: ${response.error.message}`); + + throw new Error(`${method}: ${response.error.code}: ${response.error.message}`); } this.log(JSON.stringify(response)); diff --git a/js/src/api/transport/ws/ws.js b/js/src/api/transport/ws/ws.js index 119f4ba76..d608426b0 100644 --- a/js/src/api/transport/ws/ws.js +++ b/js/src/api/transport/ws/ws.js @@ -107,7 +107,9 @@ export default class Ws extends JsonRpcBase { if (result.error) { this.error(event.data); - reject(new Error(`${result.error.code}: ${result.error.message}`)); + console.error(`${method}(${JSON.stringify(params)}): ${result.error.code}: ${result.error.message}`); + + reject(new Error(`${method}: ${result.error.code}: ${result.error.message}`)); delete this._messages[result.id]; return; } diff --git a/js/src/contracts/registry.js b/js/src/contracts/registry.js index 85b9d6bb5..9853c0df9 100644 --- a/js/src/contracts/registry.js +++ b/js/src/contracts/registry.js @@ -32,7 +32,7 @@ export default class Registry { return; } - this._api.ethcore + this._api.parity .registryAddress() .then((address) => { this._instance = this._api.newContract(abis.registry, address).instance; diff --git a/js/src/dapps/basiccoin/Application/application.js b/js/src/dapps/basiccoin/Application/application.js index 4ab97ab6c..abe0c90c5 100644 --- a/js/src/dapps/basiccoin/Application/application.js +++ b/js/src/dapps/basiccoin/Application/application.js @@ -83,7 +83,7 @@ export default class Application extends Component { Promise .all([ attachInstances(), - api.personal.accountsInfo() + api.parity.accounts() ]) .then(([{ managerInstance, registryInstance, tokenregInstance }, accountsInfo]) => { accountsInfo = accountsInfo || {}; diff --git a/js/src/dapps/basiccoin/Deploy/Deployment/deployment.js b/js/src/dapps/basiccoin/Deploy/Deployment/deployment.js index f9232789b..0fa7dc863 100644 --- a/js/src/dapps/basiccoin/Deploy/Deployment/deployment.js +++ b/js/src/dapps/basiccoin/Deploy/Deployment/deployment.js @@ -296,7 +296,7 @@ export default class Deployment extends Component { .then((signerRequestId) => { this.setState({ signerRequestId, deployState: 'Transaction posted, Waiting for transaction authorization' }); - return api.pollMethod('eth_checkRequest', signerRequestId); + return api.pollMethod('parity_checkRequest', signerRequestId); }) .then((txHash) => { this.setState({ txHash, deployState: 'Transaction authorized, Waiting for network confirmations' }); diff --git a/js/src/dapps/basiccoin/Transfer/Send/send.js b/js/src/dapps/basiccoin/Transfer/Send/send.js index aee860fe2..a9c05a228 100644 --- a/js/src/dapps/basiccoin/Transfer/Send/send.js +++ b/js/src/dapps/basiccoin/Transfer/Send/send.js @@ -279,7 +279,7 @@ export default class Send extends Component { .then((signerRequestId) => { this.setState({ signerRequestId, sendState: 'Transaction posted, Waiting for transaction authorization' }); - return api.pollMethod('eth_checkRequest', signerRequestId); + return api.pollMethod('parity_checkRequest', signerRequestId); }) .then((txHash) => { this.setState({ txHash, sendState: 'Transaction authorized, Waiting for network confirmations' }); diff --git a/js/src/dapps/basiccoin/services.js b/js/src/dapps/basiccoin/services.js index 28cc662a7..4aed4199f 100644 --- a/js/src/dapps/basiccoin/services.js +++ b/js/src/dapps/basiccoin/services.js @@ -100,8 +100,8 @@ export function attachInstances () { return Promise .all([ - api.ethcore.registryAddress(), - api.ethcore.netChain() + api.parity.registryAddress(), + api.parity.netChain() ]) .then(([registryAddress, netChain]) => { const registry = api.newContract(abis.registry, registryAddress).instance; diff --git a/js/src/dapps/githubhint.html b/js/src/dapps/githubhint.html index 631182fcb..746c7f466 100644 --- a/js/src/dapps/githubhint.html +++ b/js/src/dapps/githubhint.html @@ -11,7 +11,6 @@
- diff --git a/js/src/dapps/githubhint/Application/application.js b/js/src/dapps/githubhint/Application/application.js index 4a25d3855..5a7494928 100644 --- a/js/src/dapps/githubhint/Application/application.js +++ b/js/src/dapps/githubhint/Application/application.js @@ -41,7 +41,9 @@ export default class Application extends Component { registerBusy: false, registerError: null, registerState: '', - registerType: 'file' + registerType: 'file', + repo: '', + repoError: null } componentDidMount () { @@ -206,47 +208,64 @@ export default class Application extends Component { } onChangeCommit = (event) => { - const commit = event.target.value; + let commit = event.target.value; const commitError = null; + let hasContent = false; - // TODO: field validation + this.setState({ commit, commitError, contentHashError: null }, () => { + const { repo } = this.state || ''; + const parts = repo.split('/'); - this.setState({ commit, commitError, contentHashError: 'hash lookup in progress' }, () => { - const { repo } = this.state; - this.lookupHash(`https://codeload.github.com/${repo}/zip/${commit}`); + hasContent = commit.length !== 0 && parts.length === 2 && parts[0].length !== 0 && parts[1].length !== 0; + if (!commitError && hasContent) { + this.setState({ contentHashError: 'hash lookup in progress' }); + this.lookupHash(`https://codeload.github.com/${repo}/zip/${commit}`); + } }); } onChangeRepo = (event) => { let repo = event.target.value; const repoError = null; + let hasContent = false; // TODO: field validation if (!repoError) { repo = repo.replace('https://github.com/', ''); } - this.setState({ repo, repoError, contentHashError: 'hash lookup in progress' }, () => { - const { commit } = this.state; - this.lookupHash(`https://codeload.github.com/${repo}/zip/${commit}`); + this.setState({ repo, repoError, contentHashError: null }, () => { + const { commit } = this.state || ''; + const parts = repo.split('/'); + + hasContent = commit.length !== 0 && parts.length === 2 && parts[0].length !== 0 && parts[1].length !== 0; + if (!repoError && hasContent) { + this.setState({ contentHashError: 'hash lookup in progress' }); + this.lookupHash(`https://codeload.github.com/${repo}/zip/${commit}`); + } }); } onChangeUrl = (event) => { let url = event.target.value; const urlError = null; + let hasContent = false; // TODO: field validation if (!urlError) { const parts = url.split('/'); + hasContent = parts.length !== 0; if (parts[2] === 'github.com' || parts[2] === 'raw.githubusercontent.com') { url = `https://raw.githubusercontent.com/${parts.slice(3).join('/')}`.replace('/blob/', '/'); } } - this.setState({ url, urlError, contentHashError: 'hash lookup in progress' }, () => { - this.lookupHash(url); + this.setState({ url, urlError, contentHashError: null }, () => { + if (!urlError && hasContent) { + this.setState({ contentHashError: 'hash lookup in progress' }); + this.lookupHash(url); + } }); } @@ -271,7 +290,7 @@ export default class Application extends Component { .then((signerRequestId) => { this.setState({ signerRequestId, registerState: 'Transaction posted, Waiting for transaction authorization' }); - return api.pollMethod('eth_checkRequest', signerRequestId); + return api.pollMethod('parity_checkRequest', signerRequestId); }) .then((txHash) => { this.setState({ txHash, registerState: 'Transaction authorized, Waiting for network confirmations' }); @@ -285,7 +304,7 @@ export default class Application extends Component { }); }) .then((txReceipt) => { - this.setState({ txReceipt, registerBusy: false, registerState: 'Network confirmed, Received transaction receipt', url: '', commit: '', commitError: null, contentHash: '', contentHashOwner: null, contentHashError: null }); + this.setState({ txReceipt, registerBusy: false, registerState: 'Network confirmed, Received transaction receipt', url: '', commit: '', repo: '', commitError: null, contentHash: '', contentHashOwner: null, contentHashError: null }); }) .catch((error) => { console.error('onSend', error); @@ -298,7 +317,7 @@ export default class Application extends Component { this.setState({ registerBusy: true, registerState: 'Estimating gas for the transaction' }); - const values = [contentHash, repo, commit]; + const values = [contentHash, repo, commit.substr(0, 2) === '0x' ? commit : `0x${commit}`]; const options = { from: fromAddress }; this.trackRequest( @@ -367,7 +386,7 @@ export default class Application extends Component { console.log(`lookupHash ${url}`); - api.ethcore + api.parity .hashContent(url) .then((contentHash) => { console.log('lookupHash', contentHash); diff --git a/js/src/dapps/githubhint/parity.js b/js/src/dapps/githubhint/parity.js index f6d59f44d..acee4dee0 100644 --- a/js/src/dapps/githubhint/parity.js +++ b/js/src/dapps/githubhint/parity.js @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -const { api } = window.parity; +const api = window.parent.secureApi; export { api diff --git a/js/src/dapps/githubhint/services.js b/js/src/dapps/githubhint/services.js index b7676d5f5..c9d260b73 100644 --- a/js/src/dapps/githubhint/services.js +++ b/js/src/dapps/githubhint/services.js @@ -18,7 +18,7 @@ import * as abis from '../../contracts/abi'; import { api } from './parity'; export function attachInterface () { - return api.ethcore + return api.parity .registryAddress() .then((registryAddress) => { console.log(`the registry was found at ${registryAddress}`); @@ -29,7 +29,7 @@ export function attachInterface () { .all([ registry.getAddress.call({}, [api.util.sha3('githubhint'), 'A']), api.eth.accounts(), - api.personal.accountsInfo() + api.parity.accounts() ]); }) .then(([address, addresses, accountsInfo]) => { diff --git a/js/src/dapps/registry/actions.js b/js/src/dapps/registry/actions.js index 882af0360..b1390926b 100644 --- a/js/src/dapps/registry/actions.js +++ b/js/src/dapps/registry/actions.js @@ -29,7 +29,7 @@ export { addresses, accounts, lookup, events, names, records }; export const setContract = (contract) => ({ type: 'set contract', contract }); export const fetchContract = () => (dispatch) => - api.ethcore.registryAddress() + api.parity.registryAddress() .then((address) => { const contract = api.newContract(registryAbi, address); dispatch(setContract(contract)); diff --git a/js/src/dapps/registry/addresses/actions.js b/js/src/dapps/registry/addresses/actions.js index 17975f9e6..2341d716c 100644 --- a/js/src/dapps/registry/addresses/actions.js +++ b/js/src/dapps/registry/addresses/actions.js @@ -22,7 +22,7 @@ export const fetch = () => (dispatch) => { return Promise .all([ api.eth.accounts(), - api.personal.accountsInfo() + api.parity.accounts() ]) .then(([ accounts, data ]) => { data = data || {}; diff --git a/js/src/dapps/signaturereg/services.js b/js/src/dapps/signaturereg/services.js index cab324f7e..54394c4b8 100644 --- a/js/src/dapps/signaturereg/services.js +++ b/js/src/dapps/signaturereg/services.js @@ -39,7 +39,7 @@ const logToEvent = (log) => { }; export function attachInterface (callback) { - return api.ethcore + return api.parity .registryAddress() .then((registryAddress) => { console.log(`the registry was found at ${registryAddress}`); @@ -50,7 +50,7 @@ export function attachInterface (callback) { .all([ registry.getAddress.call({}, [api.util.sha3('signaturereg'), 'A']), api.eth.accounts(), - api.personal.accountsInfo() + api.parity.accounts() ]); }) .then(([address, addresses, accountsInfo]) => { diff --git a/js/src/dapps/tokenreg/Accounts/actions.js b/js/src/dapps/tokenreg/Accounts/actions.js index f501399c2..58a74dfd8 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(), - api.personal.accountsInfo() + api.parity.accounts() ]) .then(([ accounts, accountsInfo ]) => { accountsInfo = accountsInfo || {}; diff --git a/js/src/dapps/tokenreg/Status/actions.js b/js/src/dapps/tokenreg/Status/actions.js index b7de9c108..e9e217d6a 100644 --- a/js/src/dapps/tokenreg/Status/actions.js +++ b/js/src/dapps/tokenreg/Status/actions.js @@ -34,7 +34,7 @@ export const FIND_CONTRACT = 'FIND_CONTRACT'; export const loadContract = () => (dispatch) => { dispatch(setLoading(true)); - api.ethcore + api.parity .registryAddress() .then((registryAddress) => { console.log(`registry found at ${registryAddress}`); diff --git a/js/src/index.js b/js/src/index.js index a8cb8ac96..966e2708e 100644 --- a/js/src/index.js +++ b/js/src/index.js @@ -74,7 +74,7 @@ ReactDOM.render( - + diff --git a/js/src/jsonrpc/index.js b/js/src/jsonrpc/index.js index 18bd4302d..9e9692279 100644 --- a/js/src/jsonrpc/index.js +++ b/js/src/jsonrpc/index.js @@ -16,20 +16,22 @@ import db from './interfaces/db'; import eth from './interfaces/eth'; -import ethcore from './interfaces/ethcore'; import net from './interfaces/net'; +import parity from './interfaces/parity'; import personal from './interfaces/personal'; import shh from './interfaces/shh'; +import signer from './interfaces/signer'; import trace from './interfaces/trace'; import web3 from './interfaces/web3'; export default { - db: db, - eth: eth, - ethcore: ethcore, - net: net, - personal: personal, - shh: shh, - trace: trace, - web3: web3 + db, + eth, + parity, + net, + personal, + shh, + signer, + trace, + web3 }; diff --git a/js/src/jsonrpc/interfaces/eth.js b/js/src/jsonrpc/interfaces/eth.js index f1c8fb86f..d5ff471fb 100644 --- a/js/src/jsonrpc/interfaces/eth.js +++ b/js/src/jsonrpc/interfaces/eth.js @@ -86,20 +86,6 @@ export default { } }, - checkRequest: { - desc: 'Returns the transactionhash of the requestId (received from eth_postTransaction) if the request was confirmed', - params: [ - { - type: Quantity, - desc: 'The requestId to check for' - } - ], - returns: { - type: Hash, - desc: '32 Bytes - the transaction hash, or the zero hash if the transaction is not yet available' - } - }, - coinbase: { desc: 'Returns the client coinbase address.', params: [], @@ -823,22 +809,6 @@ export default { } }, - postTransaction: { - desc: 'Posts a transaction to the Signer.', - params: [ - { - type: Object, - desc: 'see [eth_sendTransaction](#eth_sendTransaction)', - format: 'inputCallFormatter' - } - ], - returns: { - type: Quantity, - desc: 'The id of the actual transaction', - format: 'utils.toDecimal' - } - }, - protocolVersion: { desc: 'Returns the current ethereum protocol version.', params: [], diff --git a/js/src/jsonrpc/interfaces/ethcore.js b/js/src/jsonrpc/interfaces/parity.js similarity index 66% rename from js/src/jsonrpc/interfaces/ethcore.js rename to js/src/jsonrpc/interfaces/parity.js index 310276cc8..66a8ea962 100644 --- a/js/src/jsonrpc/interfaces/ethcore.js +++ b/js/src/jsonrpc/interfaces/parity.js @@ -26,6 +26,52 @@ export default { } }, + accounts: { + desc: 'returns a map of accounts as an object', + params: [], + returns: { + type: Array, + desc: 'Account metadata', + details: { + name: { + type: String, + desc: 'Account name' + }, + meta: { + type: String, + desc: 'Encoded JSON string the defines additional account metadata' + }, + uuid: { + type: String, + desc: 'The account UUID, or null if not available/unknown/not applicable.' + } + } + } + }, + + accountsInfo: { + desc: 'returns a map of accounts as an object', + params: [], + returns: { + type: Array, + desc: 'Account metadata', + details: { + name: { + type: String, + desc: 'Account name' + }, + meta: { + type: String, + desc: 'Encoded JSON string the defines additional account metadata' + }, + uuid: { + type: String, + desc: 'The account UUID, or null if not available/unknown/not applicable.' + } + } + } + }, + addReservedPeer: { desc: '?', params: [ @@ -40,6 +86,20 @@ export default { } }, + checkRequest: { + desc: 'Returns the transactionhash of the requestId (received from parity_postTransaction) if the request was confirmed', + params: [ + { + type: Quantity, + desc: 'The requestId to check for' + } + ], + returns: { + type: Hash, + desc: '32 Bytes - the transaction hash, or the zero hash if the transaction is not yet available' + } + }, + dappsPort: { desc: 'Returns the port the dapps are running on, error if not enabled', params: [], @@ -155,6 +215,29 @@ export default { } }, + listGethAccounts: { + desc: 'Returns a list of the accounts available from Geth', + params: [], + returns: { + type: Array, + desc: '20 Bytes addresses owned by the client.' + } + }, + + importGethAccounts: { + desc: 'Imports a list of accounts from geth', + params: [ + { + type: Array, + desc: 'List of the geth addresses to import' + } + ], + returns: { + type: Array, + desc: 'Array of the imported addresses' + } + }, + minGasPrice: { desc: 'Returns currently set minimal gas price', params: [], @@ -210,6 +293,60 @@ export default { } }, + newAccountFromPhrase: { + desc: 'Creates a new account from a recovery passphrase', + params: [ + { + type: String, + desc: 'Phrase' + }, + { + type: String, + desc: 'Password' + } + ], + returns: { + type: Address, + desc: 'The created address' + } + }, + + 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: [ + { + type: String, + desc: 'JSON' + }, + { + type: String, + desc: 'Password' + } + ], + returns: { + type: Address, + desc: 'The created address' + } + }, + nodeName: { desc: 'Returns node name (identity)', params: [], @@ -233,6 +370,22 @@ export default { } }, + postTransaction: { + desc: 'Posts a transaction to the Signer.', + params: [ + { + type: Object, + desc: 'see [eth_sendTransaction](#eth_sendTransaction)', + format: 'inputCallFormatter' + } + ], + returns: { + type: Quantity, + desc: 'The id of the actual transaction', + format: 'utils.toDecimal' + } + }, + removeReservedPeer: { desc: '?', params: [ @@ -265,6 +418,42 @@ export default { } }, + setAccountName: { + desc: 'Sets a name for the account', + params: [ + { + type: Address, + desc: 'Address' + }, + { + type: String, + desc: 'Name' + } + ], + returns: { + type: Object, + desc: 'Returns null in all cases' + } + }, + + setAccountMeta: { + desc: 'Sets metadata for the account', + params: [ + { + type: Address, + desc: 'Address' + }, + { + type: String, + desc: 'Metadata (JSON encoded)' + } + ], + returns: { + type: Object, + desc: 'Returns null in all cases' + } + }, + setAuthor: { desc: 'Changes author (coinbase) for mined blocks.', params: [ diff --git a/js/src/jsonrpc/interfaces/personal.js b/js/src/jsonrpc/interfaces/personal.js index 2a9ce7c19..eb7e5fc0f 100644 --- a/js/src/jsonrpc/interfaces/personal.js +++ b/js/src/jsonrpc/interfaces/personal.js @@ -17,83 +17,6 @@ import { Address, Data, Quantity } from '../types'; export default { - accountsInfo: { - desc: 'returns a map of accounts as an object', - params: [], - returns: { - type: Array, - desc: 'Account metadata', - details: { - name: { - type: String, - desc: 'Account name' - }, - meta: { - type: String, - desc: 'Encoded JSON string the defines additional account metadata' - }, - uuid: { - type: String, - desc: 'The account UUID, or null if not available/unknown/not applicable.' - } - } - } - }, - - generateAuthorizationToken: { - desc: 'Generates a new authorization token', - params: [], - returns: { - type: String, - desc: 'The new authorization token' - } - }, - - requestsToConfirm: { - desc: 'Returns a list of the transactions requiring authorization', - params: [], - returns: { - type: Array, - desc: 'A list of the outstanding transactions' - } - }, - - confirmRequest: { - desc: 'Confirm a request in the signer queue', - params: [ - { - type: Quantity, - desc: 'The request id' - }, - { - type: Object, - desc: 'The request options' - }, - { - type: String, - desc: 'The account password' - } - ], - returns: { - type: Boolean, - desc: 'The status of the confirmation' - } - }, - - rejectRequest: { - desc: 'Rejects a request in the signer queue', - params: [ - { - type: Quantity, - desc: 'The request id' - } - ], - returns: { - type: Boolean, - desc: 'The status of the rejection' - } - }, - listAccounts: { desc: 'Returns a list of addresses owned by client.', params: [], @@ -103,29 +26,6 @@ export default { } }, - listGethAccounts: { - desc: 'Returns a list of the accounts available from Geth', - params: [], - returns: { - type: Array, - desc: '20 Bytes addresses owned by the client.' - } - }, - - importGethAccounts: { - desc: 'Imports a list of accounts from geth', - params: [ - { - type: Array, - desc: 'List of the geth addresses to import' - } - ], - returns: { - type: Array, - desc: 'Array of the imported addresses' - } - }, - newAccount: { desc: 'Creates new account', params: [ @@ -140,96 +40,6 @@ export default { } }, - newAccountFromPhrase: { - desc: 'Creates a new account from a recovery passphrase', - params: [ - { - type: String, - desc: 'Phrase' - }, - { - type: String, - desc: 'Password' - } - ], - returns: { - type: Address, - desc: 'The created address' - } - }, - - 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: [ - { - type: String, - desc: 'JSON' - }, - { - type: String, - desc: 'Password' - } - ], - returns: { - type: Address, - desc: 'The created address' - } - }, - - setAccountName: { - desc: 'Sets a name for the account', - params: [ - { - type: Address, - desc: 'Address' - }, - { - type: String, - desc: 'Name' - } - ], - returns: { - type: Object, - desc: 'Returns null in all cases' - } - }, - - setAccountMeta: { - desc: 'Sets metadata for the account', - params: [ - { - type: Address, - desc: 'Address' - }, - { - type: String, - desc: 'Metadata (JSON encoded)' - } - ], - returns: { - type: Object, - desc: 'Returns null in all cases' - } - }, - signAndSendTransaction: { desc: 'Sends and signs a transaction given account passphrase. Does not require the account to be unlocked nor unlocks the account for future transactions. ', params: [ @@ -284,15 +94,6 @@ export default { } }, - signerEnabled: { - desc: 'Returns whether signer is enabled/disabled.', - params: [], - returns: { - type: Boolean, - desc: 'true when enabled, false when disabled' - } - }, - unlockAccount: { desc: '?', params: [ diff --git a/js/src/jsonrpc/interfaces/signer.js b/js/src/jsonrpc/interfaces/signer.js new file mode 100644 index 000000000..f394dbb61 --- /dev/null +++ b/js/src/jsonrpc/interfaces/signer.js @@ -0,0 +1,82 @@ +// 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 . + +import { Quantity } from '../types'; + +export default { + generateAuthorizationToken: { + desc: 'Generates a new authorization token', + params: [], + returns: { + type: String, + desc: 'The new authorization token' + } + }, + + requestsToConfirm: { + desc: 'Returns a list of the transactions requiring authorization', + params: [], + returns: { + type: Array, + desc: 'A list of the outstanding transactions' + } + }, + + confirmRequest: { + desc: 'Confirm a request in the signer queue', + params: [ + { + type: Quantity, + desc: 'The request id' + }, + { + type: Object, + desc: 'The request options' + }, + { + type: String, + desc: 'The account password' + } + ], + returns: { + type: Boolean, + desc: 'The status of the confirmation' + } + }, + + rejectRequest: { + desc: 'Rejects a request in the signer queue', + params: [ + { + type: Quantity, + desc: 'The request id' + } + ], + returns: { + type: Boolean, + desc: 'The status of the rejection' + } + }, + + signerEnabled: { + desc: 'Returns whether signer is enabled/disabled.', + params: [], + returns: { + type: Boolean, + desc: 'true when enabled, false when disabled' + } + } +}; diff --git a/js/src/modals/AddAddress/addAddress.js b/js/src/modals/AddAddress/addAddress.js index ebcf78815..c8845aa13 100644 --- a/js/src/modals/AddAddress/addAddress.js +++ b/js/src/modals/AddAddress/addAddress.js @@ -133,8 +133,8 @@ export default class AddAddress extends Component { const { address, name, description } = this.state; Promise.all([ - api.personal.setAccountName(address, name), - api.personal.setAccountMeta(address, { + api.parity.setAccountName(address, name), + api.parity.setAccountMeta(address, { description, timestamp: Date.now(), deleted: false diff --git a/js/src/modals/AddContract/addContract.js b/js/src/modals/AddContract/addContract.js index ad7345430..418378136 100644 --- a/js/src/modals/AddContract/addContract.js +++ b/js/src/modals/AddContract/addContract.js @@ -141,8 +141,8 @@ export default class AddContract extends Component { const { abiParsed, address, name, description } = this.state; Promise.all([ - api.personal.setAccountName(address, name), - api.personal.setAccountMeta(address, { + api.parity.setAccountName(address, name), + api.parity.setAccountMeta(address, { contract: true, deleted: false, timestamp: Date.now(), diff --git a/js/src/modals/CreateAccount/NewAccount/newAccount.js b/js/src/modals/CreateAccount/NewAccount/newAccount.js index b05a6db75..2fde79ca6 100644 --- a/js/src/modals/CreateAccount/NewAccount/newAccount.js +++ b/js/src/modals/CreateAccount/NewAccount/newAccount.js @@ -173,15 +173,15 @@ export default class CreateAccount extends Component { Promise .all([ - api.ethcore.generateSecretPhrase(), - api.ethcore.generateSecretPhrase(), - api.ethcore.generateSecretPhrase(), - api.ethcore.generateSecretPhrase(), - api.ethcore.generateSecretPhrase() + api.parity.generateSecretPhrase(), + api.parity.generateSecretPhrase(), + api.parity.generateSecretPhrase(), + api.parity.generateSecretPhrase(), + api.parity.generateSecretPhrase() ]) .then((phrases) => { return Promise - .all(phrases.map((phrase) => api.ethcore.phraseToAddress(phrase))) + .all(phrases.map((phrase) => api.parity.phraseToAddress(phrase))) .then((addresses) => { const accounts = {}; diff --git a/js/src/modals/CreateAccount/NewGeth/newGeth.js b/js/src/modals/CreateAccount/NewGeth/newGeth.js index 8853a671b..4b6cc2c96 100644 --- a/js/src/modals/CreateAccount/NewGeth/newGeth.js +++ b/js/src/modals/CreateAccount/NewGeth/newGeth.js @@ -102,7 +102,7 @@ export default class NewGeth extends Component { const { api } = this.context; const { accounts } = this.props; - api.personal + api.parity .listGethAccounts() .then((_addresses) => { const addresses = (addresses || []).filter((address) => !accounts[address]); diff --git a/js/src/modals/CreateAccount/createAccount.js b/js/src/modals/CreateAccount/createAccount.js index aacc91d5e..283e91531 100644 --- a/js/src/modals/CreateAccount/createAccount.js +++ b/js/src/modals/CreateAccount/createAccount.js @@ -208,13 +208,13 @@ export default class CreateAccount extends Component { }); if (createType === 'fromNew' || createType === 'fromPhrase') { - return api.personal + return api.parity .newAccountFromPhrase(this.state.phrase, this.state.password) .then((address) => { this.setState({ address }); - return api.personal + return api.parity .setAccountName(address, this.state.name) - .then(() => api.personal.setAccountMeta(address, { + .then(() => api.parity.setAccountMeta(address, { timestamp: Date.now(), passwordHint: this.state.passwordHint })); @@ -233,13 +233,13 @@ export default class CreateAccount extends Component { this.newError(error); }); } else if (createType === 'fromRaw') { - return api.personal + return api.parity .newAccountFromSecret(this.state.rawKey, this.state.password) .then((address) => { this.setState({ address }); - return api.personal + return api.parity .setAccountName(address, this.state.name) - .then(() => api.personal.setAccountMeta(address, { + .then(() => api.parity.setAccountMeta(address, { timestamp: Date.now(), passwordHint: this.state.passwordHint })); @@ -258,13 +258,13 @@ export default class CreateAccount extends Component { this.newError(error); }); } else if (createType === 'fromGeth') { - return api.personal + return api.parity .importGethAccounts(this.state.gethAddresses) .then((result) => { console.log('result', result); return Promise.all(this.state.gethAddresses.map((address) => { - return api.personal.setAccountName(address, 'Geth Import'); + return api.parity.setAccountName(address, 'Geth Import'); })); }) .then(() => { @@ -282,16 +282,16 @@ export default class CreateAccount extends Component { }); } - return api.personal + return api.parity .newAccountFromWallet(this.state.json, this.state.password) .then((address) => { this.setState({ address: address }); - return api.personal + return api.parity .setAccountName(address, this.state.name) - .then(() => api.personal.setAccountMeta(address, { + .then(() => api.parity.setAccountMeta(address, { timestamp: Date.now(), passwordHint: this.state.passwordHint })); diff --git a/js/src/modals/DeployContract/deployContract.js b/js/src/modals/DeployContract/deployContract.js index 8dd57ffd1..588d16f6a 100644 --- a/js/src/modals/DeployContract/deployContract.js +++ b/js/src/modals/DeployContract/deployContract.js @@ -213,8 +213,8 @@ export default class DeployContract extends Component { .deploy(options, params, this.onDeploymentState) .then((address) => { return Promise.all([ - api.personal.setAccountName(address, name), - api.personal.setAccountMeta(address, { + api.parity.setAccountName(address, name), + api.parity.setAccountMeta(address, { abi: abiParsed, contract: true, timestamp: Date.now(), diff --git a/js/src/modals/EditMeta/editMeta.js b/js/src/modals/EditMeta/editMeta.js index b2ba89d61..ad893aa17 100644 --- a/js/src/modals/EditMeta/editMeta.js +++ b/js/src/modals/EditMeta/editMeta.js @@ -139,8 +139,8 @@ export default class EditMeta extends Component { Promise .all([ - api.personal.setAccountName(account.address, name), - api.personal.setAccountMeta(account.address, Object.assign({}, account.meta, meta)) + api.parity.setAccountName(account.address, name), + api.parity.setAccountMeta(account.address, Object.assign({}, account.meta, meta)) ]) .then(() => this.props.onClose()) .catch((error) => { diff --git a/js/src/modals/ExecuteContract/executeContract.js b/js/src/modals/ExecuteContract/executeContract.js index e0462982d..b45cf6875 100644 --- a/js/src/modals/ExecuteContract/executeContract.js +++ b/js/src/modals/ExecuteContract/executeContract.js @@ -221,7 +221,7 @@ export default class ExecuteContract extends Component { }) .then((requestId) => { this.setState({ busyState: 'Waiting for authorization in the Parity Signer' }); - return api.pollMethod('eth_checkRequest', requestId); + return api.pollMethod('parity_checkRequest', requestId); }) .then((txhash) => { this.setState({ sending: false, step: 2, txhash, busyState: 'Your transaction has been posted to the network' }); diff --git a/js/src/modals/FirstRun/firstRun.js b/js/src/modals/FirstRun/firstRun.js index ea49e852e..3a676fc3b 100644 --- a/js/src/modals/FirstRun/firstRun.js +++ b/js/src/modals/FirstRun/firstRun.js @@ -183,9 +183,9 @@ export default class FirstRun extends Component { canCreate: false }); - return api.personal + return api.parity .newAccountFromPhrase(phrase, password) - .then((address) => api.personal.setAccountName(address, name)) + .then((address) => api.parity.setAccountName(address, name)) .then(() => { this.onNext(); }) diff --git a/js/src/modals/PasswordManager/passwordManager.js b/js/src/modals/PasswordManager/passwordManager.js index 368664a71..aabf691ef 100644 --- a/js/src/modals/PasswordManager/passwordManager.js +++ b/js/src/modals/PasswordManager/passwordManager.js @@ -317,7 +317,7 @@ export default class PasswordManager extends Component { this.setState({ waiting: true, showMessage: false }); this.context - .api.personal + .api.parity .testPassword(account.address, currentPass) .then(correct => { const message = correct @@ -343,7 +343,7 @@ export default class PasswordManager extends Component { this.setState({ waiting: true, showMessage: false }); this.context - .api.personal + .api.parity .testPassword(account.address, currentPass) .then(correct => { if (!correct) { @@ -363,11 +363,11 @@ export default class PasswordManager extends Component { return Promise.all([ this.context - .api.personal + .api.parity .setAccountMeta(account.address, meta), this.context - .api.personal + .api.parity .changePassword(account.address, currentPass, newPass) ]) .then(() => { diff --git a/js/src/modals/Transfer/transfer.js b/js/src/modals/Transfer/transfer.js index b505ad0ae..506e91930 100644 --- a/js/src/modals/Transfer/transfer.js +++ b/js/src/modals/Transfer/transfer.js @@ -424,7 +424,7 @@ export default class Transfer extends Component { options.data = data; } - return api.eth.postTransaction(options); + return api.parity.postTransaction(options); } _sendToken () { @@ -455,7 +455,7 @@ export default class Transfer extends Component { : this._sendToken() ).then((requestId) => { this.setState({ busyState: 'Waiting for authorization in the Parity Signer' }); - return api.pollMethod('eth_checkRequest', requestId); + return api.pollMethod('parity_checkRequest', requestId); }) .then((txhash) => { this.onNext(); @@ -592,7 +592,7 @@ export default class Transfer extends Component { Promise .all([ - api.ethcore.gasPriceHistogram(), + api.parity.gasPriceHistogram(), api.eth.gasPrice() ]) .then(([gasPriceHistogram, gasPrice]) => { diff --git a/js/src/redux/providers/balances.js b/js/src/redux/providers/balances.js index fa60e4310..b80fad28f 100644 --- a/js/src/redux/providers/balances.js +++ b/js/src/redux/providers/balances.js @@ -42,7 +42,7 @@ export default class Balances { _subscribeAccountsInfo () { this._api - .subscribe('personal_accountsInfo', (error, accountsInfo) => { + .subscribe('parity_accountsInfo', (error, accountsInfo) => { if (error) { return; } @@ -76,7 +76,7 @@ export default class Balances { } _retrieveTokens () { - this._api.ethcore + this._api.parity .registryAddress() .then((registryAddress) => { const registry = this._api.newContract(abis.registry, registryAddress); diff --git a/js/src/redux/providers/personal.js b/js/src/redux/providers/personal.js index e75b4082b..fd67ab5f7 100644 --- a/js/src/redux/providers/personal.js +++ b/js/src/redux/providers/personal.js @@ -28,9 +28,9 @@ export default class Personal { _subscribeAccountsInfo () { this._api - .subscribe('personal_accountsInfo', (error, accountsInfo) => { + .subscribe('parity_accountsInfo', (error, accountsInfo) => { if (error) { - console.error('personal_accountsInfo', error); + console.error('parity_accountsInfo', error); return; } diff --git a/js/src/redux/providers/signer.js b/js/src/redux/providers/signer.js index 92f80d51e..5ece371c2 100644 --- a/js/src/redux/providers/signer.js +++ b/js/src/redux/providers/signer.js @@ -14,22 +14,6 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -// 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 . - import { signerRequestsToConfirm } from './signerActions'; export default class Signer { @@ -44,7 +28,7 @@ export default class Signer { _subscribeRequestsToConfirm () { this._api - .subscribe('personal_requestsToConfirm', (error, pending) => { + .subscribe('signer_requestsToConfirm', (error, pending) => { if (error) { return; } diff --git a/js/src/redux/providers/signerMiddleware.js b/js/src/redux/providers/signerMiddleware.js index 53144e454..6d09eeb4e 100644 --- a/js/src/redux/providers/signerMiddleware.js +++ b/js/src/redux/providers/signerMiddleware.js @@ -51,7 +51,7 @@ export default class SignerMiddleware { onConfirmStart = (store, action) => { const { id, password } = action.payload; - this._api.personal + this._api.signer .confirmRequest(id, {}, password) .then((txHash) => { console.log('confirmRequest', id, txHash); @@ -71,7 +71,7 @@ export default class SignerMiddleware { onRejectStart = (store, action) => { const id = action.payload; - this._api.personal + this._api.signer .rejectRequest(id) .then(() => { store.dispatch(actions.successRejectRequest({ id })); diff --git a/js/src/redux/providers/status.js b/js/src/redux/providers/status.js index 21712f2a9..0e2bf6866 100644 --- a/js/src/redux/providers/status.js +++ b/js/src/redux/providers/status.js @@ -31,8 +31,8 @@ export default class Status { } _fetchEnode () { - this._api - .ethcore.enode() + this._api.parity + .enode() .then((enode) => { this._store.dispatch(statusCollection({ enode })); }) @@ -101,16 +101,16 @@ export default class Status { .all([ this._api.web3.clientVersion(), this._api.eth.coinbase(), - this._api.ethcore.defaultExtraData(), - this._api.ethcore.extraData(), - this._api.ethcore.gasFloorTarget(), + this._api.parity.defaultExtraData(), + this._api.parity.extraData(), + this._api.parity.gasFloorTarget(), this._api.eth.hashrate(), - this._api.ethcore.minGasPrice(), - this._api.ethcore.netChain(), - this._api.ethcore.netPeers(), - this._api.ethcore.netPort(), - this._api.ethcore.nodeName(), - this._api.ethcore.rpcSettings(), + this._api.parity.minGasPrice(), + this._api.parity.netChain(), + this._api.parity.netPeers(), + this._api.parity.netPort(), + this._api.parity.nodeName(), + this._api.parity.rpcSettings(), this._api.eth.syncing(), this._pollTraceMode() ]) @@ -153,8 +153,8 @@ export default class Status { Promise .all([ - this._api.ethcore.devLogs(), - this._api.ethcore.devLogsLevels() + this._api.parity.devLogs(), + this._api.parity.devLogsLevels() ]) .then(([devLogs, devLogsLevels]) => { this._store.dispatch(statusLogs({ diff --git a/js/src/secureApi.js b/js/src/secureApi.js index d577e7185..2f03d62e9 100644 --- a/js/src/secureApi.js +++ b/js/src/secureApi.js @@ -62,7 +62,7 @@ export default class SecureApi extends Api { case 1: if (isConnected) { this._connectState = 2; - this.personal + this.parity .generateAuthorizationToken() .then((token) => { this.updateToken(token, 2); @@ -96,8 +96,8 @@ export default class SecureApi extends Api { Promise .all([ - this.ethcore.dappsPort(), - this.ethcore.signerPort() + this.parity.dappsPort(), + this.parity.signerPort() ]) .then(([dappsPort, signerPort]) => { this._dappsPort = dappsPort.toNumber(); diff --git a/js/src/views/Account/Header/header.js b/js/src/views/Account/Header/header.js index 2e7d5ae77..d4b01cf56 100644 --- a/js/src/views/Account/Header/header.js +++ b/js/src/views/Account/Header/header.js @@ -149,7 +149,7 @@ export default class Header extends Component { const { account } = this.props; this.setState({ name }, () => { - api.personal + api.parity .setAccountName(account.address, name) .catch((error) => { console.error(error); diff --git a/js/src/views/Address/Delete/delete.js b/js/src/views/Address/Delete/delete.js index 66e6edb95..8aeaf48e2 100644 --- a/js/src/views/Address/Delete/delete.js +++ b/js/src/views/Address/Delete/delete.js @@ -81,7 +81,7 @@ class Delete extends Component { account.meta.deleted = true; - api.personal + api.parity .setAccountMeta(account.address, account.meta) .then(() => { router.push(route); diff --git a/js/src/views/Application/application.js b/js/src/views/Application/application.js index 6b38f90d8..d49aa5d90 100644 --- a/js/src/views/Application/application.js +++ b/js/src/views/Application/application.js @@ -104,8 +104,8 @@ class Application extends Component { checkAccounts () { const { api } = this.context; - api.personal - .listAccounts() + api.eth + .accounts() .then((accounts) => { this.setState({ showFirstRun: showFirstRun || accounts.length === 0 diff --git a/js/src/views/Dapp/dapp.js b/js/src/views/Dapp/dapp.js index b5d396735..7f5f36d1d 100644 --- a/js/src/views/Dapp/dapp.js +++ b/js/src/views/Dapp/dapp.js @@ -16,6 +16,9 @@ import React, { Component, PropTypes } from 'react'; +import Contracts from '../../contracts'; +import { fetchAvailable } from '../Dapps/registry'; + import styles from './dapp.css'; export default class Dapp extends Component { @@ -27,16 +30,39 @@ export default class Dapp extends Component { params: PropTypes.object }; + state = { + app: null + } + + componentWillMount () { + this.lookup(); + } + render () { - const { name, type } = this.props.params; + const { app } = this.state; const { dappsUrl } = this.context.api; - let src = `${dappsUrl}/${name}/`; - if (type === 'builtin') { - const dapphost = process.env.NODE_ENV === 'production' - ? `${dappsUrl}/ui` - : ''; - src = `${dapphost}/${name}.html`; + if (!app) { + return null; + } + + let src = null; + switch (app.type) { + case 'builtin': + const dapphost = process.env.NODE_ENV === 'production' && !app.secure + ? `${dappsUrl}/ui` + : ''; + src = `${dapphost}/${app.url}.html`; + break; + case 'local': + src = `${dappsUrl}/${app.id}/`; + break; + case 'network': + src = `${dappsUrl}/${app.contentHash}/`; + break; + default: + console.error('unknown type', app.type); + break; } return ( @@ -50,4 +76,30 @@ export default class Dapp extends Component { ); } + + lookup () { + const { api } = this.context; + const { id } = this.props.params; + const { dappReg } = Contracts.get(); + + fetchAvailable(api) + .then((available) => { + return available.find((app) => app.id === id); + }) + .then((app) => { + if (app.type !== 'network') { + return app; + } + + return dappReg + .getContent(app.id) + .then((contentHash) => { + app.contentHash = api.util.bytesToHex(contentHash).substr(2); + return app; + }); + }) + .then((app) => { + this.setState({ app }); + }); + } } diff --git a/js/src/views/Dapps/Summary/summary.js b/js/src/views/Dapps/Summary/summary.js index b4d2bb4cb..1140bb7b9 100644 --- a/js/src/views/Dapps/Summary/summary.js +++ b/js/src/views/Dapps/Summary/summary.js @@ -39,16 +39,7 @@ export default class Summary extends Component { return null; } - let type = 'builtin'; - if (app.network) { - type = 'network'; - } else if (app.local) { - type = 'local'; - } - - const url = `/app/${type}/${app.url || app.contentHash || app.id}`; let image =
 
; - if (app.image) { image = ; } else if (app.iconUrl) { @@ -61,7 +52,7 @@ export default class Summary extends Component {
{ app.name } } + title={ { app.name } } byline={ app.description } />
{ app.author }, v{ app.version }
{ this.props.children } diff --git a/js/src/views/Dapps/registry.js b/js/src/views/Dapps/registry.js index 8975f2091..5502bd3ff 100644 --- a/js/src/views/Dapps/registry.js +++ b/js/src/views/Dapps/registry.js @@ -55,7 +55,8 @@ const builtinApps = [ name: 'GitHub Hint', description: 'A mapping of GitHub URLs to hashes for use in contracts as references', author: 'Parity Team ', - version: '1.0.0' + version: '1.0.0', + secure: true } ]; @@ -81,13 +82,6 @@ function getHost (api) { } export function fetchAvailable (api) { - // TODO: Since we don't have an extensive GithubHint app, get the value somehow - // RESULT: 0x22cd66e1b05882c0fa17a16d252d3b3ee2238ccbac8153f69a35c83f02ca76ee - // api.ethcore - // .hashContent('https://codeload.github.com/gavofyork/gavcoin/zip/5a9f11ff2ad0d05c565a938ceffdfa0d23af9981') - // .then((sha3) => { - // console.log('archive', sha3); - // }); return fetch(`${getHost(api)}/api/apps`) .then((response) => { return response.ok @@ -102,11 +96,11 @@ export function fetchAvailable (api) { const localApps = _localApps .filter((app) => !['ui'].includes(app.id)) .map((app) => { - app.local = true; + app.type = 'local'; return app; }); - return api.ethcore + return api.parity .registryAddress() .then((registryAddress) => { if (new BigNumber(registryAddress).eq(0)) { @@ -115,13 +109,13 @@ export function fetchAvailable (api) { const _builtinApps = builtinApps .map((app) => { - app.builtin = true; + app.type = 'builtin'; return app; }); return networkApps .map((app) => { - app.network = true; + app.type = 'network'; return app; }) .concat(_builtinApps); diff --git a/js/src/views/Settings/Parity/parity.js b/js/src/views/Settings/Parity/parity.js index 6a3ba631f..abec8cc8a 100644 --- a/js/src/views/Settings/Parity/parity.js +++ b/js/src/views/Settings/Parity/parity.js @@ -89,7 +89,7 @@ export default class Parity extends Component { onChangeMode = (event, index, mode) => { const { api } = this.context; - api.ethcore + api.parity .setMode(mode) .then((result) => { if (result) { @@ -104,7 +104,7 @@ export default class Parity extends Component { loadMode () { const { api } = this.context; - api.ethcore + api.parity .mode() .then((mode) => { this.setState({ mode }); diff --git a/js/src/views/Signer/components/SignRequest/SignRequest.js b/js/src/views/Signer/components/SignRequest/SignRequest.js index 25b3dd77d..395bc2c7f 100644 --- a/js/src/views/Signer/components/SignRequest/SignRequest.js +++ b/js/src/views/Signer/components/SignRequest/SignRequest.js @@ -50,7 +50,7 @@ export default class SignRequest extends Component { } componentWillMount () { - this.context.api.ethcore.netChain() + this.context.api.parity.netChain() .then((chain) => { this.setState({ chain }); }) diff --git a/js/src/views/Signer/components/TransactionFinished/TransactionFinished.js b/js/src/views/Signer/components/TransactionFinished/TransactionFinished.js index b781adb6d..08ed1f7ed 100644 --- a/js/src/views/Signer/components/TransactionFinished/TransactionFinished.js +++ b/js/src/views/Signer/components/TransactionFinished/TransactionFinished.js @@ -65,7 +65,7 @@ export default class TransactionFinished extends Component { const totalValue = tUtil.getTotalValue(fee, value); this.setState({ totalValue }); - this.context.api.ethcore.netChain() + this.context.api.parity.netChain() .then((chain) => { this.setState({ chain }); }) diff --git a/js/src/views/Signer/components/TransactionPending/TransactionPending.js b/js/src/views/Signer/components/TransactionPending/TransactionPending.js index 3ca078b83..77d02d0b1 100644 --- a/js/src/views/Signer/components/TransactionPending/TransactionPending.js +++ b/js/src/views/Signer/components/TransactionPending/TransactionPending.js @@ -64,7 +64,7 @@ export default class TransactionPending extends Component { const gasToDisplay = tUtil.getGasDisplay(gas); this.setState({ gasPriceEthmDisplay, totalValue, gasToDisplay }); - this.context.api.ethcore.netChain() + this.context.api.parity.netChain() .then((chain) => { this.setState({ chain }); }) diff --git a/js/src/views/Status/components/MiningSettings/MiningSettings.js b/js/src/views/Status/components/MiningSettings/MiningSettings.js index 086d54be1..163c103f5 100644 --- a/js/src/views/Status/components/MiningSettings/MiningSettings.js +++ b/js/src/views/Status/components/MiningSettings/MiningSettings.js @@ -87,7 +87,7 @@ export default class MiningSettings extends Component { onMinGasPriceChange = (newVal) => { const { api } = this.context; - api.ethcore.setMinGasPrice(numberFromString(newVal)); + api.parity.setMinGasPrice(numberFromString(newVal)); }; onExtraDataChange = (newVal, isResetToDefault) => { @@ -97,18 +97,18 @@ export default class MiningSettings extends Component { // In case of resetting to default we are just using raw bytes from defaultExtraData // When user sets new value we can safely send a string that will be converted to hex by formatter. const val = isResetToDefault ? nodeStatus.defaultExtraData : newVal; - api.ethcore.setExtraData(val); + api.parity.setExtraData(val); }; onAuthorChange = (newVal) => { const { api } = this.context; - api.ethcore.setAuthor(newVal); + api.parity.setAuthor(newVal); }; onGasFloorTargetChange = (newVal) => { const { api } = this.context; - api.ethcore.setGasFloorTarget(numberFromString(newVal)); + api.parity.setGasFloorTarget(numberFromString(newVal)); }; } diff --git a/parity/cli/config.full.toml b/parity/cli/config.full.toml index 520adef4b..dcba8dbee 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", "personal_safe"] +apis = ["web3", "eth", "net", "parity", "traces", "rpc"] hosts = ["none"] [ipc] disable = false path = "$HOME/.parity/jsonrpc.ipc" -apis = ["web3", "eth", "net", "ethcore", "traces", "rpc", "personal", "personal_safe"] +apis = ["web3", "eth", "net", "parity", "parity_accounts", "personal", "traces", "rpc"] [dapps] disable = false diff --git a/parity/cli/mod.rs b/parity/cli/mod.rs index ad9e8dd8b..12536e444 100644 --- a/parity/cli/mod.rs +++ b/parity/cli/mod.rs @@ -143,7 +143,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,personal_safe", + flag_jsonrpc_apis: String = "web3,eth,net,parity,traces,rpc", 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(",")), @@ -153,7 +153,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,personal,personal_safe", + flag_ipc_apis: String = "web3,eth,net,parity,parity_accounts,traces,rpc", or |c: &Config| otry!(c.ipc).apis.clone().map(|vec| vec.join(",")), // DAPPS @@ -540,13 +540,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,personal_safe".into(), + flag_jsonrpc_apis: "web3,eth,net,parity,traces,rpc".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,personal,personal_safe".into(), + flag_ipc_apis: "web3,eth,net,parity,parity_accounts,personal,traces,rpc".into(), // DAPPS flag_no_dapps: false, diff --git a/parity/cli/usage.txt b/parity/cli/usage.txt index b8734f7c2..8f453d874 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, personal_safe. + parity, parity_set, traces, rpc, parity_accounts. (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 @@ -284,7 +284,7 @@ Legacy Options: --geth Run in Geth-compatibility mode. Sets the IPC path to be the same as Geth's. Overrides the --ipc-path and --ipcpath options. Alters RPCs to reflect Geth - bugs. + bugs. Includes the personal_ RPC by default. --testnet Geth-compatible testnet mode. Equivalent to --chain testnet --keys-path $HOME/parity/testnet-keys. Overrides the --keys-path option. diff --git a/parity/configuration.rs b/parity/configuration.rs index b71404339..0617e76f3 100644 --- a/parity/configuration.rs +++ b/parity/configuration.rs @@ -509,7 +509,11 @@ impl Configuration { } fn rpc_apis(&self) -> String { - self.args.flag_rpcapi.clone().unwrap_or(self.args.flag_jsonrpc_apis.clone()) + let mut apis = self.args.flag_rpcapi.clone().unwrap_or(self.args.flag_jsonrpc_apis.clone()); + if self.args.flag_geth { + apis.push_str(",personal"); + } + apis } fn rpc_cors(&self) -> Option> { @@ -541,7 +545,13 @@ impl Configuration { let conf = IpcConfiguration { enabled: !(self.args.flag_ipcdisable || self.args.flag_ipc_off || self.args.flag_no_ipc), socket_addr: self.ipc_path(), - apis: try!(self.args.flag_ipcapi.clone().unwrap_or(self.args.flag_ipc_apis.clone()).parse()), + apis: { + let mut apis = self.args.flag_ipcapi.clone().unwrap_or(self.args.flag_ipc_apis.clone()); + if self.args.flag_geth { + apis.push_str("personal"); + } + try!(apis.parse()) + }, }; Ok(conf) diff --git a/parity/rpc_apis.rs b/parity/rpc_apis.rs index d725b9b08..9ffd8e0dd 100644 --- a/parity/rpc_apis.rs +++ b/parity/rpc_apis.rs @@ -31,15 +31,25 @@ pub use ethcore_rpc::SignerService; #[derive(Debug, PartialEq, Clone, Eq, Hash)] pub enum Api { + /// Web3 (Safe) Web3, + /// Net (Safe) Net, + /// Eth (Safe) Eth, - PersonalSafe, - PersonalAccounts, + /// Geth-compatible "personal" API (DEPRECATED; only used in `--geth` mode.) + Personal, + /// Signer - Confirm transactions in Signer (UNSAFE: Passwords, List of transactions) Signer, - Ethcore, - EthcoreSet, + /// Parity - Custom extensions (Safe) + Parity, + /// Parity Accounts extensions (UNSAFE: Passwords, Side Effects (new account)) + ParityAccounts, + /// Parity - Set methods (UNSAFE: Side Effects affecting node operation) + ParitySet, + /// Traces (Safe) Traces, + /// Rpc (Safe) Rpc, } @@ -53,11 +63,11 @@ impl FromStr for Api { "web3" => Ok(Web3), "net" => Ok(Net), "eth" => Ok(Eth), - "personal" => Ok(PersonalAccounts), - "personal_safe" => Ok(PersonalSafe), + "personal" => Ok(Personal), "signer" => Ok(Signer), - "ethcore" => Ok(Ethcore), - "ethcore_set" => Ok(EthcoreSet), + "parity" => Ok(Parity), + "parity_accounts" => Ok(ParityAccounts), + "parity_set" => Ok(ParitySet), "traces" => Ok(Traces), "rpc" => Ok(Rpc), api => Err(format!("Unknown api: {}", api)) @@ -119,11 +129,11 @@ fn to_modules(apis: &[Api]) -> BTreeMap { Api::Web3 => ("web3", "1.0"), Api::Net => ("net", "1.0"), Api::Eth => ("eth", "1.0"), - Api::PersonalSafe => ("personal_safe", "1.0"), - Api::PersonalAccounts => ("personal", "1.0"), + Api::Personal => ("personal", "1.0"), Api::Signer => ("signer", "1.0"), - Api::Ethcore => ("ethcore", "1.0"), - Api::EthcoreSet => ("ethcore_set", "1.0"), + Api::Parity => ("parity", "1.0"), + Api::ParityAccounts => ("parity_accounts", "1.0"), + Api::ParitySet => ("parity_set", "1.0"), Api::Traces => ("traces", "1.0"), Api::Rpc => ("rpc", "1.0"), }; @@ -134,24 +144,37 @@ fn to_modules(apis: &[Api]) -> BTreeMap { impl ApiSet { pub fn list_apis(&self) -> HashSet { + let mut safe_list = vec![Api::Web3, Api::Net, Api::Eth, Api::Parity, Api::Traces, Api::Rpc] + .into_iter().collect(); match *self { ApiSet::List(ref apis) => apis.clone(), - ApiSet::UnsafeContext => { - vec![Api::Web3, Api::Net, Api::Eth, Api::Ethcore, Api::Traces, Api::Rpc, Api::PersonalSafe] - .into_iter().collect() - }, + ApiSet::UnsafeContext => safe_list, ApiSet::IpcContext => { - vec![Api::Web3, Api::Net, Api::Eth, Api::Ethcore, Api::Traces, Api::Rpc, Api::PersonalAccounts, Api::PersonalSafe] - .into_iter().collect() + safe_list.insert(Api::ParityAccounts); + safe_list }, ApiSet::SafeContext => { - vec![Api::Web3, Api::Net, Api::Eth, Api::PersonalAccounts, Api::PersonalSafe, Api::Signer, Api::Ethcore, Api::EthcoreSet, Api::Traces, Api::Rpc] - .into_iter().collect() + safe_list.insert(Api::ParityAccounts); + safe_list.insert(Api::ParitySet); + safe_list.insert(Api::Signer); + safe_list }, } } } +macro_rules! add_signing_methods { + ($namespace:ident, $server:expr, $deps:expr) => { + let server = &$server; + let deps = &$deps; + if deps.signer_service.is_enabled() { + server.add_delegate($namespace::to_delegate(SigningQueueClient::new(&deps.signer_service, &deps.client, &deps.miner, &deps.secret_store))) + } else { + server.add_delegate($namespace::to_delegate(SigningUnsafeClient::new(&deps.client, &deps.secret_store, &deps.miner))) + } + } +} + pub fn setup_rpc(server: T, deps: Arc, apis: ApiSet) -> T { use ethcore_rpc::v1::*; @@ -183,39 +206,39 @@ pub fn setup_rpc(server: T, deps: Arc, apis: ApiSet let filter_client = EthFilterClient::new(&deps.client, &deps.miner); server.add_delegate(filter_client.to_delegate()); - if deps.signer_service.is_enabled() { - server.add_delegate(EthSigningQueueClient::new(&deps.signer_service, &deps.client, &deps.miner, &deps.secret_store).to_delegate()); - } else { - server.add_delegate(EthSigningUnsafeClient::new(&deps.client, &deps.secret_store, &deps.miner).to_delegate()); - } + add_signing_methods!(EthSigning, server, deps); }, - 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::Personal => { + server.add_delegate(PersonalClient::new(&deps.secret_store, &deps.client, &deps.miner, deps.geth_compatibility).to_delegate()); }, Api::Signer => { server.add_delegate(SignerClient::new(&deps.secret_store, &deps.client, &deps.miner, &deps.signer_service).to_delegate()); }, - Api::Ethcore => { + Api::Parity => { let signer = match deps.signer_service.is_enabled() { true => Some(deps.signer_service.clone()), false => None, }; - server.add_delegate(EthcoreClient::new( + server.add_delegate(ParityClient::new( &deps.client, &deps.miner, &deps.sync, &deps.net_service, + &deps.secret_store, deps.logger.clone(), deps.settings.clone(), signer, deps.dapps_port, - ).to_delegate()) + ).to_delegate()); + + add_signing_methods!(EthSigning, server, deps); + add_signing_methods!(ParitySigning, server, deps); }, - Api::EthcoreSet => { - server.add_delegate(EthcoreSetClient::new(&deps.client, &deps.miner, &deps.net_service).to_delegate()) + Api::ParityAccounts => { + server.add_delegate(ParityAccountsClient::new(&deps.secret_store, &deps.client).to_delegate()); + }, + Api::ParitySet => { + server.add_delegate(ParitySetClient::new(&deps.client, &deps.miner, &deps.net_service).to_delegate()) }, Api::Traces => { server.add_delegate(TracesClient::new(&deps.client, &deps.miner).to_delegate()) @@ -238,11 +261,11 @@ 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::PersonalAccounts, "personal".parse().unwrap()); - assert_eq!(Api::PersonalSafe, "personal_safe".parse().unwrap()); + assert_eq!(Api::Personal, "personal".parse().unwrap()); assert_eq!(Api::Signer, "signer".parse().unwrap()); - assert_eq!(Api::Ethcore, "ethcore".parse().unwrap()); - assert_eq!(Api::EthcoreSet, "ethcore_set".parse().unwrap()); + assert_eq!(Api::Parity, "parity".parse().unwrap()); + assert_eq!(Api::ParityAccounts, "parity_accounts".parse().unwrap()); + assert_eq!(Api::ParitySet, "parity_set".parse().unwrap()); assert_eq!(Api::Traces, "traces".parse().unwrap()); assert_eq!(Api::Rpc, "rpc".parse().unwrap()); assert!("rp".parse::().is_err()); @@ -260,15 +283,34 @@ mod test { #[test] fn test_api_set_unsafe_context() { - let expected = vec![Api::Web3, Api::Net, Api::Eth, Api::Ethcore, Api::Traces, Api::Rpc, Api::PersonalSafe] - .into_iter().collect(); + let expected = vec![ + // make sure this list contains only SAFE methods + Api::Web3, Api::Net, Api::Eth, Api::Parity, Api::Traces, Api::Rpc + ].into_iter().collect(); assert_eq!(ApiSet::UnsafeContext.list_apis(), expected); } + #[test] + fn test_api_set_ipc_context() { + let expected = vec![ + // safe + Api::Web3, Api::Net, Api::Eth, Api::Parity, Api::Traces, Api::Rpc, + // semi-safe + Api::ParityAccounts + ].into_iter().collect(); + assert_eq!(ApiSet::IpcContext.list_apis(), expected); + } + #[test] fn test_api_set_safe_context() { - 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(); + let expected = vec![ + // safe + Api::Web3, Api::Net, Api::Eth, Api::Parity, Api::Traces, Api::Rpc, + // semi-safe + Api::ParityAccounts, + // Unsafe + Api::ParitySet, Api::Signer, + ].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 c108f0b6b..e4083ca95 100644 --- a/rpc/src/v1/impls/mod.rs +++ b/rpc/src/v1/impls/mod.rs @@ -27,13 +27,14 @@ macro_rules! take_weak { mod eth; mod eth_filter; -mod eth_signing; -mod ethcore; -mod ethcore_set; mod net; +mod parity; +mod parity_accounts; +mod parity_set; mod personal; -mod personal_accounts; -mod personal_signer; +mod signer; +mod signing; +mod signing_unsafe; mod rpc; mod traces; mod web3; @@ -41,12 +42,13 @@ mod web3; pub use self::web3::Web3Client; pub use self::eth::{EthClient, EthClientOptions}; pub use self::eth_filter::EthFilterClient; -pub use self::eth_signing::{EthSigningUnsafeClient, EthSigningQueueClient}; pub use self::net::NetClient; +pub use self::parity::ParityClient; +pub use self::parity_accounts::ParityAccountsClient; +pub use self::parity_set::ParitySetClient; 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; +pub use self::signer::SignerClient; +pub use self::signing::SigningQueueClient; +pub use self::signing_unsafe::SigningUnsafeClient; pub use self::traces::TracesClient; pub use self::rpc::RpcClient; diff --git a/rpc/src/v1/impls/ethcore.rs b/rpc/src/v1/impls/parity.rs similarity index 74% rename from rpc/src/v1/impls/ethcore.rs rename to rpc/src/v1/impls/parity.rs index 84d159f80..b9c19f667 100644 --- a/rpc/src/v1/impls/ethcore.rs +++ b/rpc/src/v1/impls/parity.rs @@ -14,16 +14,15 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -//! Ethcore-specific rpc implementation. -use std::{fs, io}; -use std::sync::{mpsc, Arc, Weak}; +//! Parity-specific rpc implementation. +use std::sync::{Arc, Weak}; use std::str::FromStr; +use std::collections::BTreeMap; -use util::{RotatingLogger, Address, Mutex, sha3}; +use util::{RotatingLogger, Address}; use util::misc::version_data; use crypto::ecies; -use fetch::{Client as FetchClient, Fetch}; use ethkey::{Brain, Generator}; use ethstore::random_phrase; use ethsync::{SyncProvider, ManageNetwork}; @@ -31,77 +30,57 @@ use ethcore::miner::MinerService; use ethcore::client::{MiningBlockChainClient}; use ethcore::ids::BlockID; use ethcore::mode::Mode; +use ethcore::account_provider::AccountProvider; use jsonrpc_core::Error; -use v1::traits::Ethcore; +use v1::traits::Parity; use v1::types::{Bytes, U256, H160, H256, H512, Peers, Transaction, RpcSettings, Histogram}; use v1::helpers::{errors, SigningQueue, SignerService, NetworkSettings}; use v1::helpers::dispatch::DEFAULT_MAC; -use v1::helpers::auto_args::Ready; -/// Ethcore implementation. -pub struct EthcoreClient where +/// Parity implementation. +pub struct ParityClient where C: MiningBlockChainClient, M: MinerService, S: SyncProvider, - F: Fetch { - +{ client: Weak, miner: Weak, sync: Weak, net: Weak, + accounts: Weak, logger: Arc, settings: Arc, signer: Option>, - fetch: Mutex, dapps_port: Option, } -impl EthcoreClient where +impl ParityClient where C: MiningBlockChainClient, M: MinerService, - S: SyncProvider, { - /// Creates new `EthcoreClient` with default `Fetch`. + S: SyncProvider, +{ + /// Creates new `ParityClient`. pub fn new( client: &Arc, miner: &Arc, sync: &Arc, net: &Arc, + store: &Arc, logger: Arc, settings: Arc, signer: Option>, dapps_port: Option, ) -> Self { - Self::with_fetch(client, miner, sync, net, logger, settings, signer, dapps_port) - } -} - -impl EthcoreClient where - C: MiningBlockChainClient, - M: MinerService, - S: SyncProvider, - F: Fetch, { - - /// Creates new `EthcoreClient` with customizable `Fetch`. - pub fn with_fetch( - client: &Arc, - miner: &Arc, - sync: &Arc, - net: &Arc, - logger: Arc, - settings: Arc, - signer: Option>, - dapps_port: Option, - ) -> Self { - EthcoreClient { + ParityClient { client: Arc::downgrade(client), miner: Arc::downgrade(miner), sync: Arc::downgrade(sync), net: Arc::downgrade(net), + accounts: Arc::downgrade(store), logger: logger, settings: settings, signer: signer, - fetch: Mutex::new(F::default()), dapps_port: dapps_port, } } @@ -113,11 +92,10 @@ impl EthcoreClient where } } -impl Ethcore for EthcoreClient where +impl Parity for ParityClient where M: MinerService + 'static, C: MiningBlockChainClient + 'static, - S: SyncProvider + 'static, - F: Fetch + 'static { + S: SyncProvider + 'static { fn transactions_limit(&self) -> Result { try!(self.active()); @@ -278,48 +256,6 @@ impl Ethcore for EthcoreClient where Ok(take_weak!(self.miner).all_transactions().into_iter().map(Into::into).collect::>()) } - fn hash_content(&self, ready: Ready, url: String) { - let res = self.active(); - - let hash_content = |result| { - let path = try!(result); - let mut file = io::BufReader::new(try!(fs::File::open(&path))); - // Try to hash - let result = sha3(&mut file); - // Remove file (always) - try!(fs::remove_file(&path)); - // Return the result - Ok(try!(result)) - }; - - match res { - Err(e) => ready.ready(Err(e)), - Ok(()) => { - let (tx, rx) = mpsc::channel(); - let res = self.fetch.lock().request_async(&url, Default::default(), Box::new(move |result| { - let result = hash_content(result) - .map_err(errors::from_fetch_error) - .map(Into::into); - - // Receive ready and invoke with result. - let ready: Ready = rx.recv().expect( - "recv() fails when `tx` has been dropped, if this closure is invoked `tx` is not dropped (`res == Ok()`); qed" - ); - ready.ready(result); - })); - - // Either invoke ready right away or transfer it to the closure. - if let Err(e) = res { - ready.ready(Err(errors::from_fetch_error(e))); - } else { - tx.send(ready).expect( - "send() fails when `rx` end is dropped, if `res == Ok()`: `rx` is moved to the closure; qed" - ); - } - } - } - } - fn signer_port(&self) -> Result { try!(self.active()); @@ -361,4 +297,22 @@ impl Ethcore for EthcoreClient where fn enode(&self) -> Result { take_weak!(self.sync).enode().ok_or_else(errors::network_disabled) } + + fn accounts(&self) -> Result>, Error> { + try!(self.active()); + let store = take_weak!(self.accounts); + let info = try!(store.accounts_info().map_err(|e| errors::account("Could not fetch account info.", e))); + let other = store.addresses_info().expect("addresses_info always returns Ok; qed"); + + Ok(info.into_iter().chain(other.into_iter()).map(|(a, v)| { + let mut m = map![ + "name".to_owned() => v.name, + "meta".to_owned() => v.meta + ]; + if let &Some(ref uuid) = &v.uuid { + m.insert("uuid".to_owned(), format!("{}", uuid)); + } + (format!("0x{}", a.hex()), m) + }).collect()) + } } diff --git a/rpc/src/v1/impls/personal_accounts.rs b/rpc/src/v1/impls/parity_accounts.rs similarity index 68% rename from rpc/src/v1/impls/personal_accounts.rs rename to rpc/src/v1/impls/parity_accounts.rs index 56c92fc64..2644c59e3 100644 --- a/rpc/src/v1/impls/personal_accounts.rs +++ b/rpc/src/v1/impls/parity_accounts.rs @@ -16,33 +16,30 @@ //! 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::PersonalAccounts; -use v1::types::{H160 as RpcH160, H256 as RpcH256, TransactionRequest}; -use v1::helpers::errors; -use v1::helpers::dispatch::sign_and_dispatch; use ethcore::account_provider::AccountProvider; use ethcore::client::MiningBlockChainClient; -use ethcore::miner::MinerService; + +use jsonrpc_core::{Value, Error, to_value}; +use v1::traits::ParityAccounts; +use v1::types::{H160 as RpcH160, H256 as RpcH256}; +use v1::helpers::errors; /// Account management (personal) rpc implementation. -pub struct PersonalAccountsClient where C: MiningBlockChainClient, M: MinerService { +pub struct ParityAccountsClient where C: MiningBlockChainClient { accounts: Weak, client: Weak, - miner: Weak, - allow_perm_unlock: bool, } -impl PersonalAccountsClient where C: MiningBlockChainClient, M: MinerService { +impl ParityAccountsClient where C: MiningBlockChainClient { /// Creates new PersonalClient - pub fn new(store: &Arc, client: &Arc, miner: &Arc, allow_perm_unlock: bool) -> Self { - PersonalAccountsClient { + pub fn new(store: &Arc, client: &Arc) -> Self { + ParityAccountsClient { accounts: Arc::downgrade(store), client: Arc::downgrade(client), - miner: Arc::downgrade(miner), - allow_perm_unlock: allow_perm_unlock, } } @@ -53,15 +50,25 @@ impl PersonalAccountsClient where C: MiningBlockChainClient, M: Mine } } -impl PersonalAccounts for PersonalAccountsClient where C: MiningBlockChainClient, M: MinerService { - - fn new_account(&self, pass: String) -> Result { +impl ParityAccounts for ParityAccountsClient where C: MiningBlockChainClient { + fn accounts_info(&self) -> Result, Error> { try!(self.active()); let store = take_weak!(self.accounts); + let info = try!(store.accounts_info().map_err(|e| errors::account("Could not fetch account info.", e))); + let other = store.addresses_info().expect("addresses_info always returns Ok; qed"); - store.new_account(&pass) - .map(Into::into) - .map_err(|e| errors::account("Could not create account.", e)) + Ok(info.into_iter().chain(other.into_iter()).map(|(a, v)| { + let m = map![ + "name".to_owned() => to_value(&v.name), + "meta".to_owned() => to_value(&v.meta), + "uuid".to_owned() => if let &Some(ref uuid) = &v.uuid { + to_value(uuid) + } else { + Value::Null + } + ]; + (format!("0x{}", a.hex()), Value::Object(m)) + }).collect()) } fn new_account_from_phrase(&self, phrase: String, pass: String) -> Result { @@ -92,24 +99,6 @@ impl PersonalAccounts for PersonalAccountsClient w .map_err(|e| errors::account("Could not create account.", e)) } - fn unlock_account(&self, account: RpcH160, account_pass: String, duration: Option) -> Result { - try!(self.active()); - 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(true), - // TODO [ToDr] Proper error here? - Err(_) => Ok(false), - } - } - fn test_password(&self, account: RpcH160, password: String) -> Result { try!(self.active()); let account: Address = account.into(); @@ -128,18 +117,6 @@ impl PersonalAccounts for PersonalAccountsClient w .map_err(|e| errors::account("Could not fetch account info.", e)) } - fn sign_and_send_transaction(&self, request: TransactionRequest, password: String) -> Result { - try!(self.active()); - - sign_and_dispatch( - &*take_weak!(self.client), - &*take_weak!(self.miner), - &*take_weak!(self.accounts), - request.into(), - Some(password) - ) - } - fn set_account_name(&self, addr: RpcH160, name: String) -> Result { try!(self.active()); let store = take_weak!(self.accounts); @@ -162,6 +139,10 @@ impl PersonalAccounts for PersonalAccountsClient w Ok(true) } + fn set_account_visibility(&self, _address: RpcH160, _dapp: RpcH256, _visible: bool) -> Result { + Ok(false) + } + fn import_geth_accounts(&self, addresses: Vec) -> Result, Error> { let store = take_weak!(self.accounts); diff --git a/rpc/src/v1/impls/ethcore_set.rs b/rpc/src/v1/impls/parity_set.rs similarity index 62% rename from rpc/src/v1/impls/ethcore_set.rs rename to rpc/src/v1/impls/parity_set.rs index 8889ffa44..47634d518 100644 --- a/rpc/src/v1/impls/ethcore_set.rs +++ b/rpc/src/v1/impls/parity_set.rs @@ -14,36 +14,57 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -/// Ethcore-specific rpc interface for operations altering the settings. -use std::sync::{Arc, Weak}; -use jsonrpc_core::*; +/// Parity-specific rpc interface for operations altering the settings. +use std::{fs, io}; +use std::sync::{Arc, Weak, mpsc}; + use ethcore::miner::MinerService; use ethcore::client::MiningBlockChainClient; use ethcore::mode::Mode; use ethsync::ManageNetwork; -use v1::helpers::errors; -use v1::traits::EthcoreSet; -use v1::types::{Bytes, H160, U256}; +use fetch::{Client as FetchClient, Fetch}; +use util::{Mutex, sha3}; -/// Ethcore-specific rpc interface for operations altering the settings. -pub struct EthcoreSetClient where +use jsonrpc_core::Error; +use v1::helpers::auto_args::Ready; +use v1::helpers::errors; +use v1::traits::ParitySet; +use v1::types::{Bytes, H160, H256, U256}; + +/// Parity-specific rpc interface for operations altering the settings. +pub struct ParitySetClient where C: MiningBlockChainClient, - M: MinerService + M: MinerService, + F: Fetch, { client: Weak, miner: Weak, net: Weak, + fetch: Mutex, } -impl EthcoreSetClient where +impl ParitySetClient where C: MiningBlockChainClient, - M: MinerService { - /// Creates new `EthcoreSetClient`. + M: MinerService +{ + /// Creates new `ParitySetClient` with default `FetchClient`. pub fn new(client: &Arc, miner: &Arc, net: &Arc) -> Self { - EthcoreSetClient { + Self::with_fetch(client, miner, net) + } +} + +impl ParitySetClient where + C: MiningBlockChainClient, + M: MinerService, + F: Fetch, +{ + /// Creates new `ParitySetClient` with default `FetchClient`. + pub fn with_fetch(client: &Arc, miner: &Arc, net: &Arc) -> Self { + ParitySetClient { client: Arc::downgrade(client), miner: Arc::downgrade(miner), net: Arc::downgrade(net), + fetch: Mutex::new(F::default()), } } @@ -54,9 +75,11 @@ impl EthcoreSetClient where } } -impl EthcoreSet for EthcoreSetClient where +impl ParitySet for ParitySetClient where C: MiningBlockChainClient + 'static, - M: MinerService + 'static { + M: MinerService + 'static, + F: Fetch + 'static, +{ fn set_min_gas_price(&self, gas_price: U256) -> Result { try!(self.active()); @@ -159,4 +182,46 @@ impl EthcoreSet for EthcoreSetClient where }); Ok(true) } + + fn hash_content(&self, ready: Ready, url: String) { + let res = self.active(); + + let hash_content = |result| { + let path = try!(result); + let mut file = io::BufReader::new(try!(fs::File::open(&path))); + // Try to hash + let result = sha3(&mut file); + // Remove file (always) + try!(fs::remove_file(&path)); + // Return the result + Ok(try!(result)) + }; + + match res { + Err(e) => ready.ready(Err(e)), + Ok(()) => { + let (tx, rx) = mpsc::channel(); + let res = self.fetch.lock().request_async(&url, Default::default(), Box::new(move |result| { + let result = hash_content(result) + .map_err(errors::from_fetch_error) + .map(Into::into); + + // Receive ready and invoke with result. + let ready: Ready = rx.recv().expect( + "recv() fails when `tx` has been dropped, if this closure is invoked `tx` is not dropped (`res == Ok()`); qed" + ); + ready.ready(result); + })); + + // Either invoke ready right away or transfer it to the closure. + if let Err(e) = res { + ready.ready(Err(errors::from_fetch_error(e))); + } else { + tx.send(ready).expect( + "send() fails when `rx` end is dropped, if `res == Ok()`: `rx` is moved to the closure; qed" + ); + } + } + } + } } diff --git a/rpc/src/v1/impls/personal.rs b/rpc/src/v1/impls/personal.rs index bace56db1..ffb20610c 100644 --- a/rpc/src/v1/impls/personal.rs +++ b/rpc/src/v1/impls/personal.rs @@ -16,26 +16,34 @@ //! Account management (personal) rpc implementation use std::sync::{Arc, Weak}; -use std::collections::{BTreeMap}; -use jsonrpc_core::*; -use v1::traits::Personal; -use v1::types::{H160 as RpcH160}; -use v1::helpers::errors; + use ethcore::account_provider::AccountProvider; use ethcore::client::MiningBlockChainClient; +use ethcore::miner::MinerService; +use util::Address; + +use jsonrpc_core::Error; +use v1::traits::Personal; +use v1::types::{H160 as RpcH160, H256 as RpcH256, TransactionRequest}; +use v1::helpers::errors; +use v1::helpers::dispatch::sign_and_dispatch; /// Account management (personal) rpc implementation. -pub struct PersonalClient where C: MiningBlockChainClient { +pub struct PersonalClient where C: MiningBlockChainClient, M: MinerService { accounts: Weak, client: Weak, + miner: Weak, + allow_perm_unlock: bool, } -impl PersonalClient where C: MiningBlockChainClient { +impl PersonalClient where C: MiningBlockChainClient, M: MinerService { /// Creates new PersonalClient - pub fn new(store: &Arc, client: &Arc) -> Self { + pub fn new(store: &Arc, client: &Arc, miner: &Arc, allow_perm_unlock: bool) -> Self { PersonalClient { accounts: Arc::downgrade(store), client: Arc::downgrade(client), + miner: Arc::downgrade(miner), + allow_perm_unlock: allow_perm_unlock, } } @@ -46,8 +54,7 @@ impl PersonalClient where C: MiningBlockChainClient { } } -impl Personal for PersonalClient where C: MiningBlockChainClient { - +impl Personal for PersonalClient where C: MiningBlockChainClient, M: MinerService { fn accounts(&self) -> Result, Error> { try!(self.active()); @@ -56,23 +63,42 @@ impl Personal for PersonalClient where C: MiningBlockChainClient Ok(accounts.into_iter().map(Into::into).collect::>()) } - fn accounts_info(&self) -> Result, Error> { + fn new_account(&self, pass: String) -> Result { try!(self.active()); let store = take_weak!(self.accounts); - let info = try!(store.accounts_info().map_err(|e| errors::account("Could not fetch account info.", e))); - let other = store.addresses_info().expect("addresses_info always returns Ok; qed"); - Ok(info.into_iter().chain(other.into_iter()).map(|(a, v)| { - let m = map![ - "name".to_owned() => to_value(&v.name), - "meta".to_owned() => to_value(&v.meta), - "uuid".to_owned() => if let &Some(ref uuid) = &v.uuid { - to_value(uuid) - } else { - Value::Null - } - ]; - (format!("0x{}", a.hex()), Value::Object(m)) - }).collect()) + store.new_account(&pass) + .map(Into::into) + .map_err(|e| errors::account("Could not create account.", e)) + } + + fn unlock_account(&self, account: RpcH160, account_pass: String, duration: Option) -> Result { + try!(self.active()); + 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(true), + // TODO [ToDr] Proper error here? + Err(_) => Ok(false), + } + } + + fn sign_and_send_transaction(&self, request: TransactionRequest, password: String) -> Result { + try!(self.active()); + + sign_and_dispatch( + &*take_weak!(self.client), + &*take_weak!(self.miner), + &*take_weak!(self.accounts), + request.into(), + Some(password) + ) } } diff --git a/rpc/src/v1/impls/personal_signer.rs b/rpc/src/v1/impls/signer.rs similarity index 94% rename from rpc/src/v1/impls/personal_signer.rs rename to rpc/src/v1/impls/signer.rs index 95db21e9f..61453e02b 100644 --- a/rpc/src/v1/impls/personal_signer.rs +++ b/rpc/src/v1/impls/signer.rs @@ -14,14 +14,14 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -//! Transactions Confirmations (personal) rpc implementation +//! Transactions Confirmations rpc implementation use std::sync::{Arc, Weak}; use jsonrpc_core::*; use ethcore::account_provider::AccountProvider; use ethcore::client::MiningBlockChainClient; use ethcore::miner::MinerService; -use v1::traits::PersonalSigner; +use v1::traits::Signer; use v1::types::{TransactionModification, ConfirmationRequest, U256}; use v1::helpers::{errors, SignerService, SigningQueue, ConfirmationPayload}; use v1::helpers::dispatch::{sign_and_dispatch, sign, decrypt}; @@ -58,7 +58,7 @@ impl SignerClient where C: MiningBlockChainClient, } } -impl PersonalSigner for SignerClient where C: MiningBlockChainClient, M: MinerService { +impl Signer for SignerClient where C: MiningBlockChainClient, M: MinerService { fn requests_to_confirm(&self) -> Result, Error> { try!(self.active()); diff --git a/rpc/src/v1/impls/eth_signing.rs b/rpc/src/v1/impls/signing.rs similarity index 72% rename from rpc/src/v1/impls/eth_signing.rs rename to rpc/src/v1/impls/signing.rs index dd71af6a5..f54847757 100644 --- a/rpc/src/v1/impls/eth_signing.rs +++ b/rpc/src/v1/impls/signing.rs @@ -14,43 +14,22 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -//! Eth Signing RPC implementation. +//! Signing RPC implementation. use std::sync::{Arc, Weak}; -use jsonrpc_core::*; + +use ethcore::account_provider::AccountProvider; use ethcore::miner::MinerService; use ethcore::client::MiningBlockChainClient; -use util::{U256, Address, H256, Mutex}; use transient_hashmap::TransientHashMap; -use ethcore::account_provider::AccountProvider; +use util::{U256, Address, H256, Mutex}; + +use jsonrpc_core::*; use v1::helpers::{errors, SigningQueue, ConfirmationPromise, ConfirmationResult, ConfirmationPayload, TransactionRequest as TRequest, FilledTransactionRequest as FilledRequest, SignerService}; use v1::helpers::dispatch::{default_gas_price, sign_and_dispatch, sign, decrypt}; -use v1::traits::EthSigning; +use v1::traits::{EthSigning, ParitySigning}; use v1::types::{TransactionRequest, H160 as RpcH160, H256 as RpcH256, U256 as RpcU256, Bytes as RpcBytes}; -fn fill_optional_fields(request: TRequest, client: &C, miner: &M) -> FilledRequest - where C: MiningBlockChainClient, M: MinerService { - FilledRequest { - from: request.from, - to: request.to, - nonce: request.nonce, - gas_price: request.gas_price.unwrap_or_else(|| default_gas_price(client, miner)), - gas: request.gas.unwrap_or_else(|| miner.sensible_gas_limit()), - value: request.value.unwrap_or_else(|| 0.into()), - data: request.data.unwrap_or_else(Vec::new), - } -} - -/// Implementation of functions that require signing when no trusted signer is used. -pub struct EthSigningQueueClient where C: MiningBlockChainClient, M: MinerService { - signer: Weak, - accounts: Weak, - client: Weak, - miner: Weak, - - pending: Mutex>, -} - const MAX_PENDING_DURATION: u64 = 60 * 60; pub enum DispatchResult { @@ -58,10 +37,23 @@ pub enum DispatchResult { Value(Value), } -impl EthSigningQueueClient where C: MiningBlockChainClient, M: MinerService { +/// Implementation of functions that require signing when no trusted signer is used. +pub struct SigningQueueClient where C: MiningBlockChainClient, M: MinerService { + signer: Weak, + accounts: Weak, + client: Weak, + miner: Weak, + + pending: Mutex>, +} + +impl SigningQueueClient where + C: MiningBlockChainClient, + M: MinerService, +{ /// Creates a new signing queue client given shared signing queue. pub fn new(signer: &Arc, client: &Arc, miner: &Arc, accounts: &Arc) -> Self { - EthSigningQueueClient { + SigningQueueClient { signer: Arc::downgrade(signer), accounts: Arc::downgrade(accounts), client: Arc::downgrade(client), @@ -132,10 +124,10 @@ impl EthSigningQueueClient where C: MiningBlockChainClient, M: Miner } } -impl EthSigning for EthSigningQueueClient - where C: MiningBlockChainClient + 'static, M: MinerService + 'static +impl ParitySigning for SigningQueueClient where + C: MiningBlockChainClient, + M: MinerService, { - fn post_sign(&self, params: Params) -> Result { try!(self.active()); self.dispatch_sign(params).map(|result| match result { @@ -178,16 +170,6 @@ impl EthSigning for EthSigningQueueClient }) } - fn sign(&self, params: Params, ready: Ready) { - let res = self.active().and_then(|_| self.dispatch_sign(params)); - self.handle_dispatch(res, ready); - } - - fn send_transaction(&self, params: Params, ready: Ready) { - let res = self.active().and_then(|_| self.dispatch_transaction(params)); - self.handle_dispatch(res, ready); - } - fn decrypt_message(&self, params: Params, ready: Ready) { let res = self.active() .and_then(|_| from_params::<(RpcH160, RpcBytes)>(params)) @@ -205,76 +187,30 @@ impl EthSigning for EthSigningQueueClient } } -/// Implementation of functions that require signing when no trusted signer is used. -pub struct EthSigningUnsafeClient where +impl EthSigning for SigningQueueClient where C: MiningBlockChainClient, - M: MinerService { - client: Weak, - accounts: Weak, - miner: Weak, -} - -impl EthSigningUnsafeClient where - C: MiningBlockChainClient, - M: MinerService { - - /// Creates new EthClient. - pub fn new(client: &Arc, accounts: &Arc, miner: &Arc) - -> Self { - EthSigningUnsafeClient { - client: Arc::downgrade(client), - miner: Arc::downgrade(miner), - accounts: Arc::downgrade(accounts), - } - } - - fn active(&self) -> Result<(), Error> { - // TODO: only call every 30s at most. - take_weak!(self.client).keep_alive(); - Ok(()) - } -} - -impl EthSigning for EthSigningUnsafeClient where - C: MiningBlockChainClient + 'static, - M: MinerService + 'static { - + M: MinerService, +{ fn sign(&self, params: Params, ready: Ready) { - ready.ready(self.active() - .and_then(|_| from_params::<(RpcH160, RpcH256)>(params)) - .and_then(|(address, msg)| { - sign(&*take_weak!(self.accounts), address.into(), None, msg.into()) - })) + let res = self.active().and_then(|_| self.dispatch_sign(params)); + self.handle_dispatch(res, ready); } fn send_transaction(&self, params: Params, ready: Ready) { - ready.ready(self.active() - .and_then(|_| from_params::<(TransactionRequest, )>(params)) - .and_then(|(request, )| { - sign_and_dispatch(&*take_weak!(self.client), &*take_weak!(self.miner), &*take_weak!(self.accounts), request.into(), None).map(to_value) - })) - } - - fn decrypt_message(&self, params: Params, ready: Ready) { - ready.ready(self.active() - .and_then(|_| from_params::<(RpcH160, RpcBytes)>(params)) - .and_then(|(address, ciphertext)| { - decrypt(&*take_weak!(self.accounts), address.into(), None, ciphertext.0) - })) - } - - fn post_sign(&self, _: Params) -> Result { - // We don't support this in non-signer mode. - Err(errors::signer_disabled()) - } - - fn post_transaction(&self, _: Params) -> Result { - // We don't support this in non-signer mode. - Err(errors::signer_disabled()) - } - - fn check_request(&self, _: Params) -> Result { - // We don't support this in non-signer mode. - Err(errors::signer_disabled()) + let res = self.active().and_then(|_| self.dispatch_transaction(params)); + self.handle_dispatch(res, ready); + } +} + +fn fill_optional_fields(request: TRequest, client: &C, miner: &M) -> FilledRequest + where C: MiningBlockChainClient, M: MinerService { + FilledRequest { + from: request.from, + to: request.to, + nonce: request.nonce, + gas_price: request.gas_price.unwrap_or_else(|| default_gas_price(client, miner)), + gas: request.gas.unwrap_or_else(|| miner.sensible_gas_limit()), + value: request.value.unwrap_or_else(|| 0.into()), + data: request.data.unwrap_or_else(Vec::new), } } diff --git a/rpc/src/v1/impls/signing_unsafe.rs b/rpc/src/v1/impls/signing_unsafe.rs new file mode 100644 index 000000000..251ce9329 --- /dev/null +++ b/rpc/src/v1/impls/signing_unsafe.rs @@ -0,0 +1,110 @@ +// 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 . + +//! Unsafe Signing RPC implementation. + +use std::sync::{Arc, Weak}; + +use ethcore::account_provider::AccountProvider; +use ethcore::miner::MinerService; +use ethcore::client::MiningBlockChainClient; + +use jsonrpc_core::*; +use v1::helpers::errors; +use v1::helpers::dispatch::{sign_and_dispatch, sign, decrypt}; +use v1::traits::{EthSigning, ParitySigning}; +use v1::types::{TransactionRequest, H160 as RpcH160, H256 as RpcH256, Bytes as RpcBytes}; + +/// Implementation of functions that require signing when no trusted signer is used. +pub struct SigningUnsafeClient where + C: MiningBlockChainClient, + M: MinerService, +{ + accounts: Weak, + client: Weak, + miner: Weak, +} + +impl SigningUnsafeClient where + C: MiningBlockChainClient, + M: MinerService, +{ + + /// Creates new SigningUnsafeClient. + pub fn new(client: &Arc, accounts: &Arc, miner: &Arc) + -> Self { + SigningUnsafeClient { + client: Arc::downgrade(client), + miner: Arc::downgrade(miner), + accounts: Arc::downgrade(accounts), + } + } + + fn active(&self) -> Result<(), Error> { + // TODO: only call every 30s at most. + take_weak!(self.client).keep_alive(); + Ok(()) + } +} + +impl EthSigning for SigningUnsafeClient where + C: MiningBlockChainClient, + M: MinerService, +{ + fn sign(&self, params: Params, ready: Ready) { + ready.ready(self.active() + .and_then(|_| from_params::<(RpcH160, RpcH256)>(params)) + .and_then(|(address, msg)| { + sign(&*take_weak!(self.accounts), address.into(), None, msg.into()) + })) + } + + fn send_transaction(&self, params: Params, ready: Ready) { + ready.ready(self.active() + .and_then(|_| from_params::<(TransactionRequest, )>(params)) + .and_then(|(request, )| { + sign_and_dispatch(&*take_weak!(self.client), &*take_weak!(self.miner), &*take_weak!(self.accounts), request.into(), None).map(to_value) + })) + } +} + +impl ParitySigning for SigningUnsafeClient where + C: MiningBlockChainClient, + M: MinerService, +{ + fn decrypt_message(&self, params: Params, ready: Ready) { + ready.ready(self.active() + .and_then(|_| from_params::<(RpcH160, RpcBytes)>(params)) + .and_then(|(address, ciphertext)| { + decrypt(&*take_weak!(self.accounts), address.into(), None, ciphertext.0) + })) + } + + fn post_sign(&self, _: Params) -> Result { + // We don't support this in non-signer mode. + Err(errors::signer_disabled()) + } + + fn post_transaction(&self, _: Params) -> Result { + // We don't support this in non-signer mode. + Err(errors::signer_disabled()) + } + + fn check_request(&self, _: Params) -> Result { + // We don't support this in non-signer mode. + Err(errors::signer_disabled()) + } +} diff --git a/rpc/src/v1/mod.rs b/rpc/src/v1/mod.rs index 24560160c..966a87f26 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, PersonalAccounts, PersonalSigner, Net, Ethcore, EthcoreSet, Traces, Rpc}; +pub use self::traits::{Web3, Eth, EthFilter, EthSigning, Net, Parity, ParityAccounts, ParitySet, ParitySigning, Signer, Personal, Traces, Rpc}; pub use self::impls::*; pub use self::helpers::{SigningQueue, SignerService, ConfirmationsQueue, NetworkSettings, block_import}; diff --git a/rpc/src/v1/tests/eth.rs b/rpc/src/v1/tests/eth.rs index a458a0570..2f5131f32 100644 --- a/rpc/src/v1/tests/eth.rs +++ b/rpc/src/v1/tests/eth.rs @@ -33,7 +33,7 @@ use util::{U256, H256, Uint, Address}; use jsonrpc_core::IoHandler; use ethjson::blockchain::BlockChain; -use v1::impls::{EthClient, EthSigningUnsafeClient}; +use v1::impls::{EthClient, SigningUnsafeClient}; use v1::types::U256 as NU256; use v1::traits::eth::Eth; use v1::traits::eth_signing::EthSigning; @@ -140,7 +140,7 @@ impl EthTester { &external_miner, Default::default(), ); - let eth_sign = EthSigningUnsafeClient::new( + let eth_sign = SigningUnsafeClient::new( &client, &account_provider, &miner_service diff --git a/rpc/src/v1/tests/mocked/eth.rs b/rpc/src/v1/tests/mocked/eth.rs index d324715d6..9f654e7e0 100644 --- a/rpc/src/v1/tests/mocked/eth.rs +++ b/rpc/src/v1/tests/mocked/eth.rs @@ -27,7 +27,7 @@ use ethcore::receipt::LocalizedReceipt; use ethcore::transaction::{Transaction, Action}; use ethcore::miner::{ExternalMiner, MinerService}; use ethsync::SyncState; -use v1::{Eth, EthClient, EthClientOptions, EthFilter, EthFilterClient, EthSigning, EthSigningUnsafeClient}; +use v1::{Eth, EthClient, EthClientOptions, EthFilter, EthFilterClient, EthSigning, SigningUnsafeClient}; use v1::tests::helpers::{TestSyncProvider, Config, TestMinerService, TestSnapshotService}; use rustc_serialize::hex::ToHex; use time::get_time; @@ -83,7 +83,7 @@ impl EthTester { let external_miner = Arc::new(ExternalMiner::new(hashrates.clone())); let eth = EthClient::new(&client, &snapshot, &sync, &ap, &miner, &external_miner, options).to_delegate(); let filter = EthFilterClient::new(&client, &miner).to_delegate(); - let sign = EthSigningUnsafeClient::new(&client, &ap, &miner).to_delegate(); + let sign = SigningUnsafeClient::new(&client, &ap, &miner).to_delegate(); let io = IoHandler::new(); io.add_delegate(eth); io.add_delegate(sign); diff --git a/rpc/src/v1/tests/mocked/mod.rs b/rpc/src/v1/tests/mocked/mod.rs index cc54555b7..a7d7156b4 100644 --- a/rpc/src/v1/tests/mocked/mod.rs +++ b/rpc/src/v1/tests/mocked/mod.rs @@ -18,12 +18,13 @@ //! method calls properly. mod eth; -mod eth_signing; mod net; mod web3; mod personal; -mod personal_signer; -mod ethcore; -mod ethcore_set; +mod parity; +mod parity_accounts; +mod parity_set; mod rpc; +mod signer; +mod signing; mod manage_network; diff --git a/rpc/src/v1/tests/mocked/ethcore.rs b/rpc/src/v1/tests/mocked/parity.rs similarity index 73% rename from rpc/src/v1/tests/mocked/ethcore.rs rename to rpc/src/v1/tests/mocked/parity.rs index a64f133bf..8baecbd90 100644 --- a/rpc/src/v1/tests/mocked/ethcore.rs +++ b/rpc/src/v1/tests/mocked/parity.rs @@ -16,19 +16,19 @@ use std::sync::Arc; use util::log::RotatingLogger; -use util::{Address}; +use util::Address; use ethsync::ManageNetwork; use ethcore::client::{TestBlockChainClient}; +use ethcore::account_provider::AccountProvider; use ethstore::ethkey::{Generator, Random}; use jsonrpc_core::IoHandler; -use v1::{Ethcore, EthcoreClient}; +use v1::{Parity, ParityClient}; use v1::helpers::{SignerService, NetworkSettings}; -use v1::tests::helpers::{TestSyncProvider, Config, TestMinerService, TestFetch}; +use v1::tests::helpers::{TestSyncProvider, Config, TestMinerService}; use super::manage_network::TestManageNetwork; - -pub type TestEthcoreClient = EthcoreClient; +pub type TestParityClient = ParityClient; pub struct Dependencies { pub miner: Arc, @@ -37,6 +37,7 @@ pub struct Dependencies { pub logger: Arc, pub settings: Arc, pub network: Arc, + pub accounts: Arc, pub dapps_port: Option, } @@ -59,16 +60,18 @@ impl Dependencies { rpc_port: 8545, }), network: Arc::new(TestManageNetwork), + accounts: Arc::new(AccountProvider::transient_provider()), dapps_port: Some(18080), } } - pub fn client(&self, signer: Option>) -> TestEthcoreClient { - EthcoreClient::with_fetch( + pub fn client(&self, signer: Option>) -> TestParityClient { + ParityClient::new( &self.client, &self.miner, &self.sync, &self.network, + &self.accounts, self.logger.clone(), self.settings.clone(), signer, @@ -90,105 +93,105 @@ impl Dependencies { } #[test] -fn rpc_ethcore_extra_data() { +fn rpc_parity_extra_data() { let deps = Dependencies::new(); let io = deps.default_client(); - let request = r#"{"jsonrpc": "2.0", "method": "ethcore_extraData", "params": [], "id": 1}"#; + let request = r#"{"jsonrpc": "2.0", "method": "parity_extraData", "params": [], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":"0x01020304","id":1}"#; assert_eq!(io.handle_request_sync(request), Some(response.to_owned())); } #[test] -fn rpc_ethcore_default_extra_data() { +fn rpc_parity_default_extra_data() { use util::misc; use util::ToPretty; let deps = Dependencies::new(); let io = deps.default_client(); - let request = r#"{"jsonrpc": "2.0", "method": "ethcore_defaultExtraData", "params": [], "id": 1}"#; + let request = r#"{"jsonrpc": "2.0", "method": "parity_defaultExtraData", "params": [], "id": 1}"#; let response = format!(r#"{{"jsonrpc":"2.0","result":"0x{}","id":1}}"#, misc::version_data().to_hex()); assert_eq!(io.handle_request_sync(request), Some(response)); } #[test] -fn rpc_ethcore_gas_floor_target() { +fn rpc_parity_gas_floor_target() { let deps = Dependencies::new(); let io = deps.default_client(); - let request = r#"{"jsonrpc": "2.0", "method": "ethcore_gasFloorTarget", "params": [], "id": 1}"#; + let request = r#"{"jsonrpc": "2.0", "method": "parity_gasFloorTarget", "params": [], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":"0x3039","id":1}"#; assert_eq!(io.handle_request_sync(request), Some(response.to_owned())); } #[test] -fn rpc_ethcore_min_gas_price() { +fn rpc_parity_min_gas_price() { let deps = Dependencies::new(); let io = deps.default_client(); - let request = r#"{"jsonrpc": "2.0", "method": "ethcore_minGasPrice", "params": [], "id": 1}"#; + let request = r#"{"jsonrpc": "2.0", "method": "parity_minGasPrice", "params": [], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":"0x1312d00","id":1}"#; assert_eq!(io.handle_request_sync(request), Some(response.to_owned())); } #[test] -fn rpc_ethcore_dev_logs() { +fn rpc_parity_dev_logs() { let deps = Dependencies::new(); deps.logger.append("a".to_owned()); deps.logger.append("b".to_owned()); let io = deps.default_client(); - let request = r#"{"jsonrpc": "2.0", "method": "ethcore_devLogs", "params":[], "id": 1}"#; + let request = r#"{"jsonrpc": "2.0", "method": "parity_devLogs", "params":[], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":["b","a"],"id":1}"#; assert_eq!(io.handle_request_sync(request), Some(response.to_owned())); } #[test] -fn rpc_ethcore_dev_logs_levels() { +fn rpc_parity_dev_logs_levels() { let deps = Dependencies::new(); let io = deps.default_client(); - let request = r#"{"jsonrpc": "2.0", "method": "ethcore_devLogsLevels", "params":[], "id": 1}"#; + let request = r#"{"jsonrpc": "2.0", "method": "parity_devLogsLevels", "params":[], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":"rpc=trace","id":1}"#; assert_eq!(io.handle_request_sync(request), Some(response.to_owned())); } #[test] -fn rpc_ethcore_transactions_limit() { +fn rpc_parity_transactions_limit() { let deps = Dependencies::new(); let io = deps.default_client(); - let request = r#"{"jsonrpc": "2.0", "method": "ethcore_transactionsLimit", "params":[], "id": 1}"#; + let request = r#"{"jsonrpc": "2.0", "method": "parity_transactionsLimit", "params":[], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":1024,"id":1}"#; assert_eq!(io.handle_request_sync(request), Some(response.to_owned())); } #[test] -fn rpc_ethcore_net_chain() { +fn rpc_parity_net_chain() { let deps = Dependencies::new(); let io = deps.default_client(); - let request = r#"{"jsonrpc": "2.0", "method": "ethcore_netChain", "params":[], "id": 1}"#; + let request = r#"{"jsonrpc": "2.0", "method": "parity_netChain", "params":[], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":"testchain","id":1}"#; assert_eq!(io.handle_request_sync(request), Some(response.to_owned())); } #[test] -fn rpc_ethcore_net_peers() { +fn rpc_parity_net_peers() { let deps = Dependencies::new(); let io = deps.default_client(); - let request = r#"{"jsonrpc": "2.0", "method": "ethcore_netPeers", "params":[], "id": 1}"#; + let request = r#"{"jsonrpc": "2.0", "method": "parity_netPeers", "params":[], "id": 1}"#; let response = "{\"jsonrpc\":\"2.0\",\"result\":{\"active\":0,\"connected\":120,\"max\":50,\"peers\":[{\"caps\":[\"eth/62\",\"eth/63\"],\ \"id\":\"node1\",\"name\":\"Parity/1\",\"network\":{\"localAddress\":\"127.0.0.1:8888\",\"remoteAddress\":\"127.0.0.1:7777\"}\ ,\"protocols\":{\"eth\":{\"difficulty\":\"0x28\",\"head\":\"0000000000000000000000000000000000000000000000000000000000000032\"\ @@ -200,101 +203,90 @@ fn rpc_ethcore_net_peers() { } #[test] -fn rpc_ethcore_net_port() { +fn rpc_parity_net_port() { let deps = Dependencies::new(); let io = deps.default_client(); - let request = r#"{"jsonrpc": "2.0", "method": "ethcore_netPort", "params":[], "id": 1}"#; + let request = r#"{"jsonrpc": "2.0", "method": "parity_netPort", "params":[], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":30303,"id":1}"#; assert_eq!(io.handle_request_sync(request), Some(response.to_owned())); } #[test] -fn rpc_ethcore_rpc_settings() { +fn rpc_parity_rpc_settings() { let deps = Dependencies::new(); let io = deps.default_client(); - let request = r#"{"jsonrpc": "2.0", "method": "ethcore_rpcSettings", "params":[], "id": 1}"#; + let request = r#"{"jsonrpc": "2.0", "method": "parity_rpcSettings", "params":[], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":{"enabled":true,"interface":"all","port":8545},"id":1}"#; assert_eq!(io.handle_request_sync(request), Some(response.to_owned())); } #[test] -fn rpc_ethcore_node_name() { +fn rpc_parity_node_name() { let deps = Dependencies::new(); let io = deps.default_client(); - let request = r#"{"jsonrpc": "2.0", "method": "ethcore_nodeName", "params":[], "id": 1}"#; + let request = r#"{"jsonrpc": "2.0", "method": "parity_nodeName", "params":[], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":"mynode","id":1}"#; assert_eq!(io.handle_request_sync(request), Some(response.to_owned())); } #[test] -fn rpc_ethcore_unsigned_transactions_count() { +fn rpc_parity_unsigned_transactions_count() { let deps = Dependencies::new(); let io = deps.with_signer(SignerService::new_test(Some(18180))); - let request = r#"{"jsonrpc": "2.0", "method": "ethcore_unsignedTransactionsCount", "params":[], "id": 1}"#; + let request = r#"{"jsonrpc": "2.0", "method": "parity_unsignedTransactionsCount", "params":[], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":0,"id":1}"#; assert_eq!(io.handle_request_sync(request), Some(response.to_owned())); } #[test] -fn rpc_ethcore_unsigned_transactions_count_when_signer_disabled() { +fn rpc_parity_unsigned_transactions_count_when_signer_disabled() { let deps = Dependencies::new(); let io = deps.default_client(); - let request = r#"{"jsonrpc": "2.0", "method": "ethcore_unsignedTransactionsCount", "params":[], "id": 1}"#; + let request = r#"{"jsonrpc": "2.0", "method": "parity_unsignedTransactionsCount", "params":[], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","error":{"code":-32030,"message":"Trusted Signer is disabled. This API is not available.","data":null},"id":1}"#; assert_eq!(io.handle_request_sync(request), Some(response.to_owned())); } #[test] -fn rpc_ethcore_hash_content() { +fn rpc_parity_pending_transactions() { let deps = Dependencies::new(); let io = deps.default_client(); - let request = r#"{"jsonrpc": "2.0", "method": "ethcore_hashContent", "params":["https://ethcore.io/assets/images/ethcore-black-horizontal.png"], "id": 1}"#; - let response = r#"{"jsonrpc":"2.0","result":"0x2be00befcf008bc0e7d9cdefc194db9c75352e8632f48498b5a6bfce9f02c88e","id":1}"#; - - assert_eq!(io.handle_request_sync(request), Some(response.to_owned())); -} - -#[test] -fn rpc_ethcore_pending_transactions() { - let deps = Dependencies::new(); - let io = deps.default_client(); - - let request = r#"{"jsonrpc": "2.0", "method": "ethcore_pendingTransactions", "params":[], "id": 1}"#; + let request = r#"{"jsonrpc": "2.0", "method": "parity_pendingTransactions", "params":[], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":[],"id":1}"#; assert_eq!(io.handle_request_sync(request), Some(response.to_owned())); } #[test] -fn rpc_ethcore_encrypt() { +fn rpc_parity_encrypt() { let deps = Dependencies::new(); let io = deps.default_client(); let key = format!("{:?}", Random.generate().unwrap().public()); - let request = r#"{"jsonrpc": "2.0", "method": "ethcore_encryptMessage", "params":["0x"#.to_owned() + &key + r#"", "0x01"], "id": 1}"#; + let request = r#"{"jsonrpc": "2.0", "method": "parity_encryptMessage", "params":["0x"#.to_owned() + &key + r#"", "0x01"], "id": 1}"#; assert!(io.handle_request_sync(&request).unwrap().contains("result"), "Should return success."); } #[test] -fn rpc_ethcore_signer_port() { +fn rpc_parity_signer_port() { // given let deps = Dependencies::new(); let io1 = deps.with_signer(SignerService::new_test(Some(18180))); let io2 = deps.default_client(); // when - let request = r#"{"jsonrpc": "2.0", "method": "ethcore_signerPort", "params": [], "id": 1}"#; + let request = r#"{"jsonrpc": "2.0", "method": "parity_signerPort", "params": [], "id": 1}"#; let response1 = r#"{"jsonrpc":"2.0","result":18180,"id":1}"#; let response2 = r#"{"jsonrpc":"2.0","error":{"code":-32030,"message":"Trusted Signer is disabled. This API is not available.","data":null},"id":1}"#; @@ -304,7 +296,7 @@ fn rpc_ethcore_signer_port() { } #[test] -fn rpc_ethcore_dapps_port() { +fn rpc_parity_dapps_port() { // given let mut deps = Dependencies::new(); let io1 = deps.default_client(); @@ -312,7 +304,7 @@ fn rpc_ethcore_dapps_port() { let io2 = deps.default_client(); // when - let request = r#"{"jsonrpc": "2.0", "method": "ethcore_dappsPort", "params": [], "id": 1}"#; + let request = r#"{"jsonrpc": "2.0", "method": "parity_dappsPort", "params": [], "id": 1}"#; let response1 = r#"{"jsonrpc":"2.0","result":18080,"id":1}"#; let response2 = r#"{"jsonrpc":"2.0","error":{"code":-32031,"message":"Dapps Server is disabled. This API is not available.","data":null},"id":1}"#; @@ -322,7 +314,7 @@ fn rpc_ethcore_dapps_port() { } #[test] -fn rpc_ethcore_next_nonce() { +fn rpc_parity_next_nonce() { let deps = Dependencies::new(); let address = Address::default(); let io1 = deps.default_client(); @@ -332,7 +324,7 @@ fn rpc_ethcore_next_nonce() { let request = r#"{ "jsonrpc": "2.0", - "method": "ethcore_nextNonce", + "method": "parity_nextNonce", "params": [""#.to_owned() + &format!("0x{:?}", address) + r#""], "id": 1 }"#; diff --git a/rpc/src/v1/tests/mocked/parity_accounts.rs b/rpc/src/v1/tests/mocked/parity_accounts.rs new file mode 100644 index 000000000..272c55f6d --- /dev/null +++ b/rpc/src/v1/tests/mocked/parity_accounts.rs @@ -0,0 +1,118 @@ +// 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 . + +use std::sync::Arc; + +use ethcore::account_provider::AccountProvider; +use ethcore::client::TestBlockChainClient; + +use jsonrpc_core::IoHandler; +use v1::{ParityAccounts, ParityAccountsClient}; + +struct ParityAccountsTester { + accounts: Arc, + io: IoHandler, + // these unused fields are necessary to keep the data alive + // as the handler has only weak pointers. + _client: Arc, +} + +fn blockchain_client() -> Arc { + let client = TestBlockChainClient::new(); + Arc::new(client) +} + +fn accounts_provider() -> Arc { + Arc::new(AccountProvider::transient_provider()) +} + +fn setup() -> ParityAccountsTester { + let accounts = accounts_provider(); + let client = blockchain_client(); + let parity_accounts = ParityAccountsClient::new(&accounts, &client); + + let io = IoHandler::new(); + io.add_delegate(parity_accounts.to_delegate()); + + let tester = ParityAccountsTester { + accounts: accounts, + io: io, + _client: client, + }; + + tester +} + +#[test] +fn should_be_able_to_get_account_info() { + let tester = setup(); + tester.accounts.new_account("").unwrap(); + let accounts = tester.accounts.accounts().unwrap(); + assert_eq!(accounts.len(), 1); + let address = accounts[0]; + + let uuid = tester.accounts.accounts_info().unwrap().get(&address).unwrap().uuid.as_ref().unwrap().clone(); + tester.accounts.set_account_name(address.clone(), "Test".to_owned()).unwrap(); + tester.accounts.set_account_meta(address.clone(), "{foo: 69}".to_owned()).unwrap(); + + let request = r#"{"jsonrpc": "2.0", "method": "parity_accountsInfo", "params": [], "id": 1}"#; + let res = tester.io.handle_request_sync(request); + let response = format!("{{\"jsonrpc\":\"2.0\",\"result\":{{\"0x{}\":{{\"meta\":\"{{foo: 69}}\",\"name\":\"Test\",\"uuid\":\"{}\"}}}},\"id\":1}}", address.hex(), uuid); + assert_eq!(res, Some(response)); +} + +#[test] +fn should_be_able_to_set_name() { + let tester = setup(); + tester.accounts.new_account("").unwrap(); + let accounts = tester.accounts.accounts().unwrap(); + assert_eq!(accounts.len(), 1); + let address = accounts[0]; + + let request = format!(r#"{{"jsonrpc": "2.0", "method": "parity_setAccountName", "params": ["0x{}", "Test"], "id": 1}}"#, address.hex()); + let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#; + let res = tester.io.handle_request_sync(&request); + assert_eq!(res, Some(response.into())); + + let uuid = tester.accounts.accounts_info().unwrap().get(&address).unwrap().uuid.as_ref().unwrap().clone(); + + let request = r#"{"jsonrpc": "2.0", "method": "parity_accountsInfo", "params": [], "id": 1}"#; + let res = tester.io.handle_request_sync(request); + let response = format!("{{\"jsonrpc\":\"2.0\",\"result\":{{\"0x{}\":{{\"meta\":\"{{}}\",\"name\":\"Test\",\"uuid\":\"{}\"}}}},\"id\":1}}", address.hex(), uuid); + assert_eq!(res, Some(response)); +} + +#[test] +fn should_be_able_to_set_meta() { + let tester = setup(); + tester.accounts.new_account("").unwrap(); + let accounts = tester.accounts.accounts().unwrap(); + assert_eq!(accounts.len(), 1); + let address = accounts[0]; + + let request = format!(r#"{{"jsonrpc": "2.0", "method": "parity_setAccountMeta", "params": ["0x{}", "{{foo: 69}}"], "id": 1}}"#, address.hex()); + let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#; + let res = tester.io.handle_request_sync(&request); + assert_eq!(res, Some(response.into())); + + let uuid = tester.accounts.accounts_info().unwrap().get(&address).unwrap().uuid.as_ref().unwrap().clone(); + + let request = r#"{"jsonrpc": "2.0", "method": "parity_accountsInfo", "params": [], "id": 1}"#; + let res = tester.io.handle_request_sync(request); + let response = format!("{{\"jsonrpc\":\"2.0\",\"result\":{{\"0x{}\":{{\"meta\":\"{{foo: 69}}\",\"name\":\"{}\",\"uuid\":\"{}\"}}}},\"id\":1}}", address.hex(), uuid, uuid); + assert_eq!(res, Some(response)); +} + diff --git a/rpc/src/v1/tests/mocked/ethcore_set.rs b/rpc/src/v1/tests/mocked/parity_set.rs similarity index 57% rename from rpc/src/v1/tests/mocked/ethcore_set.rs rename to rpc/src/v1/tests/mocked/parity_set.rs index e87d49b8c..3202374a7 100644 --- a/rpc/src/v1/tests/mocked/ethcore_set.rs +++ b/rpc/src/v1/tests/mocked/parity_set.rs @@ -16,16 +16,18 @@ use std::sync::Arc; use std::str::FromStr; -use jsonrpc_core::IoHandler; -use v1::{EthcoreSet, EthcoreSetClient}; +use rustc_serialize::hex::FromHex; +use util::{U256, Address}; + use ethcore::miner::MinerService; use ethcore::client::TestBlockChainClient; -use v1::tests::helpers::TestMinerService; -use util::{U256, Address}; -use rustc_serialize::hex::FromHex; -use super::manage_network::TestManageNetwork; use ethsync::ManageNetwork; +use jsonrpc_core::IoHandler; +use v1::{ParitySet, ParitySetClient}; +use v1::tests::helpers::{TestMinerService, TestFetch}; +use super::manage_network::TestManageNetwork; + fn miner_service() -> Arc { Arc::new(TestMinerService::default()) } @@ -38,19 +40,21 @@ fn network_service() -> Arc { Arc::new(TestManageNetwork) } -fn ethcore_set_client(client: &Arc, miner: &Arc, net: &Arc) -> EthcoreSetClient { - EthcoreSetClient::new(client, miner, &(net.clone() as Arc)) +pub type TestParitySetClient = ParitySetClient; + +fn parity_set_client(client: &Arc, miner: &Arc, net: &Arc) -> TestParitySetClient { + ParitySetClient::with_fetch(client, miner, &(net.clone() as Arc)) } #[test] -fn rpc_ethcore_set_min_gas_price() { +fn rpc_parity_set_min_gas_price() { let miner = miner_service(); let client = client_service(); let network = network_service(); let io = IoHandler::new(); - io.add_delegate(ethcore_set_client(&client, &miner, &network).to_delegate()); + io.add_delegate(parity_set_client(&client, &miner, &network).to_delegate()); - let request = r#"{"jsonrpc": "2.0", "method": "ethcore_setMinGasPrice", "params":["0xcd1722f3947def4cf144679da39c4c32bdc35681"], "id": 1}"#; + let request = r#"{"jsonrpc": "2.0", "method": "parity_setMinGasPrice", "params":["0xcd1722f3947def4cf144679da39c4c32bdc35681"], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#; assert_eq!(io.handle_request_sync(request), Some(response.to_owned())); @@ -58,14 +62,14 @@ fn rpc_ethcore_set_min_gas_price() { } #[test] -fn rpc_ethcore_set_gas_floor_target() { +fn rpc_parity_set_gas_floor_target() { let miner = miner_service(); let client = client_service(); let network = network_service(); let io = IoHandler::new(); - io.add_delegate(ethcore_set_client(&client, &miner, &network).to_delegate()); + io.add_delegate(parity_set_client(&client, &miner, &network).to_delegate()); - let request = r#"{"jsonrpc": "2.0", "method": "ethcore_setGasFloorTarget", "params":["0xcd1722f3947def4cf144679da39c4c32bdc35681"], "id": 1}"#; + let request = r#"{"jsonrpc": "2.0", "method": "parity_setGasFloorTarget", "params":["0xcd1722f3947def4cf144679da39c4c32bdc35681"], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#; assert_eq!(io.handle_request_sync(request), Some(response.to_owned())); @@ -73,14 +77,14 @@ fn rpc_ethcore_set_gas_floor_target() { } #[test] -fn rpc_ethcore_set_extra_data() { +fn rpc_parity_set_extra_data() { let miner = miner_service(); let client = client_service(); let network = network_service(); let io = IoHandler::new(); - io.add_delegate(ethcore_set_client(&client, &miner, &network).to_delegate()); + io.add_delegate(parity_set_client(&client, &miner, &network).to_delegate()); - let request = r#"{"jsonrpc": "2.0", "method": "ethcore_setExtraData", "params":["0xcd1722f3947def4cf144679da39c4c32bdc35681"], "id": 1}"#; + let request = r#"{"jsonrpc": "2.0", "method": "parity_setExtraData", "params":["0xcd1722f3947def4cf144679da39c4c32bdc35681"], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#; assert_eq!(io.handle_request_sync(request), Some(response.to_owned())); @@ -88,14 +92,14 @@ fn rpc_ethcore_set_extra_data() { } #[test] -fn rpc_ethcore_set_author() { +fn rpc_parity_set_author() { let miner = miner_service(); let client = client_service(); let network = network_service(); let io = IoHandler::new(); - io.add_delegate(ethcore_set_client(&client, &miner, &network).to_delegate()); + io.add_delegate(parity_set_client(&client, &miner, &network).to_delegate()); - let request = r#"{"jsonrpc": "2.0", "method": "ethcore_setAuthor", "params":["0xcd1722f3947def4cf144679da39c4c32bdc35681"], "id": 1}"#; + let request = r#"{"jsonrpc": "2.0", "method": "parity_setAuthor", "params":["0xcd1722f3947def4cf144679da39c4c32bdc35681"], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#; assert_eq!(io.handle_request_sync(request), Some(response.to_owned())); @@ -103,16 +107,31 @@ fn rpc_ethcore_set_author() { } #[test] -fn rpc_ethcore_set_transactions_limit() { +fn rpc_parity_set_transactions_limit() { let miner = miner_service(); let client = client_service(); let network = network_service(); let io = IoHandler::new(); - io.add_delegate(ethcore_set_client(&client, &miner, &network).to_delegate()); + io.add_delegate(parity_set_client(&client, &miner, &network).to_delegate()); - let request = r#"{"jsonrpc": "2.0", "method": "ethcore_setTransactionsLimit", "params":[10240240], "id": 1}"#; + let request = r#"{"jsonrpc": "2.0", "method": "parity_setTransactionsLimit", "params":[10240240], "id": 1}"#; let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#; assert_eq!(io.handle_request_sync(request), Some(response.to_owned())); assert_eq!(miner.transactions_limit(), 10_240_240); -} \ No newline at end of file +} + +#[test] +fn rpc_parity_set_hash_content() { + let miner = miner_service(); + let client = client_service(); + let network = network_service(); + let io = IoHandler::new(); + io.add_delegate(parity_set_client(&client, &miner, &network).to_delegate()); + + let request = r#"{"jsonrpc": "2.0", "method": "parity_hashContent", "params":["https://ethcore.io/assets/images/ethcore-black-horizontal.png"], "id": 1}"#; + let response = r#"{"jsonrpc":"2.0","result":"0x2be00befcf008bc0e7d9cdefc194db9c75352e8632f48498b5a6bfce9f02c88e","id":1}"#; + + assert_eq!(io.handle_request_sync(request), Some(response.to_owned())); +} + diff --git a/rpc/src/v1/tests/mocked/personal.rs b/rpc/src/v1/tests/mocked/personal.rs index 511f23415..6e2de1e2e 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, PersonalAccountsClient, PersonalAccounts, Personal}; +use v1::{PersonalClient, Personal}; use v1::tests::helpers::TestMinerService; use ethcore::client::TestBlockChainClient; use ethcore::transaction::{Action, Transaction}; @@ -50,12 +50,10 @@ fn setup() -> PersonalTester { let accounts = accounts_provider(); let client = blockchain_client(); let miner = miner_service(); - let personal = PersonalClient::new(&accounts, &client); - let personal_accounts = PersonalAccountsClient::new(&accounts, &client, &miner, false); + let personal = PersonalClient::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, @@ -92,66 +90,6 @@ fn new_account() { assert_eq!(res, Some(response)); } -#[test] -fn should_be_able_to_get_account_info() { - let tester = setup(); - tester.accounts.new_account("").unwrap(); - let accounts = tester.accounts.accounts().unwrap(); - assert_eq!(accounts.len(), 1); - let address = accounts[0]; - - let uuid = tester.accounts.accounts_info().unwrap().get(&address).unwrap().uuid.as_ref().unwrap().clone(); - tester.accounts.set_account_name(address.clone(), "Test".to_owned()).unwrap(); - tester.accounts.set_account_meta(address.clone(), "{foo: 69}".to_owned()).unwrap(); - - let request = r#"{"jsonrpc": "2.0", "method": "personal_accountsInfo", "params": [], "id": 1}"#; - let res = tester.io.handle_request_sync(request); - let response = format!("{{\"jsonrpc\":\"2.0\",\"result\":{{\"0x{}\":{{\"meta\":\"{{foo: 69}}\",\"name\":\"Test\",\"uuid\":\"{}\"}}}},\"id\":1}}", address.hex(), uuid); - assert_eq!(res, Some(response)); -} - -#[test] -fn should_be_able_to_set_name() { - let tester = setup(); - tester.accounts.new_account("").unwrap(); - let accounts = tester.accounts.accounts().unwrap(); - assert_eq!(accounts.len(), 1); - let address = accounts[0]; - - let request = format!(r#"{{"jsonrpc": "2.0", "method": "personal_setAccountName", "params": ["0x{}", "Test"], "id": 1}}"#, address.hex()); - let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#; - let res = tester.io.handle_request_sync(&request); - assert_eq!(res, Some(response.into())); - - let uuid = tester.accounts.accounts_info().unwrap().get(&address).unwrap().uuid.as_ref().unwrap().clone(); - - let request = r#"{"jsonrpc": "2.0", "method": "personal_accountsInfo", "params": [], "id": 1}"#; - let res = tester.io.handle_request_sync(request); - let response = format!("{{\"jsonrpc\":\"2.0\",\"result\":{{\"0x{}\":{{\"meta\":\"{{}}\",\"name\":\"Test\",\"uuid\":\"{}\"}}}},\"id\":1}}", address.hex(), uuid); - assert_eq!(res, Some(response)); -} - -#[test] -fn should_be_able_to_set_meta() { - let tester = setup(); - tester.accounts.new_account("").unwrap(); - let accounts = tester.accounts.accounts().unwrap(); - assert_eq!(accounts.len(), 1); - let address = accounts[0]; - - let request = format!(r#"{{"jsonrpc": "2.0", "method": "personal_setAccountMeta", "params": ["0x{}", "{{foo: 69}}"], "id": 1}}"#, address.hex()); - let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#; - let res = tester.io.handle_request_sync(&request); - assert_eq!(res, Some(response.into())); - - let uuid = tester.accounts.accounts_info().unwrap().get(&address).unwrap().uuid.as_ref().unwrap().clone(); - - let request = r#"{"jsonrpc": "2.0", "method": "personal_accountsInfo", "params": [], "id": 1}"#; - let res = tester.io.handle_request_sync(request); - let response = format!("{{\"jsonrpc\":\"2.0\",\"result\":{{\"0x{}\":{{\"meta\":\"{{foo: 69}}\",\"name\":\"{}\",\"uuid\":\"{}\"}}}},\"id\":1}}", address.hex(), uuid, uuid); - assert_eq!(res, Some(response)); -} - #[test] fn sign_and_send_transaction_with_invalid_password() { let tester = setup(); diff --git a/rpc/src/v1/tests/mocked/personal_signer.rs b/rpc/src/v1/tests/mocked/signer.rs similarity index 91% rename from rpc/src/v1/tests/mocked/personal_signer.rs rename to rpc/src/v1/tests/mocked/signer.rs index ffcc47432..92e20676f 100644 --- a/rpc/src/v1/tests/mocked/personal_signer.rs +++ b/rpc/src/v1/tests/mocked/signer.rs @@ -21,11 +21,11 @@ use util::{U256, Uint, Address}; use ethcore::account_provider::AccountProvider; use ethcore::client::TestBlockChainClient; use ethcore::transaction::{Transaction, Action}; -use v1::{SignerClient, PersonalSigner}; +use v1::{SignerClient, Signer}; use v1::tests::helpers::TestMinerService; use v1::helpers::{SigningQueue, SignerService, FilledTransactionRequest, ConfirmationPayload}; -struct PersonalSignerTester { +struct SignerTester { signer: Arc, accounts: Arc, io: IoHandler, @@ -48,7 +48,7 @@ fn miner_service() -> Arc { Arc::new(TestMinerService::default()) } -fn signer_tester() -> PersonalSignerTester { +fn signer_tester() -> SignerTester { let signer = Arc::new(SignerService::new_test(None)); let accounts = accounts_provider(); let client = blockchain_client(); @@ -57,7 +57,7 @@ fn signer_tester() -> PersonalSignerTester { let io = IoHandler::new(); io.add_delegate(SignerClient::new(&accounts, &client, &miner, &signer).to_delegate()); - PersonalSignerTester { + SignerTester { signer: signer, accounts: accounts, io: io, @@ -83,7 +83,7 @@ fn should_return_list_of_items_to_confirm() { tester.signer.add_request(ConfirmationPayload::Sign(1.into(), 5.into())).unwrap(); // when - let request = r#"{"jsonrpc":"2.0","method":"personal_requestsToConfirm","params":[],"id":1}"#; + let request = r#"{"jsonrpc":"2.0","method":"signer_requestsToConfirm","params":[],"id":1}"#; let response = concat!( r#"{"jsonrpc":"2.0","result":["#, r#"{"id":"0x1","payload":{"transaction":{"data":"0x","from":"0x0000000000000000000000000000000000000001","gas":"0x989680","gasPrice":"0x2710","nonce":null,"to":"0xd46e8dd67c5d32be8058bb8eb970870f07244567","value":"0x1"}}},"#, @@ -112,7 +112,7 @@ fn should_reject_transaction_from_queue_without_dispatching() { assert_eq!(tester.signer.requests().len(), 1); // when - let request = r#"{"jsonrpc":"2.0","method":"personal_rejectRequest","params":["0x1"],"id":1}"#; + let request = r#"{"jsonrpc":"2.0","method":"signer_rejectRequest","params":["0x1"],"id":1}"#; let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#; // then @@ -137,7 +137,7 @@ fn should_not_remove_transaction_if_password_is_invalid() { assert_eq!(tester.signer.requests().len(), 1); // when - let request = r#"{"jsonrpc":"2.0","method":"personal_confirmRequest","params":["0x1",{},"xxx"],"id":1}"#; + let request = r#"{"jsonrpc":"2.0","method":"signer_confirmRequest","params":["0x1",{},"xxx"],"id":1}"#; let response = r#"{"jsonrpc":"2.0","error":{"code":-32021,"message":"Account password is invalid or account does not exist.","data":"SStore(InvalidAccount)"},"id":1}"#; // then @@ -153,7 +153,7 @@ fn should_not_remove_sign_if_password_is_invalid() { assert_eq!(tester.signer.requests().len(), 1); // when - let request = r#"{"jsonrpc":"2.0","method":"personal_confirmRequest","params":["0x1",{},"xxx"],"id":1}"#; + let request = r#"{"jsonrpc":"2.0","method":"signer_confirmRequest","params":["0x1",{},"xxx"],"id":1}"#; let response = r#"{"jsonrpc":"2.0","error":{"code":-32021,"message":"Account password is invalid or account does not exist.","data":"SStore(InvalidAccount)"},"id":1}"#; // then @@ -194,7 +194,7 @@ fn should_confirm_transaction_and_dispatch() { // when let request = r#"{ "jsonrpc":"2.0", - "method":"personal_confirmRequest", + "method":"signer_confirmRequest", "params":["0x1", {"gasPrice":"0x1000"}, "test"], "id":1 }"#; @@ -214,7 +214,7 @@ fn should_generate_new_token() { // when let request = r#"{ "jsonrpc":"2.0", - "method":"personal_generateAuthorizationToken", + "method":"signer_generateAuthorizationToken", "params":[], "id":1 }"#; diff --git a/rpc/src/v1/tests/mocked/eth_signing.rs b/rpc/src/v1/tests/mocked/signing.rs similarity index 90% rename from rpc/src/v1/tests/mocked/eth_signing.rs rename to rpc/src/v1/tests/mocked/signing.rs index 0f0125f87..fc460409c 100644 --- a/rpc/src/v1/tests/mocked/eth_signing.rs +++ b/rpc/src/v1/tests/mocked/signing.rs @@ -17,12 +17,12 @@ use std::str::FromStr; use std::sync::Arc; use jsonrpc_core::{IoHandler, to_value, Success}; -use v1::impls::EthSigningQueueClient; -use v1::traits::{EthSigning, Ethcore}; +use v1::impls::SigningQueueClient; +use v1::traits::{EthSigning, ParitySigning, Parity}; use v1::helpers::{SignerService, SigningQueue}; use v1::types::{H256 as RpcH256, H520 as RpcH520, Bytes}; use v1::tests::helpers::TestMinerService; -use v1::tests::mocked::ethcore; +use v1::tests::mocked::parity; use util::{Address, FixedHash, Uint, U256, H256, H520}; use ethcore::account_provider::AccountProvider; @@ -31,7 +31,7 @@ use ethcore::transaction::{Transaction, Action}; use ethstore::ethkey::{Generator, Random}; use serde_json; -struct EthSigningTester { +struct SigningTester { pub signer: Arc, pub client: Arc, pub miner: Arc, @@ -39,16 +39,19 @@ struct EthSigningTester { pub io: IoHandler, } -impl Default for EthSigningTester { +impl Default for SigningTester { fn default() -> Self { let signer = Arc::new(SignerService::new_test(None)); let client = Arc::new(TestBlockChainClient::default()); let miner = Arc::new(TestMinerService::default()); let accounts = Arc::new(AccountProvider::transient_provider()); let io = IoHandler::new(); - io.add_delegate(EthSigningQueueClient::new(&signer, &client, &miner, &accounts).to_delegate()); + let rpc = SigningQueueClient::new(&signer, &client, &miner, &accounts); + io.add_delegate(EthSigning::to_delegate(rpc)); + let rpc = SigningQueueClient::new(&signer, &client, &miner, &accounts); + io.add_delegate(ParitySigning::to_delegate(rpc)); - EthSigningTester { + SigningTester { signer: signer, client: client, miner: miner, @@ -58,8 +61,8 @@ impl Default for EthSigningTester { } } -fn eth_signing() -> EthSigningTester { - EthSigningTester::default() +fn eth_signing() -> SigningTester { + SigningTester::default() } #[test] @@ -101,7 +104,7 @@ fn should_post_sign_to_queue() { // when let request = r#"{ "jsonrpc": "2.0", - "method": "eth_postSign", + "method": "parity_postSign", "params": [ ""#.to_owned() + format!("0x{:?}", address).as_ref() + r#"", "0x0000000000000000000000000000000000000000000000000000000000000005" @@ -122,7 +125,7 @@ fn should_check_status_of_request() { let address = Address::random(); let request = r#"{ "jsonrpc": "2.0", - "method": "eth_postSign", + "method": "parity_postSign", "params": [ ""#.to_owned() + format!("0x{:?}", address).as_ref() + r#"", "0x0000000000000000000000000000000000000000000000000000000000000005" @@ -134,7 +137,7 @@ fn should_check_status_of_request() { // when let request = r#"{ "jsonrpc": "2.0", - "method": "eth_checkRequest", + "method": "parity_checkRequest", "params": ["0x1"], "id": 1 }"#; @@ -151,7 +154,7 @@ fn should_check_status_of_request_when_its_resolved() { let address = Address::random(); let request = r#"{ "jsonrpc": "2.0", - "method": "eth_postSign", + "method": "parity_postSign", "params": [ ""#.to_owned() + format!("0x{:?}", address).as_ref() + r#"", "0x0000000000000000000000000000000000000000000000000000000000000005" @@ -164,7 +167,7 @@ fn should_check_status_of_request_when_its_resolved() { // when let request = r#"{ "jsonrpc": "2.0", - "method": "eth_checkRequest", + "method": "parity_checkRequest", "params": ["0x1"], "id": 1 }"#; @@ -272,15 +275,15 @@ fn should_dispatch_transaction_if_account_is_unlock() { fn should_decrypt_message_if_account_is_unlocked() { // given let tester = eth_signing(); - let ethcore = ethcore::Dependencies::new(); - tester.io.add_delegate(ethcore.client(None).to_delegate()); + let parity = parity::Dependencies::new(); + tester.io.add_delegate(parity.client(None).to_delegate()); let (address, public) = tester.accounts.new_account_and_public("test").unwrap(); tester.accounts.unlock_account_permanently(address, "test".into()).unwrap(); // First encrypt message let request = format!("{}0x{:?}{}", - r#"{"jsonrpc": "2.0", "method": "ethcore_encryptMessage", "params":[""#, + r#"{"jsonrpc": "2.0", "method": "parity_encryptMessage", "params":[""#, public, r#"", "0x01020304"], "id": 1}"# ); @@ -288,7 +291,7 @@ fn should_decrypt_message_if_account_is_unlocked() { // then call decrypt let request = format!("{}{:?}{}{:?}{}", - r#"{"jsonrpc": "2.0", "method": "ethcore_decryptMessage", "params":["0x"#, + r#"{"jsonrpc": "2.0", "method": "parity_decryptMessage", "params":["0x"#, address, r#"","#, encrypted.result, @@ -311,7 +314,7 @@ fn should_add_decryption_to_the_queue() { // when let request = r#"{ "jsonrpc": "2.0", - "method": "ethcore_decryptMessage", + "method": "parity_decryptMessage", "params": ["0x"#.to_owned() + &format!("{:?}", acc.address()) + r#"", "0x012345"], "id": 1 diff --git a/rpc/src/v1/traits/eth_signing.rs b/rpc/src/v1/traits/eth_signing.rs index 1d6f6e501..80979a7db 100644 --- a/rpc/src/v1/traits/eth_signing.rs +++ b/rpc/src/v1/traits/eth_signing.rs @@ -23,41 +23,18 @@ pub trait EthSigning: Sized + Send + Sync + 'static { /// Signs the data with given address signature. fn sign(&self, _: Params, _: Ready); - /// Posts sign request asynchronously. - /// Will return a confirmation ID for later use with check_transaction. - fn post_sign(&self, _: Params) -> Result; - /// Sends transaction; will block for 20s to try to return the /// transaction hash. /// If it cannot yet be signed, it will return a transaction ID for /// later use with check_transaction. fn send_transaction(&self, _: Params, _: Ready); - /// Posts transaction asynchronously. - /// Will return a transaction ID for later use with check_transaction. - fn post_transaction(&self, _: Params) -> Result; - - /// Checks the progress of a previously posted request (transaction/sign). - /// Should be given a valid send_transaction ID. - /// Returns the transaction hash, the zero hash (not yet available), - /// or the signature, - /// or an error. - fn check_request(&self, _: Params) -> Result; - - /// Decrypt some ECIES-encrypted message. - /// First parameter is the address with which it is encrypted, second is the ciphertext. - fn decrypt_message(&self, _: Params, _: Ready); - /// Should be used to convert object to io delegate. fn to_delegate(self) -> IoDelegate { let mut delegate = IoDelegate::new(Arc::new(self)); delegate.add_async_method("eth_sign", EthSigning::sign); delegate.add_async_method("eth_sendTransaction", EthSigning::send_transaction); - delegate.add_async_method("ethcore_decryptMessage", EthSigning::decrypt_message); - delegate.add_method("eth_postSign", EthSigning::post_sign); - delegate.add_method("eth_postTransaction", EthSigning::post_transaction); - delegate.add_method("eth_checkRequest", EthSigning::check_request); delegate } } diff --git a/rpc/src/v1/traits/mod.rs b/rpc/src/v1/traits/mod.rs index ea0834463..86b55b7c0 100644 --- a/rpc/src/v1/traits/mod.rs +++ b/rpc/src/v1/traits/mod.rs @@ -20,9 +20,12 @@ pub mod web3; pub mod eth; pub mod eth_signing; pub mod net; +pub mod parity; +pub mod parity_accounts; +pub mod parity_set; +pub mod parity_signing; pub mod personal; -pub mod ethcore; -pub mod ethcore_set; +pub mod signer; pub mod traces; pub mod rpc; @@ -30,9 +33,12 @@ 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, PersonalAccounts, PersonalSigner}; -pub use self::ethcore::Ethcore; -pub use self::ethcore_set::EthcoreSet; +pub use self::parity::Parity; +pub use self::parity_accounts::ParityAccounts; +pub use self::parity_set::ParitySet; +pub use self::parity_signing::ParitySigning; +pub use self::personal::Personal; +pub use self::signer::Signer; pub use self::traces::Traces; pub use self::rpc::Rpc; diff --git a/rpc/src/v1/traits/ethcore.rs b/rpc/src/v1/traits/parity.rs similarity index 74% rename from rpc/src/v1/traits/ethcore.rs rename to rpc/src/v1/traits/parity.rs index b7ef2d151..23cf50ed3 100644 --- a/rpc/src/v1/traits/ethcore.rs +++ b/rpc/src/v1/traits/parity.rs @@ -14,128 +14,129 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -//! Ethcore-specific rpc interface. +//! Parity-specific rpc interface. use jsonrpc_core::Error; -use v1::helpers::auto_args::{Wrap, WrapAsync, Ready}; +use std::collections::BTreeMap; +use v1::helpers::auto_args::Wrap; use v1::types::{H160, H256, H512, U256, Bytes, Peers, Transaction, RpcSettings, Histogram}; build_rpc_trait! { - /// Ethcore-specific rpc interface. - pub trait Ethcore { + /// Parity-specific rpc interface. + pub trait Parity { /// Returns current transactions limit. - #[rpc(name = "ethcore_transactionsLimit")] + #[rpc(name = "parity_transactionsLimit")] fn transactions_limit(&self) -> Result; /// Returns mining extra data. - #[rpc(name = "ethcore_extraData")] + #[rpc(name = "parity_extraData")] fn extra_data(&self) -> Result; /// Returns mining gas floor target. - #[rpc(name = "ethcore_gasFloorTarget")] + #[rpc(name = "parity_gasFloorTarget")] fn gas_floor_target(&self) -> Result; /// Returns mining gas floor cap. - #[rpc(name = "ethcore_gasCeilTarget")] + #[rpc(name = "parity_gasCeilTarget")] fn gas_ceil_target(&self) -> Result; /// Returns minimal gas price for transaction to be included in queue. - #[rpc(name = "ethcore_minGasPrice")] + #[rpc(name = "parity_minGasPrice")] fn min_gas_price(&self) -> Result; /// Returns latest logs - #[rpc(name = "ethcore_devLogs")] + #[rpc(name = "parity_devLogs")] fn dev_logs(&self) -> Result, Error>; /// Returns logs levels - #[rpc(name = "ethcore_devLogsLevels")] + #[rpc(name = "parity_devLogsLevels")] fn dev_logs_levels(&self) -> Result; /// Returns chain name - #[rpc(name = "ethcore_netChain")] + #[rpc(name = "parity_netChain")] fn net_chain(&self) -> Result; /// Returns peers details - #[rpc(name = "ethcore_netPeers")] + #[rpc(name = "parity_netPeers")] fn net_peers(&self) -> Result; /// Returns network port - #[rpc(name = "ethcore_netPort")] + #[rpc(name = "parity_netPort")] fn net_port(&self) -> Result; /// Returns rpc settings - #[rpc(name = "ethcore_rpcSettings")] + #[rpc(name = "parity_rpcSettings")] fn rpc_settings(&self) -> Result; /// Returns node name - #[rpc(name = "ethcore_nodeName")] + #[rpc(name = "parity_nodeName")] fn node_name(&self) -> Result; /// Returns default extra data - #[rpc(name = "ethcore_defaultExtraData")] + #[rpc(name = "parity_defaultExtraData")] fn default_extra_data(&self) -> Result; /// Returns distribution of gas price in latest blocks. - #[rpc(name = "ethcore_gasPriceHistogram")] + #[rpc(name = "parity_gasPriceHistogram")] fn gas_price_histogram(&self) -> Result; /// Returns number of unsigned transactions waiting in the signer queue (if signer enabled) /// Returns error when signer is disabled - #[rpc(name = "ethcore_unsignedTransactionsCount")] + #[rpc(name = "parity_unsignedTransactionsCount")] fn unsigned_transactions_count(&self) -> Result; /// Returns a cryptographically random phrase sufficient for securely seeding a secret key. - #[rpc(name = "ethcore_generateSecretPhrase")] + #[rpc(name = "parity_generateSecretPhrase")] fn generate_secret_phrase(&self) -> Result; /// Returns whatever address would be derived from the given phrase if it were to seed a brainwallet. - #[rpc(name = "ethcore_phraseToAddress")] + #[rpc(name = "parity_phraseToAddress")] fn phrase_to_address(&self, String) -> Result; /// Returns the value of the registrar for this network. - #[rpc(name = "ethcore_registryAddress")] + #[rpc(name = "parity_registryAddress")] fn registry_address(&self) -> Result, Error>; /// Returns all addresses if Fat DB is enabled (`--fat-db`), or null if not. - #[rpc(name = "ethcore_listAccounts")] + #[rpc(name = "parity_listAccounts")] fn list_accounts(&self) -> Result>, Error>; /// Returns all storage keys of the given address (first parameter) if Fat DB is enabled (`--fat-db`), /// or null if not. - #[rpc(name = "ethcore_listStorageKeys")] + #[rpc(name = "parity_listStorageKeys")] fn list_storage_keys(&self, H160) -> Result>, Error>; /// Encrypt some data with a public key under ECIES. /// First parameter is the 512-byte destination public key, second is the message. - #[rpc(name = "ethcore_encryptMessage")] + #[rpc(name = "parity_encryptMessage")] fn encrypt_message(&self, H512, Bytes) -> Result; /// Returns all pending transactions from transaction queue. - #[rpc(name = "ethcore_pendingTransactions")] + #[rpc(name = "parity_pendingTransactions")] fn pending_transactions(&self) -> Result, Error>; - /// Hash a file content under given URL. - #[rpc(async, name = "ethcore_hashContent")] - fn hash_content(&self, Ready, String); - /// Returns current Trusted Signer port or an error if signer is disabled. - #[rpc(name = "ethcore_signerPort")] + #[rpc(name = "parity_signerPort")] fn signer_port(&self) -> Result; /// Returns current Dapps Server port or an error if dapps server is disabled. - #[rpc(name = "ethcore_dappsPort")] + #[rpc(name = "parity_dappsPort")] fn dapps_port(&self) -> Result; /// Returns next nonce for particular sender. Should include all transactions in the queue. - #[rpc(name = "ethcore_nextNonce")] + #[rpc(name = "parity_nextNonce")] fn next_nonce(&self, H160) -> Result; /// Get the mode. Results one of: "active", "passive", "dark", "offline". - #[rpc(name = "ethcore_mode")] + #[rpc(name = "parity_mode")] fn mode(&self) -> Result; /// Get the enode of this node. - #[rpc(name = "ethcore_enode")] + #[rpc(name = "parity_enode")] fn enode(&self) -> Result; + + /// Returns accounts information. + #[rpc(name = "parity_accounts")] + fn accounts(&self) -> Result>, Error>; } } diff --git a/rpc/src/v1/traits/parity_accounts.rs b/rpc/src/v1/traits/parity_accounts.rs new file mode 100644 index 000000000..0f62f59d1 --- /dev/null +++ b/rpc/src/v1/traits/parity_accounts.rs @@ -0,0 +1,77 @@ +// 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 . + +//! Parity Accounts-related rpc interface. +use std::collections::BTreeMap; +use jsonrpc_core::{Value, Error}; + +use v1::helpers::auto_args::Wrap; +use v1::types::{H160, H256}; + +build_rpc_trait! { + /// Personal Parity rpc interface. + pub trait ParityAccounts { + /// Returns accounts information. + #[rpc(name = "parity_accountsInfo")] + fn accounts_info(&self) -> Result, Error>; + + /// Creates new account from the given phrase using standard brainwallet mechanism. + /// Second parameter is password for the new account. + #[rpc(name = "parity_newAccountFromPhrase")] + fn new_account_from_phrase(&self, String, String) -> Result; + + /// Creates new account from the given JSON wallet. + /// Second parameter is password for the wallet and the new account. + #[rpc(name = "parity_newAccountFromWallet")] + fn new_account_from_wallet(&self, String, String) -> Result; + + /// Creates new account from the given raw secret. + /// Second parameter is password for the new account. + #[rpc(name = "parity_newAccountFromSecret")] + fn new_account_from_secret(&self, H256, String) -> Result; + + /// Returns true if given `password` would unlock given `account`. + /// Arguments: `account`, `password`. + #[rpc(name = "parity_testPassword")] + fn test_password(&self, H160, String) -> Result; + + /// Changes an account's password. + /// Arguments: `account`, `password`, `new_password`. + #[rpc(name = "parity_changePassword")] + fn change_password(&self, H160, String, String) -> Result; + + /// Set an account's name. + #[rpc(name = "parity_setAccountName")] + fn set_account_name(&self, H160, String) -> Result; + + /// Set an account's metadata string. + #[rpc(name = "parity_setAccountMeta")] + fn set_account_meta(&self, H160, String) -> Result; + + /// Returns accounts information. + #[rpc(name = "parity_setAccountVisiblity")] + fn set_account_visibility(&self, H160, H256, bool) -> Result; + + /// Imports a number of Geth accounts, with the list provided as the argument. + #[rpc(name = "parity_importGethAccounts")] + fn import_geth_accounts(&self, Vec) -> Result, Error>; + + /// Returns the accounts available for importing from Geth. + #[rpc(name = "parity_listGethAccounts")] + fn geth_accounts(&self) -> Result, Error>; + } +} + diff --git a/rpc/src/v1/traits/ethcore_set.rs b/rpc/src/v1/traits/parity_set.rs similarity index 70% rename from rpc/src/v1/traits/ethcore_set.rs rename to rpc/src/v1/traits/parity_set.rs index ad31a6e64..c83eff022 100644 --- a/rpc/src/v1/traits/ethcore_set.rs +++ b/rpc/src/v1/traits/parity_set.rs @@ -14,74 +14,78 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -//! Ethcore-specific rpc interface for operations altering the settings. +//! Parity-specific rpc interface for operations altering the settings. use jsonrpc_core::Error; -use v1::helpers::auto_args::Wrap; -use v1::types::{Bytes, H160, U256}; +use v1::helpers::auto_args::{Wrap, WrapAsync, Ready}; +use v1::types::{Bytes, H160, H256, U256}; build_rpc_trait! { - /// Ethcore-specific rpc interface for operations altering the settings. - pub trait EthcoreSet { + /// Parity-specific rpc interface for operations altering the settings. + pub trait ParitySet { /// Sets new minimal gas price for mined blocks. - #[rpc(name = "ethcore_setMinGasPrice")] + #[rpc(name = "parity_setMinGasPrice")] fn set_min_gas_price(&self, U256) -> Result; /// Sets new gas floor target for mined blocks. - #[rpc(name = "ethcore_setGasFloorTarget")] + #[rpc(name = "parity_setGasFloorTarget")] fn set_gas_floor_target(&self, U256) -> Result; /// Sets new gas ceiling target for mined blocks. - #[rpc(name = "ethcore_setGasCeilTarget")] + #[rpc(name = "parity_setGasCeilTarget")] fn set_gas_ceil_target(&self, U256) -> Result; /// Sets new extra data for mined blocks. - #[rpc(name = "ethcore_setExtraData")] + #[rpc(name = "parity_setExtraData")] fn set_extra_data(&self, Bytes) -> Result; /// Sets new author for mined block. - #[rpc(name = "ethcore_setAuthor")] + #[rpc(name = "parity_setAuthor")] fn set_author(&self, H160) -> Result; /// Sets the limits for transaction queue. - #[rpc(name = "ethcore_setTransactionsLimit")] + #[rpc(name = "parity_setTransactionsLimit")] fn set_transactions_limit(&self, usize) -> Result; /// Sets the maximum amount of gas a single transaction may consume. - #[rpc(name = "ethcore_setMaxTransactionGas")] + #[rpc(name = "parity_setMaxTransactionGas")] fn set_tx_gas_limit(&self, U256) -> Result; /// Add a reserved peer. - #[rpc(name = "ethcore_addReservedPeer")] + #[rpc(name = "parity_addReservedPeer")] fn add_reserved_peer(&self, String) -> Result; /// Remove a reserved peer. - #[rpc(name = "ethcore_removeReservedPeer")] + #[rpc(name = "parity_removeReservedPeer")] fn remove_reserved_peer(&self, String) -> Result; /// Drop all non-reserved peers. - #[rpc(name = "ethcore_dropNonReservedPeers")] + #[rpc(name = "parity_dropNonReservedPeers")] fn drop_non_reserved_peers(&self) -> Result; /// Accept non-reserved peers (default behavior) - #[rpc(name = "ethcore_acceptNonReservedPeers")] + #[rpc(name = "parity_acceptNonReservedPeers")] fn accept_non_reserved_peers(&self) -> Result; /// Start the network. - /// + /// /// Deprecated. Use `set_mode("active")` instead. - #[rpc(name = "ethcore_startNetwork")] + #[rpc(name = "parity_startNetwork")] fn start_network(&self) -> Result; /// Stop the network. /// /// Deprecated. Use `set_mode("offline")` instead. - #[rpc(name = "ethcore_stopNetwork")] + #[rpc(name = "parity_stopNetwork")] fn stop_network(&self) -> Result; /// Set the mode. Argument must be one of: "active", "passive", "dark", "offline". - #[rpc(name = "ethcore_setMode")] + #[rpc(name = "parity_setMode")] fn set_mode(&self, String) -> Result; + + /// Hash a file content under given URL. + #[rpc(async, name = "parity_hashContent")] + fn hash_content(&self, Ready, String); } -} \ No newline at end of file +} diff --git a/rpc/src/v1/traits/parity_signing.rs b/rpc/src/v1/traits/parity_signing.rs new file mode 100644 index 000000000..1c2778b31 --- /dev/null +++ b/rpc/src/v1/traits/parity_signing.rs @@ -0,0 +1,52 @@ +// 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 . + +//! ParitySigning rpc interface. +use std::sync::Arc; +use jsonrpc_core::*; + +/// Signing methods implementation relying on unlocked accounts. +pub trait ParitySigning: Sized + Send + Sync + 'static { + /// Posts sign request asynchronously. + /// Will return a confirmation ID for later use with check_transaction. + fn post_sign(&self, _: Params) -> Result; + + /// Posts transaction asynchronously. + /// Will return a transaction ID for later use with check_transaction. + fn post_transaction(&self, _: Params) -> Result; + + /// Checks the progress of a previously posted request (transaction/sign). + /// Should be given a valid send_transaction ID. + /// Returns the transaction hash, the zero hash (not yet available), + /// or the signature, + /// or an error. + fn check_request(&self, _: Params) -> Result; + + /// Decrypt some ECIES-encrypted message. + /// First parameter is the address with which it is encrypted, second is the ciphertext. + fn decrypt_message(&self, _: Params, _: Ready); + + /// 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("parity_postSign", ParitySigning::post_sign); + delegate.add_method("parity_postTransaction", ParitySigning::post_transaction); + delegate.add_method("parity_checkRequest", ParitySigning::check_request); + delegate.add_async_method("parity_decryptMessage", ParitySigning::decrypt_message); + + delegate + } +} diff --git a/rpc/src/v1/traits/personal.rs b/rpc/src/v1/traits/personal.rs index fe5c3cb02..42e61839e 100644 --- a/rpc/src/v1/traits/personal.rs +++ b/rpc/src/v1/traits/personal.rs @@ -15,11 +15,10 @@ // along with Parity. If not, see . //! Personal rpc interface. -use std::collections::BTreeMap; -use jsonrpc_core::{Value, Error}; +use jsonrpc_core::Error; use v1::helpers::auto_args::Wrap; -use v1::types::{H160, H256, U256, TransactionRequest, TransactionModification, ConfirmationRequest}; +use v1::types::{H160, H256, TransactionRequest}; build_rpc_trait! { /// Personal rpc interface. Safe (read-only) functions. @@ -28,91 +27,17 @@ build_rpc_trait! { #[rpc(name = "personal_listAccounts")] fn accounts(&self) -> Result, Error>; - /// Returns accounts information. - #[rpc(name = "personal_accountsInfo")] - fn accounts_info(&self) -> Result, Error>; - } -} - -build_rpc_trait! { - /// Personal rpc methods altering stored accounts or their settings. - pub trait PersonalAccounts { - /// Creates new account (it becomes new current unlocked account) /// Param is the password for the account. #[rpc(name = "personal_newAccount")] fn new_account(&self, String) -> Result; - /// Creates new account from the given phrase using standard brainwallet mechanism. - /// Second parameter is password for the new account. - #[rpc(name = "personal_newAccountFromPhrase")] - fn new_account_from_phrase(&self, String, String) -> Result; - - /// Creates new account from the given JSON wallet. - /// Second parameter is password for the wallet and the new account. - #[rpc(name = "personal_newAccountFromWallet")] - fn new_account_from_wallet(&self, String, String) -> Result; - - /// Creates new account from the given raw secret. - /// Second parameter is password for the new account. - #[rpc(name = "personal_newAccountFromSecret")] - fn new_account_from_secret(&self, H256, String) -> Result; - /// Unlocks specified account for use (can only be one unlocked account at one moment) #[rpc(name = "personal_unlockAccount")] fn unlock_account(&self, H160, String, Option) -> Result; - /// Returns true if given `password` would unlock given `account`. - /// Arguments: `account`, `password`. - #[rpc(name = "personal_testPassword")] - fn test_password(&self, H160, String) -> Result; - - /// Changes an account's password. - /// Arguments: `account`, `password`, `new_password`. - #[rpc(name = "personal_changePassword")] - fn change_password(&self, H160, String, String) -> Result; - /// Sends transaction and signs it in single call. The account is not unlocked in such case. #[rpc(name = "personal_signAndSendTransaction")] fn sign_and_send_transaction(&self, TransactionRequest, String) -> Result; - - /// Set an account's name. - #[rpc(name = "personal_setAccountName")] - fn set_account_name(&self, H160, String) -> Result; - - /// Set an account's metadata string. - #[rpc(name = "personal_setAccountMeta")] - fn set_account_meta(&self, H160, String) -> Result; - - /// Imports a number of Geth accounts, with the list provided as the argument. - #[rpc(name = "personal_importGethAccounts")] - fn import_geth_accounts(&self, Vec) -> Result, Error>; - - /// Returns the accounts available for importing from Geth. - #[rpc(name = "personal_listGethAccounts")] - fn geth_accounts(&self) -> Result, Error>; } } - -build_rpc_trait! { - /// Personal extension for confirmations rpc interface. - pub trait PersonalSigner { - - /// Returns a list of items to confirm. - #[rpc(name = "personal_requestsToConfirm")] - fn requests_to_confirm(&self) -> Result, Error>; - - /// Confirm specific request. - #[rpc(name = "personal_confirmRequest")] - fn confirm_request(&self, U256, TransactionModification, String) -> Result; - - /// Reject the confirmation request. - #[rpc(name = "personal_rejectRequest")] - fn reject_request(&self, U256) -> Result; - - /// Generates new authorization token. - #[rpc(name = "personal_generateAuthorizationToken")] - fn generate_token(&self) -> Result; - } -} - diff --git a/rpc/src/v1/traits/signer.rs b/rpc/src/v1/traits/signer.rs new file mode 100644 index 000000000..d80c6f1a6 --- /dev/null +++ b/rpc/src/v1/traits/signer.rs @@ -0,0 +1,44 @@ +// 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 . + +//! Parity Signer-related rpc interface. +use jsonrpc_core::{Value, Error}; + +use v1::helpers::auto_args::Wrap; +use v1::types::{U256, TransactionModification, ConfirmationRequest}; + + +build_rpc_trait! { + /// Signer extension for confirmations rpc interface. + pub trait Signer { + + /// Returns a list of items to confirm. + #[rpc(name = "signer_requestsToConfirm")] + fn requests_to_confirm(&self) -> Result, Error>; + + /// Confirm specific request. + #[rpc(name = "signer_confirmRequest")] + fn confirm_request(&self, U256, TransactionModification, String) -> Result; + + /// Reject the confirmation request. + #[rpc(name = "signer_rejectRequest")] + fn reject_request(&self, U256) -> Result; + + /// Generates new authorization token. + #[rpc(name = "signer_generateAuthorizationToken")] + fn generate_token(&self) -> Result; + } +} From d172c83c2604960744e5ef0156f1a2dcc08e2e05 Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Sun, 6 Nov 2016 12:10:21 +0000 Subject: [PATCH 33/62] [ci skip] js-precompiled 20161106-120902 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4826c68cd..e3398d615 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1249,7 +1249,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#1ffffece9a3c1ebdab7a00c9137b1dafd2c2b0f2" +source = "git+https://github.com/ethcore/js-precompiled.git#24f63e21f9461b43e4a1958d2b68be025cc6c788" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/js/package.json b/js/package.json index eabdcfa7e..bfc5a602b 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.13", + "version": "0.2.14", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", From d8a95084cb5bb81eb4663e80fa9ada3fc4b61637 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Wagner?= Date: Sun, 6 Nov 2016 17:31:26 +0100 Subject: [PATCH 34/62] Improve 'invalid raw key' error msg (#3219) --- js/src/modals/CreateAccount/NewAccount/newAccount.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/src/modals/CreateAccount/NewAccount/newAccount.js b/js/src/modals/CreateAccount/NewAccount/newAccount.js index 2fde79ca6..8c476634f 100644 --- a/js/src/modals/CreateAccount/NewAccount/newAccount.js +++ b/js/src/modals/CreateAccount/NewAccount/newAccount.js @@ -27,7 +27,7 @@ const ERRORS = { noName: 'you need to specify a valid name for the account', noPhrase: 'you need to specify the recovery phrase', noKey: 'you need to provide the raw private key', - invalidKey: 'the raw key needs to be hex, 64 characters in length', + invalidKey: 'the raw key needs to be hex, 64 characters in length and contain the prefix "0x"', invalidPassword: 'you need to specify a password >= 8 characters', noMatchPassword: 'the supplied passwords does not match' }; From 44266115b67fbd501ff8c68153656a57253c1439 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Sun, 6 Nov 2016 17:42:25 +0100 Subject: [PATCH 35/62] Fix /parity-utils/{web3,parity}.js webpack errors (#3221) * Don't override library builds * Don't override libraries on build * Enhance working blockNumber display --- js/package.json | 6 +++--- js/src/dev.parity.html | 25 ++++++++++++++++++++++++- js/src/dev.web3.html | 27 ++++++++++++++++++++++++++- js/webpack.config.js | 4 ---- 4 files changed, 53 insertions(+), 9 deletions(-) diff --git a/js/package.json b/js/package.json index bfc5a602b..a721394b8 100644 --- a/js/package.json +++ b/js/package.json @@ -25,16 +25,16 @@ "Promise" ], "scripts": { - "build": "npm run build:dll && npm run build:app && npm run build:lib", + "build": "npm run build:lib && npm run build:dll && npm run build:app", "build:app": "webpack --progress", "build:lib": "webpack --config webpack.libraries --progress", "build:dll": "webpack --config webpack.vendor --progress", - "ci:build": "npm run ci:build:dll && npm run ci:build:app && npm run ci:build:lib", + "ci:build": "npm run ci:build:lib && npm run ci:build:dll && npm run ci:build:app", "ci:build:app": "NODE_ENV=production webpack", "ci:build:lib": "NODE_ENV=production webpack --config webpack.libraries", "ci:build:dll": "NODE_ENV=production webpack --config webpack.vendor", "ci:build:npm": "NODE_ENV=production webpack --config webpack.npm", - "start": "npm install && npm run build:dll && npm run start:app", + "start": "npm install && npm run build:lib && npm run build:dll && npm run start:app", "start:app": "webpack-dev-server -d --history-api-fallback --open --hot --inline --progress --colors --port 3000", "clean": "rm -rf ./build ./coverage", "coveralls": "npm run testCoverage && coveralls < coverage/lcov.info", diff --git a/js/src/dev.parity.html b/js/src/dev.parity.html index 9cfe5ac18..504dfbb70 100644 --- a/js/src/dev.parity.html +++ b/js/src/dev.parity.html @@ -6,8 +6,31 @@ dev::Parity.js + + - +
+ best block #unknown +
+ diff --git a/js/src/dev.web3.html b/js/src/dev.web3.html index 93faba8e5..f4006160a 100644 --- a/js/src/dev.web3.html +++ b/js/src/dev.web3.html @@ -6,8 +6,33 @@ dev::Web3 + + - +
+ best block #unknown +
+ diff --git a/js/webpack.config.js b/js/webpack.config.js index 41da9aa25..a85fefa9f 100644 --- a/js/webpack.config.js +++ b/js/webpack.config.js @@ -41,10 +41,6 @@ module.exports = { 'registry': ['./dapps/registry.js'], 'signaturereg': ['./dapps/signaturereg.js'], 'tokenreg': ['./dapps/tokenreg.js'], - // library - 'inject': ['./web3.js'], - 'web3': ['./web3.js'], - 'parity': ['./parity.js'], // app 'index': ['./index.js'] }, From a4cc6058ddafdc1051b44f49f23e59bc95e6d875 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Sun, 6 Nov 2016 17:42:50 +0100 Subject: [PATCH 36/62] Don't push empty tags to input (#3222) --- js/src/modals/EditMeta/editMeta.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/js/src/modals/EditMeta/editMeta.js b/js/src/modals/EditMeta/editMeta.js index ad893aa17..7f0a061e2 100644 --- a/js/src/modals/EditMeta/editMeta.js +++ b/js/src/modals/EditMeta/editMeta.js @@ -99,11 +99,10 @@ export default class EditMeta extends Component { renderTags () { const { meta } = this.state; - const { tags } = meta || []; return ( Date: Sun, 6 Nov 2016 16:51:51 +0000 Subject: [PATCH 37/62] [ci skip] js-precompiled 20161106-165021 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e3398d615..8f00ffc19 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1249,7 +1249,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#24f63e21f9461b43e4a1958d2b68be025cc6c788" +source = "git+https://github.com/ethcore/js-precompiled.git#eb971d1c03969e4630b7fd99ab1b9e186f2b615f" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/js/package.json b/js/package.json index a721394b8..aba81f052 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.14", + "version": "0.2.15", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", From eb0c92e8c8a909d6902397ee6f22fbf6e595d752 Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Sun, 6 Nov 2016 17:01:11 +0000 Subject: [PATCH 38/62] [ci skip] js-precompiled 20161106-165954 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8f00ffc19..7fc30730e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1249,7 +1249,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#eb971d1c03969e4630b7fd99ab1b9e186f2b615f" +source = "git+https://github.com/ethcore/js-precompiled.git#6657aa3f8e915500ffa831b67aec3b09f7aaf1e1" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/js/package.json b/js/package.json index aba81f052..e003ac06a 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.15", + "version": "0.2.16", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", From 8cad185a57c5a6a774bbcdf62258100998566c51 Mon Sep 17 00:00:00 2001 From: arkpar Date: Mon, 7 Nov 2016 12:10:29 +0100 Subject: [PATCH 39/62] Set version to unstable --- util/src/misc.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/src/misc.rs b/util/src/misc.rs index 0f45dea66..b0452e85e 100644 --- a/util/src/misc.rs +++ b/util/src/misc.rs @@ -40,7 +40,7 @@ pub fn version() -> String { let date_dash = if commit_date.is_empty() { "" } else { "-" }; let env = Target::env(); let env_dash = if env.is_empty() { "" } else { "-" }; - format!("Parity/v{}-beta{}{}{}{}/{}-{}{}{}/rustc{}", env!("CARGO_PKG_VERSION"), sha3_dash, sha3, date_dash, commit_date, Target::arch(), Target::os(), env_dash, env, rustc_version()) + format!("Parity/v{}-unstable{}{}{}{}/{}-{}{}{}/rustc{}", env!("CARGO_PKG_VERSION"), sha3_dash, sha3, date_dash, commit_date, Target::arch(), Target::os(), env_dash, env, rustc_version()) } /// Get the standard version data for this software. From 6388c814ea55d2523fac767beb6535176605b23a Mon Sep 17 00:00:00 2001 From: GitLab Build Bot Date: Mon, 7 Nov 2016 11:33:25 +0000 Subject: [PATCH 40/62] [ci skip] js-precompiled 20161107-113204 --- Cargo.lock | 2 +- js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7fc30730e..62d622938 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1249,7 +1249,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#6657aa3f8e915500ffa831b67aec3b09f7aaf1e1" +source = "git+https://github.com/ethcore/js-precompiled.git#3832db910dff4c0038c5a74655b282d93fd017b9" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/js/package.json b/js/package.json index e003ac06a..a035ae526 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.16", + "version": "0.2.17", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", From 834c0703fe4342e33a5cb84cf06dc9ca2caa6ca9 Mon Sep 17 00:00:00 2001 From: "Denis S. Soldatov aka General-Beck" Date: Mon, 7 Nov 2016 21:07:37 +0700 Subject: [PATCH 41/62] Update gitlab-ci Add i686 cache optimization add triggers --- .gitlab-ci.yml | 52 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7190d887b..dca1d51d7 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -10,7 +10,7 @@ variables: CARGOFLAGS: "" NIGHTLY: "nigtly" cache: - key: "$CI_BUILD_REF_NAME" + key: "$CI_BUILD_STAGE/$CI_BUILD_REF_NAME" untracked: true linux-stable: stage: build @@ -20,6 +20,7 @@ linux-stable: - beta - tags - stable + - triggers script: - cargo build --release $CARGOFLAGS - strip target/release/parity @@ -50,6 +51,7 @@ linux-stable-14.04: - beta - tags - stable + - triggers script: - cargo build --release $CARGOFLAGS - strip target/release/parity @@ -80,6 +82,7 @@ linux-beta: - beta - tags - stable + - triggers script: - cargo build --release $CARGOFLAGS - strip target/release/parity @@ -99,6 +102,7 @@ linux-nightly: - beta - tags - stable + - triggers script: - cargo build --release $CARGOFLAGS - strip target/release/parity @@ -118,6 +122,7 @@ linux-centos: - beta - tags - stable + - triggers script: - export CXX="g++" - export CC="gcc" @@ -135,6 +140,40 @@ linux-centos: paths: - target/release/parity name: "x86_64-unknown-centos-gnu_parity" +linux-i686: + stage: build + image: ethcore/rust-i686:latest + only: + - $NIGHTLY + - beta + - tags + - stable + - triggers + script: + - export HOST_CC=gcc + - export HOST_CXX=g++ + - cargo build --target i686-unknown-linux-gnu --release $CARGOFLAGS + - strip target/i686-unknown-linux-gnu/release/parity + - md5sum target/i686-unknown-linux-gnu/release/parity >> parity.md5 + - sh scripts/deb-build.sh i386 + - cp target/i686-unknown-linux-gnu/release/parity deb/usr/bin/parity + - export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n") + - dpkg-deb -b deb "parity_"$VER"_i386.deb" + - md5sum "parity_"$VER"_i386.deb" >> "parity_"$VER"_i386.deb.md5" + - aws configure set aws_access_key_id $s3_key + - aws configure set aws_secret_access_key $s3_secret + - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/i686-unknown-linux-gnu/parity --body target/i686-unknown-linux-gnu/release/parity + - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/i686-unknown-linux-gnu/parity.md5 --body parity.md5 + - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/i686-unknown-linux-gnu/"parity_"$VER"_i386.deb" --body "parity_"$VER"_i386.deb" + - aws s3api put-object --bucket builds-parity --key $CI_BUILD_REF_NAME/i686-unknown-linux-gnu/"parity_"$VER"_i386.deb.md5" --body "parity_"$VER"_i386.deb.md5" + tags: + - rust + - rust-i686 + artifacts: + paths: + - target/i686-unknown-linux-gnu/release/parity + name: "i686-unknown-linux-gnu" + allow_failure: true linux-armv7: stage: build image: ethcore/rust-armv7:latest @@ -143,6 +182,7 @@ linux-armv7: - beta - tags - stable + - triggers script: - export CC=arm-linux-gnueabihf-gcc - export CXX=arm-linux-gnueabihf-g++ @@ -183,6 +223,7 @@ linux-arm: - beta - tags - stable + - triggers script: - export CC=arm-linux-gnueabihf-gcc - export CXX=arm-linux-gnueabihf-g++ @@ -223,6 +264,7 @@ linux-armv6: - beta - tags - stable + - triggers script: - export CC=arm-linux-gnueabi-gcc - export CXX=arm-linux-gnueabi-g++ @@ -256,6 +298,7 @@ linux-aarch64: - beta - tags - stable + - triggers script: - export CC=aarch64-linux-gnu-gcc - export CXX=aarch64-linux-gnu-g++ @@ -295,6 +338,7 @@ darwin: - beta - tags - stable + - triggers script: - cargo build --release $CARGOFLAGS - rm -rf parity.md5 @@ -310,12 +354,16 @@ darwin: - target/release/parity name: "x86_64-apple-darwin_parity" windows: + cache: + key: "%CI_BUILD_STAGE%/%CI_BUILD_REF_NAME%" + untracked: true stage: build only: - $NIGHTLY - beta - tags - stable + - triggers script: - set INCLUDE=C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Include;C:\vs2015\VC\include;C:\Program Files (x86)\Windows Kits\10\Include\10.0.10240.0\ucrt - set LIB=C:\vs2015\VC\lib;C:\Program Files (x86)\Windows Kits\10\Lib\10.0.10240.0\ucrt\x64 @@ -365,6 +413,7 @@ test-darwin: - beta - tags - stable + - triggers before_script: - git submodule update --init --recursive script: @@ -419,6 +468,7 @@ js-release: - beta - stable - tags + - triggers image: ethcore/rust:stable before_script: - ./js/scripts/install-deps.sh From f0054aa201f1fbfcfea7ba84610911e6ac5f53fc Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Mon, 7 Nov 2016 15:22:46 +0100 Subject: [PATCH 42/62] Add store for dapps state (#3211) * Add mobx * Use mobx store for dapps * Cleanup hidden reads * Remove (now) unused hidden.js * _ denotes internal functions * s/visibleApps/visible/ * AddDapps now use the mobx store as well * Move modalOpen state to store * Simplify * Complete master merge * Remove extra indirection * Remove unneeded check * Readability improvements * Remove final debug info --- js/package.json | 5 +- js/src/views/Dapp/dapp.js | 42 +---- js/src/views/Dapps/AddDapps/AddDapps.js | 53 +++--- js/src/views/Dapps/Summary/summary.js | 19 +- js/src/views/Dapps/dapps.js | 119 ++----------- js/src/views/Dapps/dappsStore.js | 222 ++++++++++++++++++++++++ js/src/views/Dapps/hidden.js | 35 ---- js/src/views/Dapps/registry.js | 151 ---------------- 8 files changed, 289 insertions(+), 357 deletions(-) create mode 100644 js/src/views/Dapps/dappsStore.js delete mode 100644 js/src/views/Dapps/hidden.js delete mode 100644 js/src/views/Dapps/registry.js diff --git a/js/package.json b/js/package.json index a035ae526..842a81e6f 100644 --- a/js/package.json +++ b/js/package.json @@ -46,7 +46,7 @@ "devDependencies": { "babel-cli": "^6.10.1", "babel-core": "^6.10.4", - "babel-eslint": "^6.1.2", + "babel-eslint": "^7.1.0", "babel-loader": "^6.2.3", "babel-plugin-lodash": "^3.2.2", "babel-plugin-transform-class-properties": "^6.11.5", @@ -128,6 +128,9 @@ "marked": "^0.3.6", "material-ui": "^0.16.1", "material-ui-chip-input": "^0.8.0", + "mobx": "^2.6.1", + "mobx-react": "^3.5.8", + "mobx-react-devtools": "^4.2.9", "moment": "^2.14.1", "qs": "^6.3.0", "react": "^15.2.1", diff --git a/js/src/views/Dapp/dapp.js b/js/src/views/Dapp/dapp.js index 7f5f36d1d..3e5b206ad 100644 --- a/js/src/views/Dapp/dapp.js +++ b/js/src/views/Dapp/dapp.js @@ -15,12 +15,13 @@ // along with Parity. If not, see . import React, { Component, PropTypes } from 'react'; +import { observer } from 'mobx-react'; -import Contracts from '../../contracts'; -import { fetchAvailable } from '../Dapps/registry'; +import DappsStore from '../Dapps/dappsStore'; import styles from './dapp.css'; +@observer export default class Dapp extends Component { static contextTypes = { api: PropTypes.object.isRequired @@ -30,17 +31,12 @@ export default class Dapp extends Component { params: PropTypes.object }; - state = { - app: null - } - - componentWillMount () { - this.lookup(); - } + store = new DappsStore(this.context.api); render () { - const { app } = this.state; const { dappsUrl } = this.context.api; + const { id } = this.props.params; + const app = this.store.apps.find((app) => app.id === id); if (!app) { return null; @@ -76,30 +72,4 @@ export default class Dapp extends Component { ); } - - lookup () { - const { api } = this.context; - const { id } = this.props.params; - const { dappReg } = Contracts.get(); - - fetchAvailable(api) - .then((available) => { - return available.find((app) => app.id === id); - }) - .then((app) => { - if (app.type !== 'network') { - return app; - } - - return dappReg - .getContent(app.id) - .then((contentHash) => { - app.contentHash = api.util.bytesToHex(contentHash).substr(2); - return app; - }); - }) - .then((app) => { - this.setState({ app }); - }); - } } diff --git a/js/src/views/Dapps/AddDapps/AddDapps.js b/js/src/views/Dapps/AddDapps/AddDapps.js index 208a65004..38bb64792 100644 --- a/js/src/views/Dapps/AddDapps/AddDapps.js +++ b/js/src/views/Dapps/AddDapps/AddDapps.js @@ -15,6 +15,7 @@ // along with Parity. If not, see . import React, { Component, PropTypes } from 'react'; +import { observer } from 'mobx-react'; import DoneIcon from 'material-ui/svg-icons/action/done'; import { List, ListItem } from 'material-ui/List'; import Checkbox from 'material-ui/Checkbox'; @@ -23,57 +24,67 @@ import { Modal, Button } from '../../../ui'; import styles from './AddDapps.css'; +@observer export default class AddDapps extends Component { static propTypes = { - available: PropTypes.array.isRequired, - hidden: PropTypes.array.isRequired, - open: PropTypes.bool.isRequired, - onHideApp: PropTypes.func.isRequired, - onShowApp: PropTypes.func.isRequired, - onClose: PropTypes.func.isRequired + store: PropTypes.object.isRequired }; render () { - const { onClose, open, available } = this.props; + const { store } = this.props; + + if (!store.modalOpen) { + return null; + } return ( } /> +