Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
987390fb7d | ||
|
|
797f23f30b | ||
|
|
c1da49bbc4 | ||
|
|
1164193019 | ||
|
|
1c217cbf2b | ||
|
|
d24f71f150 | ||
|
|
4c66a021ec | ||
|
|
ccc57328e2 | ||
|
|
77240b6e5a | ||
|
|
2e428ddd58 | ||
|
|
cd4081c149 | ||
|
|
428342d69d | ||
|
|
8ee9b262f9 | ||
|
|
e2bc251ff4 | ||
|
|
d15372c8a6 | ||
|
|
bcf2245110 | ||
|
|
36f74fdf0d | ||
|
|
45402544d3 | ||
|
|
ab236690df | ||
|
|
34e81101d7 | ||
|
|
07d4b9bbc9 | ||
|
|
d25a20b274 | ||
|
|
70d17ce1d8 | ||
|
|
3b14bcf29a | ||
|
|
4fc69e11d6 | ||
|
|
8e4df824b0 | ||
|
|
e1f2e840cb | ||
|
|
2a36a89c36 | ||
|
|
b643f4011d | ||
|
|
814304bdd7 | ||
|
|
f90607302d | ||
|
|
af826f877e | ||
|
|
f9a0aa0022 | ||
|
|
c4196a5de3 | ||
|
|
3b56e8eded | ||
|
|
b3ccbbe913 | ||
|
|
fe0f037f23 |
@@ -1,8 +1,8 @@
|
||||
stages:
|
||||
- test
|
||||
- js-build
|
||||
- build
|
||||
- push-release
|
||||
- build
|
||||
variables:
|
||||
GIT_DEPTH: "3"
|
||||
SIMPLECOV: "true"
|
||||
@@ -26,14 +26,14 @@ linux-stable:
|
||||
- cargo build -j $(nproc) --release -p ethstore
|
||||
- cargo build -j $(nproc) --release -p ethkey
|
||||
- strip target/release/parity
|
||||
- strip target/release/evm
|
||||
- strip target/release/parity-evm
|
||||
- strip target/release/ethstore
|
||||
- strip target/release/ethkey
|
||||
- export SHA3=$(target/release/parity tools hash target/release/parity)
|
||||
- md5sum target/release/parity > parity.md5
|
||||
- sh scripts/deb-build.sh amd64
|
||||
- cp target/release/parity deb/usr/bin/parity
|
||||
- cp target/release/evm deb/usr/bin/evm
|
||||
- cp target/release/parity-evm deb/usr/bin/parity-evm
|
||||
- cp target/release/ethstore deb/usr/bin/ethstore
|
||||
- cp target/release/ethkey deb/usr/bin/ethkey
|
||||
- export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n")
|
||||
@@ -55,9 +55,9 @@ linux-stable:
|
||||
artifacts:
|
||||
paths:
|
||||
- target/release/parity
|
||||
- target/release/parity/evmbin
|
||||
- target/release/parity/ethstore
|
||||
- target/release/parity/ethkey
|
||||
- target/release/parity-evm
|
||||
- target/release/ethstore
|
||||
- target/release/ethkey
|
||||
name: "stable-x86_64-unknown-linux-gnu_parity"
|
||||
linux-stable-debian:
|
||||
stage: build
|
||||
@@ -73,14 +73,14 @@ linux-stable-debian:
|
||||
- cargo build -j $(nproc) --release -p ethstore
|
||||
- cargo build -j $(nproc) --release -p ethkey
|
||||
- strip target/release/parity
|
||||
- strip target/release/evm
|
||||
- strip target/release/parity-evm
|
||||
- strip target/release/ethstore
|
||||
- strip target/release/ethkey
|
||||
- export SHA3=$(target/release/parity tools hash target/release/parity)
|
||||
- md5sum target/release/parity > parity.md5
|
||||
- sh scripts/deb-build.sh amd64
|
||||
- cp target/release/parity deb/usr/bin/parity
|
||||
- cp target/release/evm deb/usr/bin/evm
|
||||
- cp target/release/parity-evm deb/usr/bin/parity-evm
|
||||
- cp target/release/ethstore deb/usr/bin/ethstore
|
||||
- cp target/release/ethkey deb/usr/bin/ethkey
|
||||
- export VER=$(grep -m 1 version Cargo.toml | awk '{print $3}' | tr -d '"' | tr -d "\n")
|
||||
@@ -460,9 +460,8 @@ windows:
|
||||
- zip win-installer.zip InstallParity.exe InstallParity.exe.md5
|
||||
- md5sums win-installer.zip > win-installer.zip.md5
|
||||
- cd ..\target\release\
|
||||
- md5sums parity.exe parity.pdb > parity.md5
|
||||
- md5sums parity.exe > parity.exe.md5
|
||||
- zip parity.zip parity.exe parity.pdb parity.md5
|
||||
- zip parity.zip parity.exe parity.md5
|
||||
- md5sums parity.zip > parity.zip.md5
|
||||
- cd ..\..
|
||||
- aws configure set aws_access_key_id %s3_key%
|
||||
@@ -488,7 +487,6 @@ windows:
|
||||
artifacts:
|
||||
paths:
|
||||
- target/release/parity.exe
|
||||
- target/release/parity.pdb
|
||||
- nsis/InstallParity.exe
|
||||
name: "x86_64-pc-windows-msvc_parity"
|
||||
docker-build:
|
||||
|
||||
184
Cargo.lock
generated
184
Cargo.lock
generated
@@ -1,6 +1,6 @@
|
||||
[root]
|
||||
name = "parity"
|
||||
version = "1.6.0"
|
||||
version = "1.6.5"
|
||||
dependencies = [
|
||||
"ansi_term 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"app_dirs 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -23,7 +23,7 @@ dependencies = [
|
||||
"ethcore-secretstore 1.0.0",
|
||||
"ethcore-signer 1.6.0",
|
||||
"ethcore-stratum 1.6.0",
|
||||
"ethcore-util 1.6.0",
|
||||
"ethcore-util 1.6.5",
|
||||
"ethsync 1.6.0",
|
||||
"evmbin 0.1.0",
|
||||
"fdlimit 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -204,7 +204,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"integer-encoding 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"multibase 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"multihash 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"multihash 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -253,7 +253,7 @@ version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"core-foundation-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -261,7 +261,7 @@ name = "core-foundation-sys"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -284,7 +284,7 @@ version = "1.1.1"
|
||||
source = "git+https://github.com/ethcore/rust-ctrlc.git#f4927770f89eca80ec250911eea3adcbf579ac48"
|
||||
dependencies = [
|
||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
@@ -293,7 +293,7 @@ name = "daemonize"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -348,7 +348,7 @@ source = "git+https://github.com/ethcore/rust-secp256k1#98ad9b9ecae44a563efdd642
|
||||
dependencies = [
|
||||
"arrayvec 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"gcc 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
@@ -394,13 +394,14 @@ dependencies = [
|
||||
"ethcore-ipc-codegen 1.6.0",
|
||||
"ethcore-ipc-nano 1.6.0",
|
||||
"ethcore-stratum 1.6.0",
|
||||
"ethcore-util 1.6.0",
|
||||
"ethcore-util 1.6.5",
|
||||
"ethjson 0.1.0",
|
||||
"ethkey 0.2.0",
|
||||
"ethstore 0.1.0",
|
||||
"evmjit 1.6.0",
|
||||
"hardware-wallet 1.6.0",
|
||||
"hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)",
|
||||
"itertools 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"linked-hash-map 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -422,7 +423,7 @@ version = "0.1.2"
|
||||
dependencies = [
|
||||
"bigint 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
@@ -443,7 +444,7 @@ dependencies = [
|
||||
"env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ethcore-devtools 1.6.0",
|
||||
"ethcore-rpc 1.6.0",
|
||||
"ethcore-util 1.6.0",
|
||||
"ethcore-util 1.6.5",
|
||||
"fetch 0.1.0",
|
||||
"futures 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)",
|
||||
@@ -491,8 +492,8 @@ name = "ethcore-ipc"
|
||||
version = "1.6.0"
|
||||
dependencies = [
|
||||
"ethcore-devtools 1.6.0",
|
||||
"ethcore-util 1.6.0",
|
||||
"nanomsg 0.5.1 (git+https://github.com/ethcore/nanomsg.rs.git)",
|
||||
"ethcore-util 1.6.5",
|
||||
"nanomsg 0.5.1 (git+https://github.com/ethcore/nanomsg.rs.git?branch=parity-1.7)",
|
||||
"semver 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
@@ -515,7 +516,7 @@ dependencies = [
|
||||
"ethcore-ipc-codegen 1.6.0",
|
||||
"ethcore-ipc-nano 1.6.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)",
|
||||
"nanomsg 0.5.1 (git+https://github.com/ethcore/nanomsg.rs.git?branch=parity-1.7)",
|
||||
"semver 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
@@ -527,7 +528,7 @@ dependencies = [
|
||||
"ethcore-ipc 1.6.0",
|
||||
"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)",
|
||||
"nanomsg 0.5.1 (git+https://github.com/ethcore/nanomsg.rs.git)",
|
||||
"nanomsg 0.5.1 (git+https://github.com/ethcore/nanomsg.rs.git?branch=parity-1.7)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -538,9 +539,9 @@ dependencies = [
|
||||
"ethcore-ipc 1.6.0",
|
||||
"ethcore-ipc-codegen 1.6.0",
|
||||
"ethcore-ipc-nano 1.6.0",
|
||||
"ethcore-util 1.6.0",
|
||||
"ethcore-util 1.6.5",
|
||||
"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)",
|
||||
"nanomsg 0.5.1 (git+https://github.com/ethcore/nanomsg.rs.git?branch=parity-1.7)",
|
||||
"semver 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
@@ -553,7 +554,7 @@ dependencies = [
|
||||
"ethcore-ipc 1.6.0",
|
||||
"ethcore-ipc-codegen 1.6.0",
|
||||
"ethcore-network 1.6.0",
|
||||
"ethcore-util 1.6.0",
|
||||
"ethcore-util 1.6.5",
|
||||
"futures 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"itertools 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -569,7 +570,7 @@ name = "ethcore-logger"
|
||||
version = "1.6.0"
|
||||
dependencies = [
|
||||
"env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ethcore-util 1.6.0",
|
||||
"ethcore-util 1.6.5",
|
||||
"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)",
|
||||
@@ -585,11 +586,11 @@ dependencies = [
|
||||
"bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ethcore-devtools 1.6.0",
|
||||
"ethcore-io 1.6.0",
|
||||
"ethcore-util 1.6.0",
|
||||
"ethcore-util 1.6.5",
|
||||
"ethcrypto 0.1.0",
|
||||
"ethkey 0.2.0",
|
||||
"igd 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"mio 0.6.1 (git+https://github.com/ethcore/mio)",
|
||||
"parking_lot 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -613,7 +614,7 @@ dependencies = [
|
||||
"ethcore-io 1.6.0",
|
||||
"ethcore-ipc 1.6.0",
|
||||
"ethcore-light 1.6.0",
|
||||
"ethcore-util 1.6.0",
|
||||
"ethcore-util 1.6.5",
|
||||
"ethcrypto 0.1.0",
|
||||
"ethjson 0.1.0",
|
||||
"ethkey 0.2.0",
|
||||
@@ -648,7 +649,7 @@ dependencies = [
|
||||
"ethcore-ipc 1.6.0",
|
||||
"ethcore-ipc-codegen 1.6.0",
|
||||
"ethcore-ipc-nano 1.6.0",
|
||||
"ethcore-util 1.6.0",
|
||||
"ethcore-util 1.6.5",
|
||||
"ethcrypto 0.1.0",
|
||||
"ethkey 0.2.0",
|
||||
"hyper 0.10.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -666,7 +667,7 @@ dependencies = [
|
||||
"ethcore-devtools 1.6.0",
|
||||
"ethcore-io 1.6.0",
|
||||
"ethcore-rpc 1.6.0",
|
||||
"ethcore-util 1.6.0",
|
||||
"ethcore-util 1.6.5",
|
||||
"jsonrpc-core 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)",
|
||||
"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)",
|
||||
@@ -685,7 +686,7 @@ dependencies = [
|
||||
"ethcore-ipc 1.6.0",
|
||||
"ethcore-ipc-codegen 1.6.0",
|
||||
"ethcore-ipc-nano 1.6.0",
|
||||
"ethcore-util 1.6.0",
|
||||
"ethcore-util 1.6.5",
|
||||
"futures 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"jsonrpc-core 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)",
|
||||
"jsonrpc-macros 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)",
|
||||
@@ -699,7 +700,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ethcore-util"
|
||||
version = "1.6.0"
|
||||
version = "1.6.5"
|
||||
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)",
|
||||
@@ -713,7 +714,7 @@ dependencies = [
|
||||
"heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"itertools 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lru-cache 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"parking_lot 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -748,7 +749,7 @@ dependencies = [
|
||||
name = "ethjson"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"ethcore-util 1.6.0",
|
||||
"ethcore-util 1.6.5",
|
||||
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_derive 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -776,12 +777,12 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"docopt 0.6.80 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ethcore-devtools 1.6.0",
|
||||
"ethcore-util 1.6.0",
|
||||
"ethcore-util 1.6.5",
|
||||
"ethcrypto 0.1.0",
|
||||
"ethkey 0.2.0",
|
||||
"itertools 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.21 (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.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -809,7 +810,7 @@ dependencies = [
|
||||
"ethcore-ipc-nano 1.6.0",
|
||||
"ethcore-light 1.6.0",
|
||||
"ethcore-network 1.6.0",
|
||||
"ethcore-util 1.6.0",
|
||||
"ethcore-util 1.6.5",
|
||||
"ethkey 0.2.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)",
|
||||
@@ -826,7 +827,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"docopt 0.6.80 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ethcore 1.6.0",
|
||||
"ethcore-util 1.6.0",
|
||||
"ethcore-util 1.6.5",
|
||||
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
@@ -842,7 +843,7 @@ name = "fdlimit"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -862,7 +863,7 @@ name = "flate2"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"miniz-sys 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
@@ -937,7 +938,7 @@ version = "0.3.1"
|
||||
source = "git+https://github.com/ethcore/hidapi-rs#9a127c1dca7e327e4fdd428406a76c9f5ef48563"
|
||||
dependencies = [
|
||||
"gcc 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1054,7 +1055,7 @@ version = "1.6.0"
|
||||
dependencies = [
|
||||
"ethcore-ipc 1.6.0",
|
||||
"ethcore-ipc-codegen 1.6.0",
|
||||
"ethcore-util 1.6.0",
|
||||
"ethcore-util 1.6.5",
|
||||
"semver 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
@@ -1064,7 +1065,7 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
@@ -1173,7 +1174,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.16"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
@@ -1182,7 +1183,7 @@ version = "0.3.0"
|
||||
source = "git+https://github.com/ethcore/libusb-rs#32bacf61abd981d5cbd4a8fecca5a2dc0b762a96"
|
||||
dependencies = [
|
||||
"bit-set 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libusb-sys 0.2.3 (git+https://github.com/ethcore/libusb-sys)",
|
||||
]
|
||||
|
||||
@@ -1192,7 +1193,7 @@ version = "0.2.3"
|
||||
source = "git+https://github.com/ethcore/libusb-sys#c10b1180646c9dc3f23a9b6bb825abcd3b7487ce"
|
||||
dependencies = [
|
||||
"gcc 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1228,7 +1229,7 @@ name = "memchr"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1256,7 +1257,7 @@ version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"gcc 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1265,7 +1266,7 @@ version = "0.5.1"
|
||||
source = "git+https://github.com/ethcore/mio?branch=v0.5.x#3842d3b250ffd7bd9b16f9586b875ddcbac2b0dd"
|
||||
dependencies = [
|
||||
"bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"miow 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"net2 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -1281,7 +1282,7 @@ version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"miow 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"net2 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -1297,7 +1298,7 @@ version = "0.6.0-dev"
|
||||
source = "git+https://github.com/ethcore/mio?branch=timer-fix#31eccc40ece3d47abaefaf23bb2114033175b972"
|
||||
dependencies = [
|
||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"miow 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"net2 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -1313,7 +1314,7 @@ source = "git+https://github.com/ethcore/mio#ef182bae193a9c7457cd2cf661fcaffb226
|
||||
dependencies = [
|
||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazycell 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"miow 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"net2 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -1329,7 +1330,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazycell 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"miow 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"net2 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -1369,29 +1370,29 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "multihash"
|
||||
version = "0.5.0"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"ring 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ring 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"tiny-keccak 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nanomsg"
|
||||
version = "0.5.1"
|
||||
source = "git+https://github.com/ethcore/nanomsg.rs.git#c40fe442c9afaea5b38009a3d992ca044dcceb00"
|
||||
source = "git+https://github.com/ethcore/nanomsg.rs.git?branch=parity-1.7#673b79beef6e149273899850d7692335a481a920"
|
||||
dependencies = [
|
||||
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"nanomsg-sys 0.5.0 (git+https://github.com/ethcore/nanomsg.rs.git)",
|
||||
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"nanomsg-sys 0.5.0 (git+https://github.com/ethcore/nanomsg.rs.git?branch=parity-1.7)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nanomsg-sys"
|
||||
version = "0.5.0"
|
||||
source = "git+https://github.com/ethcore/nanomsg.rs.git#c40fe442c9afaea5b38009a3d992ca044dcceb00"
|
||||
source = "git+https://github.com/ethcore/nanomsg.rs.git?branch=parity-1.7#673b79beef6e149273899850d7692335a481a920"
|
||||
dependencies = [
|
||||
"gcc 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1413,7 +1414,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
@@ -1424,7 +1425,7 @@ version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1434,7 +1435,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"semver 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -1447,7 +1448,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"semver 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -1537,7 +1538,7 @@ name = "num_cpus"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1545,7 +1546,7 @@ name = "num_cpus"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1577,7 +1578,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"openssl-sys 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
@@ -1587,7 +1588,7 @@ version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"gdi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"pkg-config 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"user32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
@@ -1621,7 +1622,7 @@ name = "parity-hash-fetch"
|
||||
version = "1.6.0"
|
||||
dependencies = [
|
||||
"ethabi 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ethcore-util 1.6.0",
|
||||
"ethcore-util 1.6.5",
|
||||
"fetch 0.1.0",
|
||||
"futures 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -1638,11 +1639,11 @@ version = "1.6.0"
|
||||
dependencies = [
|
||||
"cid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"ethcore 1.6.0",
|
||||
"ethcore-util 1.6.0",
|
||||
"ethcore-util 1.6.5",
|
||||
"hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)",
|
||||
"jsonrpc-http-server 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)",
|
||||
"mime 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"multihash 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"multihash 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rlp 0.1.0",
|
||||
]
|
||||
|
||||
@@ -1652,7 +1653,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"ethcore 1.6.0",
|
||||
"ethcore-io 1.6.0",
|
||||
"ethcore-util 1.6.0",
|
||||
"ethcore-util 1.6.5",
|
||||
"ethkey 0.2.0",
|
||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rlp 0.1.0",
|
||||
@@ -1675,7 +1676,7 @@ version = "1.4.0"
|
||||
dependencies = [
|
||||
"ethcore-rpc 1.6.0",
|
||||
"ethcore-signer 1.6.0",
|
||||
"ethcore-util 1.6.0",
|
||||
"ethcore-util 1.6.5",
|
||||
"futures 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"jsonrpc-core 6.0.0 (git+https://github.com/ethcore/jsonrpc.git)",
|
||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -1692,7 +1693,7 @@ name = "parity-ui"
|
||||
version = "1.6.0"
|
||||
dependencies = [
|
||||
"parity-ui-dev 1.4.0",
|
||||
"parity-ui-precompiled 1.4.0 (git+https://github.com/ethcore/js-precompiled.git)",
|
||||
"parity-ui-precompiled 1.4.0 (git+https://github.com/ethcore/js-precompiled.git?branch=beta)",
|
||||
"rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
@@ -1706,7 +1707,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "parity-ui-precompiled"
|
||||
version = "1.4.0"
|
||||
source = "git+https://github.com/ethcore/js-precompiled.git#94da980fb81d6145e38ca87d37a9137e8440086a"
|
||||
source = "git+https://github.com/ethcore/js-precompiled.git?branch=beta#ad0cbde4b11d76174c7dbfdbb8dc94ae7ae525e4"
|
||||
dependencies = [
|
||||
"parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
@@ -1719,7 +1720,7 @@ dependencies = [
|
||||
"ethcore 1.6.0",
|
||||
"ethcore-ipc 1.6.0",
|
||||
"ethcore-ipc-codegen 1.6.0",
|
||||
"ethcore-util 1.6.0",
|
||||
"ethcore-util 1.6.5",
|
||||
"ethsync 1.6.0",
|
||||
"ipc-common-types 1.6.0",
|
||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -1743,7 +1744,7 @@ version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -1872,7 +1873,7 @@ name = "rand"
|
||||
version = "0.3.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1918,10 +1919,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.6.2"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"untrusted 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
@@ -1940,7 +1942,7 @@ name = "rocksdb"
|
||||
version = "0.4.5"
|
||||
source = "git+https://github.com/ethcore/rust-rocksdb#64c63ccbe1f62c2e2b39262486f9ba813793af58"
|
||||
dependencies = [
|
||||
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rocksdb-sys 0.3.0 (git+https://github.com/ethcore/rust-rocksdb)",
|
||||
]
|
||||
|
||||
@@ -1950,7 +1952,7 @@ version = "0.3.0"
|
||||
source = "git+https://github.com/ethcore/rust-rocksdb#64c63ccbe1f62c2e2b39262486f9ba813793af58"
|
||||
dependencies = [
|
||||
"gcc 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1971,7 +1973,7 @@ version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"termios 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
@@ -1982,7 +1984,7 @@ version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"termios 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
@@ -1993,7 +1995,7 @@ version = "1.4.0"
|
||||
dependencies = [
|
||||
"ethcore-bigint 0.1.2",
|
||||
"ethcore-rpc 1.6.0",
|
||||
"ethcore-util 1.6.0",
|
||||
"ethcore-util 1.6.5",
|
||||
"futures 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"parity-rpc-client 1.4.0",
|
||||
"rpassword 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -2005,7 +2007,7 @@ version = "0.2.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"gcc 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -2059,7 +2061,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"core-foundation 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"core-foundation-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"security-framework-sys 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
@@ -2069,7 +2071,7 @@ version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"core-foundation-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2262,7 +2264,7 @@ version = "0.33.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bitflags 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"term 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@@ -2305,7 +2307,7 @@ name = "termios"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2314,7 +2316,7 @@ version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2331,7 +2333,7 @@ version = "0.1.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
@@ -2635,7 +2637,7 @@ dependencies = [
|
||||
"checksum language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a"
|
||||
"checksum lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "49247ec2a285bb3dcb23cbd9c35193c025e7251bfce77c1d5da97e6362dffe7f"
|
||||
"checksum lazycell 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ce12306c4739d86ee97c23139f3a34ddf0387bbf181bc7929d287025a8c3ef6b"
|
||||
"checksum libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)" = "408014cace30ee0f767b1c4517980646a573ec61a57957aeeabcac8ac0a02e8d"
|
||||
"checksum libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)" = "88ee81885f9f04bff991e306fea7c1c60a5f0f9e409e99f6b40e3311a3363135"
|
||||
"checksum libusb 0.3.0 (git+https://github.com/ethcore/libusb-rs)" = "<none>"
|
||||
"checksum libusb-sys 0.2.3 (git+https://github.com/ethcore/libusb-sys)" = "<none>"
|
||||
"checksum linked-hash-map 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bda158e0dabeb97ee8a401f4d17e479d6b891a14de0bba79d5cc2d4d325b5e48"
|
||||
@@ -2655,9 +2657,9 @@ dependencies = [
|
||||
"checksum miow 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d5bfc6782530ac8ace97af10a540054a37126b63b0702ddaaa243b73b5745b9a"
|
||||
"checksum msdos_time 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "c04b68cc63a8480fb2550343695f7be72effdec953a9d4508161c3e69041c7d8"
|
||||
"checksum multibase 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b9c35dac080fd6e16a99924c8dfdef0af89d797dd851adab25feaffacf7850d6"
|
||||
"checksum multihash 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "755d5a39bee3faaf649437e873beab334990221b2faf1f2e56ca10a9e4600235"
|
||||
"checksum nanomsg 0.5.1 (git+https://github.com/ethcore/nanomsg.rs.git)" = "<none>"
|
||||
"checksum nanomsg-sys 0.5.0 (git+https://github.com/ethcore/nanomsg.rs.git)" = "<none>"
|
||||
"checksum multihash 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c9f70f2402fa07c16c40be8fd0a748a39257c5dc3ff5c857cbbde2f39135c505"
|
||||
"checksum nanomsg 0.5.1 (git+https://github.com/ethcore/nanomsg.rs.git?branch=parity-1.7)" = "<none>"
|
||||
"checksum nanomsg-sys 0.5.0 (git+https://github.com/ethcore/nanomsg.rs.git?branch=parity-1.7)" = "<none>"
|
||||
"checksum native-tls 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aa4e52995154bb6f0b41e4379a279482c9387c1632e3798ba4e511ef8c54ee09"
|
||||
"checksum net2 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)" = "6a816012ca11cb47009693c1e0c6130e26d39e4d97ee2a13c50e868ec83e3204"
|
||||
"checksum nix 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f05c2fc965fc1cd6b73fa57fa7b89f288178737f2f3ce9e63e4a6a141189000e"
|
||||
@@ -2682,7 +2684,7 @@ dependencies = [
|
||||
"checksum order-stat 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "efa535d5117d3661134dbf1719b6f0ffe06f2375843b13935db186cd094105eb"
|
||||
"checksum owning_ref 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8d91377085359426407a287ab16884a0111ba473aa6844ff01d4ec20ce3d75e7"
|
||||
"checksum parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "98378dec0a185da2b7180308752f0bad73aaa949c3e0a3b0528d0e067945f7ab"
|
||||
"checksum parity-ui-precompiled 1.4.0 (git+https://github.com/ethcore/js-precompiled.git)" = "<none>"
|
||||
"checksum parity-ui-precompiled 1.4.0 (git+https://github.com/ethcore/js-precompiled.git?branch=beta)" = "<none>"
|
||||
"checksum parking_lot 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "e1435e7a2a00dfebededd6c6bdbd54008001e94b4a2aadd6aef0dc4c56317621"
|
||||
"checksum parking_lot_core 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fb1b97670a2ffadce7c397fb80a3d687c4f3060140b885621ef1653d0e5d5068"
|
||||
"checksum phf 0.7.14 (registry+https://github.com/rust-lang/crates.io-index)" = "447d9d45f2e0b4a9b532e808365abf18fc211be6ca217202fcd45236ef12f026"
|
||||
@@ -2706,7 +2708,7 @@ dependencies = [
|
||||
"checksum regex 0.1.68 (registry+https://github.com/rust-lang/crates.io-index)" = "b4329b8928a284580a1c63ec9d846b12f6d3472317243ff7077aff11f23f2b29"
|
||||
"checksum regex-syntax 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "841591b1e05609a643e3b4d0045fce04f701daba7151ddcd3ad47b080693d5a9"
|
||||
"checksum reqwest 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3bef9ed8fdfcc30947d6b774938dc0c3f369a474efe440df2c7f278180b2d2e6"
|
||||
"checksum ring 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "87ac4fce2ee4bb10dd106788e90fdfa4c5a7f3f9f6aae29824db77dc57e2767d"
|
||||
"checksum ring 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "595afba2db7545b940ec900dc59b5281f719d327fc0674eeadc9953617e55357"
|
||||
"checksum rocksdb 0.4.5 (git+https://github.com/ethcore/rust-rocksdb)" = "<none>"
|
||||
"checksum rocksdb-sys 0.3.0 (git+https://github.com/ethcore/rust-rocksdb)" = "<none>"
|
||||
"checksum rotor 0.6.3 (git+https://github.com/ethcore/rotor)" = "<none>"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
description = "Parity Ethereum client"
|
||||
name = "parity"
|
||||
version = "1.6.0"
|
||||
version = "1.6.5"
|
||||
license = "GPL-3.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
|
||||
|
||||
@@ -39,7 +39,11 @@ pub struct RestApi {
|
||||
impl RestApi {
|
||||
pub fn new(cors_domains: Vec<String>, endpoints: Arc<Endpoints>, fetcher: Arc<Fetcher>) -> Box<Endpoint> {
|
||||
Box::new(RestApi {
|
||||
cors_domains: Some(cors_domains.into_iter().map(AccessControlAllowOrigin::Value).collect()),
|
||||
cors_domains: Some(cors_domains.into_iter().map(|domain| match domain.as_ref() {
|
||||
"all" | "*" | "any" => AccessControlAllowOrigin::Any,
|
||||
"null" => AccessControlAllowOrigin::Null,
|
||||
other => AccessControlAllowOrigin::Value(other.into()),
|
||||
}).collect()),
|
||||
endpoints: endpoints,
|
||||
fetcher: fetcher,
|
||||
})
|
||||
|
||||
@@ -111,6 +111,7 @@ pub struct ServerBuilder<T: Fetch = FetchClient> {
|
||||
web_proxy_tokens: Arc<WebProxyTokens>,
|
||||
signer_address: Option<(String, u16)>,
|
||||
allowed_hosts: Option<Vec<String>>,
|
||||
extra_cors: Option<Vec<String>>,
|
||||
remote: Remote,
|
||||
fetch: Option<T>,
|
||||
}
|
||||
@@ -126,6 +127,7 @@ impl ServerBuilder {
|
||||
web_proxy_tokens: Arc::new(|_| false),
|
||||
signer_address: None,
|
||||
allowed_hosts: Some(vec![]),
|
||||
extra_cors: None,
|
||||
remote: remote,
|
||||
fetch: None,
|
||||
}
|
||||
@@ -143,6 +145,7 @@ impl<T: Fetch> ServerBuilder<T> {
|
||||
web_proxy_tokens: self.web_proxy_tokens,
|
||||
signer_address: self.signer_address,
|
||||
allowed_hosts: self.allowed_hosts,
|
||||
extra_cors: self.extra_cors,
|
||||
remote: self.remote,
|
||||
fetch: Some(fetch),
|
||||
}
|
||||
@@ -174,6 +177,13 @@ impl<T: Fetch> ServerBuilder<T> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Extra cors headers.
|
||||
/// `None` - no additional CORS URLs
|
||||
pub fn extra_cors_headers(mut self, cors: Option<Vec<String>>) -> Self {
|
||||
self.extra_cors = cors;
|
||||
self
|
||||
}
|
||||
|
||||
/// Change extra dapps paths (apart from `dapps_path`)
|
||||
pub fn extra_dapps<P: AsRef<Path>>(mut self, extra_dapps: &[P]) -> Self {
|
||||
self.extra_dapps = extra_dapps.iter().map(|p| p.as_ref().to_owned()).collect();
|
||||
@@ -187,6 +197,7 @@ impl<T: Fetch> ServerBuilder<T> {
|
||||
Server::start_http(
|
||||
addr,
|
||||
self.allowed_hosts,
|
||||
self.extra_cors,
|
||||
NoAuth,
|
||||
handler,
|
||||
self.dapps_path,
|
||||
@@ -207,6 +218,7 @@ impl<T: Fetch> ServerBuilder<T> {
|
||||
Server::start_http(
|
||||
addr,
|
||||
self.allowed_hosts,
|
||||
self.extra_cors,
|
||||
HttpBasicAuth::single_user(username, password),
|
||||
handler,
|
||||
self.dapps_path,
|
||||
@@ -251,8 +263,8 @@ impl Server {
|
||||
}
|
||||
|
||||
/// Returns a list of CORS domains for API endpoint.
|
||||
fn cors_domains(signer_address: Option<(String, u16)>) -> Vec<String> {
|
||||
match signer_address {
|
||||
fn cors_domains(signer_address: Option<(String, u16)>, extra_cors: Option<Vec<String>>) -> Vec<String> {
|
||||
let basic_cors = match signer_address {
|
||||
Some(signer_address) => vec![
|
||||
format!("http://{}{}", HOME_PAGE, DAPPS_DOMAIN),
|
||||
format!("http://{}{}:{}", HOME_PAGE, DAPPS_DOMAIN, signer_address.1),
|
||||
@@ -260,15 +272,20 @@ impl Server {
|
||||
format!("https://{}{}", HOME_PAGE, DAPPS_DOMAIN),
|
||||
format!("https://{}{}:{}", HOME_PAGE, DAPPS_DOMAIN, signer_address.1),
|
||||
format!("https://{}", address(&signer_address)),
|
||||
|
||||
],
|
||||
None => vec![],
|
||||
};
|
||||
|
||||
match extra_cors {
|
||||
None => basic_cors,
|
||||
Some(extra_cors) => basic_cors.into_iter().chain(extra_cors).collect(),
|
||||
}
|
||||
}
|
||||
|
||||
fn start_http<A: Authorization + 'static, F: Fetch, T: Middleware<Metadata>>(
|
||||
addr: &SocketAddr,
|
||||
hosts: Option<Vec<String>>,
|
||||
extra_cors: Option<Vec<String>>,
|
||||
authorization: A,
|
||||
handler: RpcHandler<Metadata, T>,
|
||||
dapps_path: PathBuf,
|
||||
@@ -297,7 +314,7 @@ impl Server {
|
||||
remote.clone(),
|
||||
fetch.clone(),
|
||||
));
|
||||
let cors_domains = Self::cors_domains(signer_address.clone());
|
||||
let cors_domains = Self::cors_domains(signer_address.clone(), extra_cors);
|
||||
|
||||
let special = Arc::new({
|
||||
let mut special = HashMap::new();
|
||||
@@ -413,8 +430,9 @@ mod util_tests {
|
||||
// given
|
||||
|
||||
// when
|
||||
let none = Server::cors_domains(None);
|
||||
let some = Server::cors_domains(Some(("127.0.0.1".into(), 18180)));
|
||||
let none = Server::cors_domains(None, None);
|
||||
let some = Server::cors_domains(Some(("127.0.0.1".into(), 18180)), None);
|
||||
let extra = Server::cors_domains(None, Some(vec!["all".to_owned()]));
|
||||
|
||||
// then
|
||||
assert_eq!(none, Vec::<String>::new());
|
||||
@@ -425,7 +443,7 @@ mod util_tests {
|
||||
"https://parity.web3.site".into(),
|
||||
"https://parity.web3.site:18180".into(),
|
||||
"https://127.0.0.1:18180".into()
|
||||
|
||||
]);
|
||||
assert_eq!(extra, vec!["all".to_owned()]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use tests::helpers::{serve, serve_with_registrar, request, assert_security_headers};
|
||||
use tests::helpers::{serve, serve_with_registrar, serve_extra_cors, request, assert_security_headers};
|
||||
|
||||
#[test]
|
||||
fn should_return_error() {
|
||||
@@ -212,3 +212,25 @@ fn should_return_signer_port_cors_headers_for_home_parity_with_port() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_return_extra_cors_headers() {
|
||||
// given
|
||||
let server = serve_extra_cors(Some(vec!["all".to_owned()]));
|
||||
|
||||
// when
|
||||
let response = request(server,
|
||||
"\
|
||||
POST /api/ping HTTP/1.1\r\n\
|
||||
Host: localhost:8080\r\n\
|
||||
Origin: http://somedomain.io\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
{}
|
||||
"
|
||||
);
|
||||
|
||||
// then
|
||||
response.assert_status("HTTP/1.1 200 OK");
|
||||
response.assert_header("Access-Control-Allow-Origin", "http://somedomain.io");
|
||||
}
|
||||
|
||||
|
||||
@@ -109,6 +109,10 @@ pub fn serve_hosts(hosts: Option<Vec<String>>) -> ServerLoop {
|
||||
init_server(|builder| builder.allowed_hosts(hosts), Default::default(), Remote::new_sync()).0
|
||||
}
|
||||
|
||||
pub fn serve_extra_cors(extra_cors: Option<Vec<String>>) -> ServerLoop {
|
||||
init_server(|builder| builder.allowed_hosts(None).extra_cors_headers(extra_cors), Default::default(), Remote::new_sync()).0
|
||||
}
|
||||
|
||||
pub fn serve_with_registrar() -> (ServerLoop, Arc<FakeRegistrar>) {
|
||||
init_server(|builder| builder.allowed_hosts(None), Default::default(), Remote::new_sync())
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ rustc_version = "0.1"
|
||||
|
||||
[dependencies]
|
||||
parity-ui-dev = { path = "../../js", optional = true }
|
||||
parity-ui-precompiled = { git = "https://github.com/ethcore/js-precompiled.git", optional = true }
|
||||
parity-ui-precompiled = { git = "https://github.com/ethcore/js-precompiled.git", branch = "beta", optional = true }
|
||||
|
||||
[features]
|
||||
no-precompiled-js = ["parity-ui-dev"]
|
||||
|
||||
@@ -27,6 +27,7 @@ byteorder = "1.0"
|
||||
transient-hashmap = "0.1"
|
||||
linked-hash-map = "0.3.0"
|
||||
lru-cache = "0.1.0"
|
||||
itertools = "0.5"
|
||||
ethabi = "1.0.0"
|
||||
evmjit = { path = "../evmjit", optional = true }
|
||||
clippy = { version = "0.0.103", optional = true}
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
"timestamp": "0x00",
|
||||
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"extraData": "0x",
|
||||
"gasLimit": "0x2fefd8"
|
||||
"gasLimit": "0x222222"
|
||||
},
|
||||
"accounts": {
|
||||
"0000000000000000000000000000000000000001": { "balance": "1", "nonce": "1048576", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } },
|
||||
|
||||
@@ -30,7 +30,8 @@
|
||||
"params": {
|
||||
"maximumExtraDataSize": "0x20",
|
||||
"minGasLimit": "0x1388",
|
||||
"networkID" : "0x2A"
|
||||
"networkID" : "0x2A",
|
||||
"validateReceipts" : false
|
||||
},
|
||||
"genesis": {
|
||||
"seal": {
|
||||
|
||||
@@ -25,8 +25,8 @@
|
||||
"maximumExtraDataSize": "0x20",
|
||||
"minGasLimit": "0x1388",
|
||||
"networkID" : "0x3",
|
||||
"forkBlock": 333922,
|
||||
"forkCanonHash": "0x8737eb141d4f05db57af63fc8d3b4d4d8f9cddb0c4e1ab855de8c288fdc1924f",
|
||||
"forkBlock": 641350,
|
||||
"forkCanonHash": "0x8033403e9fe5811a7b6d6b469905915de1c59207ce2172cbcf5d6ff14fa6a2eb",
|
||||
"eip98Transition": "0x7fffffffffffff"
|
||||
},
|
||||
"genesis": {
|
||||
@@ -44,11 +44,8 @@
|
||||
"gasLimit": "0x1000000"
|
||||
},
|
||||
"nodes": [
|
||||
"enode://a22f0977ce02653bf95e38730106356342df48b5222e2c2a1a6f9ef34769bf593bae9ca0a888cf60839edd52efc1b6e393c63a57d76f4c4fe14e641f1f9e637e@128.199.55.137:30303",
|
||||
"enode://012239fccf3ff1d92b036983a430cb6705c6528c96c0354413f8854802138e5135c084ab36e7c54efb621c46728df8c3a6f4c1db9bb48a1330efe3f82f2dd7a6@52.169.94.142:30303",
|
||||
"enode://1462682e4b7ba2258346d55e25e5b9d264b0db40cee12bdfba4e72b1d7050350ea954c006e9106dd96a128e6e0bd6dffb17eed51f9f99bf7f9cdadfeaf8da4ff@51.15.61.253:30303",
|
||||
"enode://98fbb020c799ae39a828bd75dc2bd5d4721539faf317076b275f91182a5c8900b592e8abfdddceae674a7c3bb40ea00a6ca9ccb7805ab58c4b7b29c61c8f7239@51.15.62.44:30303",
|
||||
"enode://d801dd4e3d15a8bf785931add164bd9c313e3f6b5749d9302b311f2b48064cba5c86c32b1302c27cd983fc89ae07d4d306dd1197610835b8782e95dfb1b3f9ea@51.15.43.255:30303"
|
||||
"enode://20c9ad97c081d63397d7b685a412227a40e23c8bdc6688c6f37e97cfbc22d2b4d1db1510d8f61e6a8866ad7f0e17c02b14182d37ea7c3c8b9c2683aeb6b733a1@52.169.14.227:30303",
|
||||
"enode://6ce05930c72abc632c58e2e4324f7c7ea478cec0ed4fa2528982cf34483094e9cbc9216e7aa349691242576d552a2a56aaeae426c5303ded677ce455ba1acd9d@13.84.180.240:30303"
|
||||
],
|
||||
"accounts": {
|
||||
"0000000000000000000000000000000000000001": { "balance": "1", "nonce": "0", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } },
|
||||
|
||||
@@ -139,6 +139,7 @@
|
||||
}
|
||||
},
|
||||
"params": {
|
||||
"eip98Transition": "0x7fffffffffffffff",
|
||||
"accountStartNonce": "0x00",
|
||||
"maximumExtraDataSize": "0x20",
|
||||
"minGasLimit": "0x1388",
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
"timestamp": "0x00",
|
||||
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"extraData": "0x",
|
||||
"gasLimit": "0x2fefd8"
|
||||
"gasLimit": "0x222222"
|
||||
},
|
||||
"accounts": {
|
||||
"0000000000000000000000000000000000000001": { "balance": "1", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } },
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
"timestamp": "0x00",
|
||||
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"extraData": "0x",
|
||||
"gasLimit": "0x2fefd8"
|
||||
"gasLimit": "0x222222"
|
||||
},
|
||||
"accounts": {
|
||||
"0000000000000000000000000000000000000001": { "balance": "1", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } },
|
||||
|
||||
42
ethcore/res/validator_multi.json
Normal file
42
ethcore/res/validator_multi.json
Normal file
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"name": "TestMutiValidator",
|
||||
"engine": {
|
||||
"basicAuthority": {
|
||||
"params": {
|
||||
"gasLimitBoundDivisor": "0x0400",
|
||||
"durationLimit": "0x0d",
|
||||
"validators": {
|
||||
"multi": {
|
||||
"0": { "list": ["0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1"] },
|
||||
"2": { "list": ["0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e"] }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"params": {
|
||||
"accountStartNonce": "0x0",
|
||||
"maximumExtraDataSize": "0x20",
|
||||
"minGasLimit": "0x1388",
|
||||
"networkID" : "0x69"
|
||||
},
|
||||
"genesis": {
|
||||
"seal": {
|
||||
"generic": "0xc180"
|
||||
},
|
||||
"difficulty": "0x20000",
|
||||
"author": "0x0000000000000000000000000000000000000000",
|
||||
"timestamp": "0x00",
|
||||
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"extraData": "0x",
|
||||
"gasLimit": "0x2fefd8"
|
||||
},
|
||||
"accounts": {
|
||||
"0000000000000000000000000000000000000001": { "balance": "1", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } },
|
||||
"0000000000000000000000000000000000000002": { "balance": "1", "builtin": { "name": "sha256", "pricing": { "linear": { "base": 60, "word": 12 } } } },
|
||||
"0000000000000000000000000000000000000003": { "balance": "1", "builtin": { "name": "ripemd160", "pricing": { "linear": { "base": 600, "word": 120 } } } },
|
||||
"0000000000000000000000000000000000000004": { "balance": "1", "builtin": { "name": "identity", "pricing": { "linear": { "base": 15, "word": 3 } } } },
|
||||
"0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1": { "balance": "99999999999999999999999" },
|
||||
"0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e": { "balance": "99999999999999999999999" }
|
||||
}
|
||||
}
|
||||
@@ -152,7 +152,7 @@ impl AccountProvider {
|
||||
manager.set_key_path(if settings.hardware_wallet_classic_key { KeyPath::EthereumClassic } else { KeyPath::Ethereum });
|
||||
hardware_store = Some(manager)
|
||||
},
|
||||
Err(e) => warn!("Error initializing hardware wallets: {}", e),
|
||||
Err(e) => debug!("Error initializing hardware wallets: {}", e),
|
||||
}
|
||||
}
|
||||
AccountProvider {
|
||||
|
||||
@@ -553,7 +553,6 @@ pub fn enact(
|
||||
b.set_extra_data(header.extra_data().clone()).unwrap_or_else(|e| warn!("Couldn't set extradata: {}. Ignoring.", e));
|
||||
b.set_uncles_hash(header.uncles_hash().clone());
|
||||
b.set_transactions_root(header.transactions_root().clone());
|
||||
b.set_receipts_root(header.receipts_root().clone());
|
||||
|
||||
push_transactions(&mut b, transactions)?;
|
||||
for u in uncles {
|
||||
|
||||
@@ -253,7 +253,7 @@ impl Client {
|
||||
if let Some(reg_addr) = client.additional_params().get("registrar").and_then(|s| Address::from_str(s).ok()) {
|
||||
trace!(target: "client", "Found registrar at {}", reg_addr);
|
||||
let weak = Arc::downgrade(&client);
|
||||
let registrar = Registry::new(reg_addr, move |a, d| weak.upgrade().ok_or("No client!".into()).and_then(|c| c.call_contract(a, d)));
|
||||
let registrar = Registry::new(reg_addr, move |a, d| weak.upgrade().ok_or("No client!".into()).and_then(|c| c.call_contract(BlockId::Latest, a, d)));
|
||||
*client.registrar.lock() = Some(registrar);
|
||||
}
|
||||
Ok(client)
|
||||
@@ -380,7 +380,7 @@ impl Client {
|
||||
})?;
|
||||
|
||||
// Final Verification
|
||||
if let Err(e) = self.verifier.verify_block_final(header, locked_block.block().header()) {
|
||||
if let Err(e) = self.verifier.verify_block_final(header, locked_block.block().header(), self.engine().params().validate_receipts) {
|
||||
warn!(target: "client", "Stage 4 block verification failed for #{} ({})\nError: {:?}", header.number(), header.hash(), e);
|
||||
return Err(());
|
||||
}
|
||||
@@ -1445,7 +1445,7 @@ impl BlockChainClient for Client {
|
||||
}
|
||||
}
|
||||
|
||||
fn call_contract(&self, address: Address, data: Bytes) -> Result<Bytes, String> {
|
||||
fn call_contract(&self, block_id: BlockId, address: Address, data: Bytes) -> Result<Bytes, String> {
|
||||
let from = Address::default();
|
||||
let transaction = Transaction {
|
||||
nonce: self.latest_nonce(&from),
|
||||
@@ -1456,7 +1456,7 @@ impl BlockChainClient for Client {
|
||||
data: data,
|
||||
}.fake_sign(from);
|
||||
|
||||
self.call(&transaction, BlockId::Latest, Default::default())
|
||||
self.call(&transaction, block_id, Default::default())
|
||||
.map_err(|e| format!("{:?}", e))
|
||||
.map(|executed| {
|
||||
executed.output
|
||||
|
||||
@@ -731,7 +731,7 @@ impl BlockChainClient for TestBlockChainClient {
|
||||
}
|
||||
}
|
||||
|
||||
fn call_contract(&self, _address: Address, _data: Bytes) -> Result<Bytes, String> { Ok(vec![]) }
|
||||
fn call_contract(&self, _id: BlockId, _address: Address, _data: Bytes) -> Result<Bytes, String> { Ok(vec![]) }
|
||||
|
||||
fn transact_contract(&self, address: Address, data: Bytes) -> Result<TransactionImportResult, EthcoreError> {
|
||||
let transaction = Transaction {
|
||||
|
||||
@@ -254,7 +254,7 @@ pub trait BlockChainClient : Sync + Send {
|
||||
fn pruning_info(&self) -> PruningInfo;
|
||||
|
||||
/// Like `call`, but with various defaults. Designed to be used for calling contracts.
|
||||
fn call_contract(&self, address: Address, data: Bytes) -> Result<Bytes, String>;
|
||||
fn call_contract(&self, id: BlockId, address: Address, data: Bytes) -> Result<Bytes, String>;
|
||||
|
||||
/// Import a transaction: used for misbehaviour reporting.
|
||||
fn transact_contract(&self, address: Address, data: Bytes) -> Result<TransactionImportResult, EthcoreError>;
|
||||
|
||||
@@ -82,7 +82,9 @@ pub struct AuthorityRound {
|
||||
proposed: AtomicBool,
|
||||
client: RwLock<Option<Weak<EngineClient>>>,
|
||||
signer: EngineSigner,
|
||||
validators: Box<ValidatorSet + Send + Sync>,
|
||||
validators: Box<ValidatorSet>,
|
||||
/// Is this Engine just for testing (prevents step calibration).
|
||||
calibrate_step: bool,
|
||||
}
|
||||
|
||||
fn header_step(header: &Header) -> Result<usize, ::rlp::DecoderError> {
|
||||
@@ -122,6 +124,7 @@ impl AuthorityRound {
|
||||
client: RwLock::new(None),
|
||||
signer: Default::default(),
|
||||
validators: new_validator_set(our_params.validators),
|
||||
calibrate_step: our_params.start_step.is_none(),
|
||||
});
|
||||
// Do not initialize timeouts for tests.
|
||||
if should_timeout {
|
||||
@@ -131,6 +134,12 @@ impl AuthorityRound {
|
||||
Ok(engine)
|
||||
}
|
||||
|
||||
fn calibrate_step(&self) {
|
||||
if self.calibrate_step {
|
||||
self.step.store((unix_now().as_secs() / self.step_duration.as_secs()) as usize, AtomicOrdering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
fn remaining_step_duration(&self) -> Duration {
|
||||
let now = unix_now();
|
||||
let step_end = self.step_duration * (self.step.load(AtomicOrdering::SeqCst) as u32 + 1);
|
||||
@@ -141,12 +150,22 @@ impl AuthorityRound {
|
||||
}
|
||||
}
|
||||
|
||||
fn step_proposer(&self, step: usize) -> Address {
|
||||
self.validators.get(step)
|
||||
fn step_proposer(&self, bh: &H256, step: usize) -> Address {
|
||||
self.validators.get(bh, step)
|
||||
}
|
||||
|
||||
fn is_step_proposer(&self, step: usize, address: &Address) -> bool {
|
||||
self.step_proposer(step) == *address
|
||||
fn is_step_proposer(&self, bh: &H256, step: usize, address: &Address) -> bool {
|
||||
self.step_proposer(bh, step) == *address
|
||||
}
|
||||
|
||||
fn is_future_step(&self, step: usize) -> bool {
|
||||
if step > self.step.load(AtomicOrdering::SeqCst) + 1 {
|
||||
// Make absolutely sure that the step is correct.
|
||||
self.calibrate_step();
|
||||
step > self.step.load(AtomicOrdering::SeqCst) + 1
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,9 +235,8 @@ impl Engine for AuthorityRound {
|
||||
}
|
||||
|
||||
fn populate_from_parent(&self, header: &mut Header, parent: &Header, gas_floor_target: U256, _gas_ceil_target: U256) {
|
||||
// Chain scoring: total weight is sqrt(U256::max_value())*height - step
|
||||
let new_difficulty = U256::from(U128::max_value()) + header_step(parent).expect("Header has been verified; qed").into() - self.step.load(AtomicOrdering::SeqCst).into();
|
||||
header.set_difficulty(new_difficulty);
|
||||
// Chain scoring: weak height scoring, backported for compatibility.
|
||||
header.set_difficulty(parent.difficulty().clone());
|
||||
header.set_gas_limit({
|
||||
let gas_limit = parent.gas_limit().clone();
|
||||
let bound_divisor = self.gas_limit_bound_divisor;
|
||||
@@ -231,7 +249,7 @@ impl Engine for AuthorityRound {
|
||||
}
|
||||
|
||||
fn seals_internally(&self) -> Option<bool> {
|
||||
Some(self.validators.contains(&self.signer.address()))
|
||||
Some(self.signer.address() != Address::default())
|
||||
}
|
||||
|
||||
/// Attempt to seal the block internally.
|
||||
@@ -242,7 +260,7 @@ impl Engine for AuthorityRound {
|
||||
if self.proposed.load(AtomicOrdering::SeqCst) { return Seal::None; }
|
||||
let header = block.header();
|
||||
let step = self.step.load(AtomicOrdering::SeqCst);
|
||||
if self.is_step_proposer(step, header.author()) {
|
||||
if self.is_step_proposer(header.parent_hash(), step, header.author()) {
|
||||
if let Ok(signature) = self.signer.sign(header.bare_hash()) {
|
||||
trace!(target: "engine", "generate_seal: Issuing a block for step {}.", step);
|
||||
self.proposed.store(true, AtomicOrdering::SeqCst);
|
||||
@@ -281,32 +299,33 @@ impl Engine for AuthorityRound {
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if the signature belongs to the correct proposer.
|
||||
fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> {
|
||||
let header_step = header_step(header)?;
|
||||
// Give one step slack if step is lagging, double vote is still not possible.
|
||||
if header_step <= self.step.load(AtomicOrdering::SeqCst) + 1 {
|
||||
let proposer_signature = header_signature(header)?;
|
||||
let correct_proposer = self.step_proposer(header_step);
|
||||
if verify_address(&correct_proposer, &proposer_signature, &header.bare_hash())? {
|
||||
fn verify_block_unordered(&self, _header: &Header, _block: Option<&[u8]>) -> Result<(), Error> {
|
||||
Ok(())
|
||||
} else {
|
||||
trace!(target: "engine", "verify_block_unordered: bad proposer for step: {}", header_step);
|
||||
Err(EngineError::NotProposer(Mismatch { expected: correct_proposer, found: header.author().clone() }))?
|
||||
}
|
||||
} else {
|
||||
}
|
||||
|
||||
/// Do the validator and gas limit validation.
|
||||
fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> Result<(), Error> {
|
||||
let step = header_step(header)?;
|
||||
// Give one step slack if step is lagging, double vote is still not possible.
|
||||
if self.is_future_step(step) {
|
||||
trace!(target: "engine", "verify_block_unordered: block from the future");
|
||||
self.validators.report_benign(header.author());
|
||||
Err(BlockError::InvalidSeal)?
|
||||
} else {
|
||||
// Check if the signature belongs to a validator, can depend on parent state.
|
||||
let proposer_signature = header_signature(header)?;
|
||||
let correct_proposer = self.step_proposer(header.parent_hash(), step);
|
||||
if !verify_address(&correct_proposer, &proposer_signature, &header.bare_hash())? {
|
||||
trace!(target: "engine", "verify_block_unordered: bad proposer for step: {}", step);
|
||||
Err(EngineError::NotProposer(Mismatch { expected: correct_proposer, found: header.author().clone() }))?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> Result<(), Error> {
|
||||
// Do not calculate difficulty for genesis blocks.
|
||||
if header.number() == 0 {
|
||||
return Err(From::from(BlockError::RidiculousNumber(OutOfBounds { min: Some(1), max: None, found: header.number() })));
|
||||
}
|
||||
|
||||
let step = header_step(header)?;
|
||||
// Check if parent is from a previous step.
|
||||
if step == header_step(parent)? {
|
||||
trace!(target: "engine", "Multiple blocks proposed for step {}.", step);
|
||||
@@ -394,7 +413,7 @@ mod tests {
|
||||
let mut header: Header = Header::default();
|
||||
header.set_seal(vec![encode(&H520::default()).to_vec()]);
|
||||
|
||||
let verify_result = engine.verify_block_unordered(&header, None);
|
||||
let verify_result = engine.verify_block_family(&header, &Default::default(), None);
|
||||
assert!(verify_result.is_err());
|
||||
}
|
||||
|
||||
@@ -432,10 +451,14 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn proposer_switching() {
|
||||
let mut header: Header = Header::default();
|
||||
let tap = AccountProvider::transient_provider();
|
||||
let addr = tap.insert_account(Secret::from_slice(&"0".sha3()).unwrap(), "0").unwrap();
|
||||
|
||||
let mut parent_header: Header = Header::default();
|
||||
parent_header.set_seal(vec![encode(&0usize).to_vec()]);
|
||||
parent_header.set_gas_limit(U256::from_str("222222").unwrap());
|
||||
let mut header: Header = Header::default();
|
||||
header.set_number(1);
|
||||
header.set_gas_limit(U256::from_str("222222").unwrap());
|
||||
header.set_author(addr);
|
||||
|
||||
let engine = Spec::new_test_round().engine;
|
||||
@@ -444,17 +467,22 @@ mod tests {
|
||||
// Two validators.
|
||||
// Spec starts with step 2.
|
||||
header.set_seal(vec![encode(&2usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]);
|
||||
assert!(engine.verify_block_seal(&header).is_err());
|
||||
assert!(engine.verify_block_family(&header, &parent_header, None).is_err());
|
||||
header.set_seal(vec![encode(&1usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]);
|
||||
assert!(engine.verify_block_seal(&header).is_ok());
|
||||
assert!(engine.verify_block_family(&header, &parent_header, None).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_future_block() {
|
||||
let mut header: Header = Header::default();
|
||||
let tap = AccountProvider::transient_provider();
|
||||
let addr = tap.insert_account(Secret::from_slice(&"0".sha3()).unwrap(), "0").unwrap();
|
||||
|
||||
let mut parent_header: Header = Header::default();
|
||||
parent_header.set_seal(vec![encode(&0usize).to_vec()]);
|
||||
parent_header.set_gas_limit(U256::from_str("222222").unwrap());
|
||||
let mut header: Header = Header::default();
|
||||
header.set_number(1);
|
||||
header.set_gas_limit(U256::from_str("222222").unwrap());
|
||||
header.set_author(addr);
|
||||
|
||||
let engine = Spec::new_test_round().engine;
|
||||
@@ -463,8 +491,8 @@ mod tests {
|
||||
// Two validators.
|
||||
// Spec starts with step 2.
|
||||
header.set_seal(vec![encode(&1usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]);
|
||||
assert!(engine.verify_block_seal(&header).is_ok());
|
||||
assert!(engine.verify_block_family(&header, &parent_header, None).is_ok());
|
||||
header.set_seal(vec![encode(&5usize).to_vec(), encode(&(&*signature as &[u8])).to_vec()]);
|
||||
assert!(engine.verify_block_seal(&header).is_err());
|
||||
assert!(engine.verify_block_family(&header, &parent_header, None).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ pub struct BasicAuthority {
|
||||
gas_limit_bound_divisor: U256,
|
||||
builtins: BTreeMap<Address, Builtin>,
|
||||
signer: EngineSigner,
|
||||
validators: Box<ValidatorSet + Send + Sync>,
|
||||
validators: Box<ValidatorSet>,
|
||||
}
|
||||
|
||||
impl BasicAuthority {
|
||||
@@ -104,14 +104,14 @@ impl Engine for BasicAuthority {
|
||||
}
|
||||
|
||||
fn seals_internally(&self) -> Option<bool> {
|
||||
Some(self.validators.contains(&self.signer.address()))
|
||||
Some(self.signer.address() != Address::default())
|
||||
}
|
||||
|
||||
/// Attempt to seal the block internally.
|
||||
fn generate_seal(&self, block: &ExecutedBlock) -> Seal {
|
||||
let header = block.header();
|
||||
let author = header.author();
|
||||
if self.validators.contains(author) {
|
||||
if self.validators.contains(header.parent_hash(), author) {
|
||||
// account should be pernamently unlocked, otherwise sealing will fail
|
||||
if let Ok(signature) = self.signer.sign(header.bare_hash()) {
|
||||
return Seal::Regular(vec![::rlp::encode(&(&H520::from(signature) as &[u8])).to_vec()]);
|
||||
@@ -133,20 +133,20 @@ impl Engine for BasicAuthority {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> {
|
||||
use rlp::{UntrustedRlp, View};
|
||||
|
||||
// check the signature is legit.
|
||||
let sig = UntrustedRlp::new(&header.seal()[0]).as_val::<H520>()?;
|
||||
let signer = public_to_address(&recover(&sig.into(), &header.bare_hash())?);
|
||||
if !self.validators.contains(&signer) {
|
||||
return Err(BlockError::InvalidSeal)?;
|
||||
}
|
||||
fn verify_block_unordered(&self, _header: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> {
|
||||
// we should not calculate difficulty for genesis blocks
|
||||
use rlp::{UntrustedRlp, View};
|
||||
// Check if the signature belongs to a validator, can depend on parent state.
|
||||
let sig = UntrustedRlp::new(&header.seal()[0]).as_val::<H520>()?;
|
||||
let signer = public_to_address(&recover(&sig.into(), &header.bare_hash())?);
|
||||
if !self.validators.contains(header.parent_hash(), &signer) {
|
||||
return Err(BlockError::InvalidSeal)?;
|
||||
}
|
||||
|
||||
// Do not calculate difficulty for genesis blocks.
|
||||
if header.number() == 0 {
|
||||
return Err(From::from(BlockError::RidiculousNumber(OutOfBounds { min: Some(1), max: None, found: header.number() })));
|
||||
}
|
||||
@@ -239,7 +239,7 @@ mod tests {
|
||||
let mut header: Header = Header::default();
|
||||
header.set_seal(vec![::rlp::encode(&H520::default()).to_vec()]);
|
||||
|
||||
let verify_result = engine.verify_block_unordered(&header, None);
|
||||
let verify_result = engine.verify_block_family(&header, &Default::default(), None);
|
||||
assert!(verify_result.is_err());
|
||||
}
|
||||
|
||||
|
||||
@@ -166,7 +166,9 @@ pub trait Engine : Sync + Send {
|
||||
}
|
||||
|
||||
/// The network ID that transactions should be signed with.
|
||||
fn signing_network_id(&self, _env_info: &EnvInfo) -> Option<u64> { None }
|
||||
fn signing_network_id(&self, _env_info: &EnvInfo) -> Option<u64> {
|
||||
Some(self.params().chain_id)
|
||||
}
|
||||
|
||||
/// Verify the seal of a block. This is an auxilliary method that actually just calls other `verify_` methods
|
||||
/// to get the job done. By default it must pass `verify_basic` and `verify_block_unordered`. If more or fewer
|
||||
|
||||
@@ -95,8 +95,10 @@ pub struct Tendermint {
|
||||
last_lock: AtomicUsize,
|
||||
/// Bare hash of the proposed block, used for seal submission.
|
||||
proposal: RwLock<Option<H256>>,
|
||||
/// Hash of the proposal parent block.
|
||||
proposal_parent: RwLock<H256>,
|
||||
/// Set used to determine the current validators.
|
||||
validators: Box<ValidatorSet + Send + Sync>,
|
||||
validators: Box<ValidatorSet>,
|
||||
}
|
||||
|
||||
impl Tendermint {
|
||||
@@ -114,11 +116,12 @@ impl Tendermint {
|
||||
height: AtomicUsize::new(1),
|
||||
view: AtomicUsize::new(0),
|
||||
step: RwLock::new(Step::Propose),
|
||||
votes: VoteCollector::default(),
|
||||
votes: Default::default(),
|
||||
signer: Default::default(),
|
||||
lock_change: RwLock::new(None),
|
||||
last_lock: AtomicUsize::new(0),
|
||||
proposal: RwLock::new(None),
|
||||
proposal_parent: Default::default(),
|
||||
validators: new_validator_set(our_params.validators),
|
||||
});
|
||||
let handler = TransitionHandler::new(Arc::downgrade(&engine) as Weak<Engine>, Box::new(our_params.timeouts));
|
||||
@@ -232,7 +235,7 @@ impl Tendermint {
|
||||
let height = self.height.load(AtomicOrdering::SeqCst);
|
||||
if let Some(block_hash) = *self.proposal.read() {
|
||||
// Generate seal and remove old votes.
|
||||
if self.is_signer_proposer() {
|
||||
if self.is_signer_proposer(&*self.proposal_parent.read()) {
|
||||
let proposal_step = VoteStep::new(height, view, Step::Propose);
|
||||
let precommit_step = VoteStep::new(proposal_step.height, proposal_step.view, Step::Precommit);
|
||||
if let Some(seal) = self.votes.seal_signatures(proposal_step, precommit_step, &block_hash) {
|
||||
@@ -254,23 +257,23 @@ impl Tendermint {
|
||||
}
|
||||
|
||||
fn is_authority(&self, address: &Address) -> bool {
|
||||
self.validators.contains(address)
|
||||
self.validators.contains(&*self.proposal_parent.read(), address)
|
||||
}
|
||||
|
||||
fn is_above_threshold(&self, n: usize) -> bool {
|
||||
n > self.validators.count() * 2/3
|
||||
n > self.validators.count(&*self.proposal_parent.read()) * 2/3
|
||||
}
|
||||
|
||||
/// Find the designated for the given view.
|
||||
fn view_proposer(&self, height: Height, view: View) -> Address {
|
||||
fn view_proposer(&self, bh: &H256, height: Height, view: View) -> Address {
|
||||
let proposer_nonce = height + view;
|
||||
trace!(target: "engine", "Proposer nonce: {}", proposer_nonce);
|
||||
self.validators.get(proposer_nonce)
|
||||
self.validators.get(bh, proposer_nonce)
|
||||
}
|
||||
|
||||
/// Check if address is a proposer for given view.
|
||||
fn is_view_proposer(&self, height: Height, view: View, address: &Address) -> Result<(), EngineError> {
|
||||
let proposer = self.view_proposer(height, view);
|
||||
fn is_view_proposer(&self, bh: &H256, height: Height, view: View, address: &Address) -> Result<(), EngineError> {
|
||||
let proposer = self.view_proposer(bh, height, view);
|
||||
if proposer == *address {
|
||||
Ok(())
|
||||
} else {
|
||||
@@ -279,8 +282,8 @@ impl Tendermint {
|
||||
}
|
||||
|
||||
/// Check if current signer is the current proposer.
|
||||
fn is_signer_proposer(&self) -> bool {
|
||||
let proposer = self.view_proposer(self.height.load(AtomicOrdering::SeqCst), self.view.load(AtomicOrdering::SeqCst));
|
||||
fn is_signer_proposer(&self, bh: &H256) -> bool {
|
||||
let proposer = self.view_proposer(bh, self.height.load(AtomicOrdering::SeqCst), self.view.load(AtomicOrdering::SeqCst));
|
||||
self.signer.is_address(&proposer)
|
||||
}
|
||||
|
||||
@@ -419,7 +422,7 @@ impl Engine for Tendermint {
|
||||
|
||||
/// Should this node participate.
|
||||
fn seals_internally(&self) -> Option<bool> {
|
||||
Some(self.is_authority(&self.signer.address()))
|
||||
Some(self.signer.address() != Address::default())
|
||||
}
|
||||
|
||||
/// Attempt to seal generate a proposal seal.
|
||||
@@ -427,7 +430,7 @@ impl Engine for Tendermint {
|
||||
let header = block.header();
|
||||
let author = header.author();
|
||||
// Only proposer can generate seal if None was generated.
|
||||
if !self.is_signer_proposer() || self.proposal.read().is_some() {
|
||||
if !self.is_signer_proposer(header.parent_hash()) || self.proposal.read().is_some() {
|
||||
return Seal::None;
|
||||
}
|
||||
|
||||
@@ -441,6 +444,7 @@ impl Engine for Tendermint {
|
||||
self.votes.vote(ConsensusMessage::new(signature, height, view, Step::Propose, bh), author);
|
||||
// Remember proposal for later seal submission.
|
||||
*self.proposal.write() = bh;
|
||||
*self.proposal_parent.write() = header.parent_hash().clone();
|
||||
Seal::Proposal(vec![
|
||||
::rlp::encode(&view).to_vec(),
|
||||
::rlp::encode(&signature).to_vec(),
|
||||
@@ -505,7 +509,12 @@ impl Engine for Tendermint {
|
||||
|
||||
}
|
||||
|
||||
fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> {
|
||||
fn verify_block_unordered(&self, _header: &Header, _block: Option<&[u8]>) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Verify validators and gas limit.
|
||||
fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> Result<(), Error> {
|
||||
let proposal = ConsensusMessage::new_proposal(header)?;
|
||||
let proposer = proposal.verify()?;
|
||||
if !self.is_authority(&proposer) {
|
||||
@@ -522,7 +531,7 @@ impl Engine for Tendermint {
|
||||
Some(a) => a,
|
||||
None => public_to_address(&recover(&precommit.signature.into(), &precommit_hash)?),
|
||||
};
|
||||
if !self.validators.contains(&address) {
|
||||
if !self.validators.contains(header.parent_hash(), &address) {
|
||||
Err(EngineError::NotAuthorized(address.to_owned()))?
|
||||
}
|
||||
|
||||
@@ -545,12 +554,9 @@ impl Engine for Tendermint {
|
||||
found: signatures_len
|
||||
}))?;
|
||||
}
|
||||
self.is_view_proposer(proposal.vote_step.height, proposal.vote_step.view, &proposer)?;
|
||||
self.is_view_proposer(header.parent_hash(), proposal.vote_step.height, proposal.vote_step.view, &proposer)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> Result<(), Error> {
|
||||
if header.number() == 0 {
|
||||
Err(BlockError::RidiculousNumber(OutOfBounds { min: Some(1), max: None, found: header.number() }))?;
|
||||
}
|
||||
@@ -595,6 +601,7 @@ impl Engine for Tendermint {
|
||||
debug!(target: "engine", "Received a new proposal {:?} from {}.", proposal.vote_step, proposer);
|
||||
if self.is_view(&proposal) {
|
||||
*self.proposal.write() = proposal.block_hash.clone();
|
||||
*self.proposal_parent.write() = header.parent_hash().clone();
|
||||
}
|
||||
self.votes.vote(proposal, &proposer);
|
||||
true
|
||||
@@ -607,7 +614,7 @@ impl Engine for Tendermint {
|
||||
trace!(target: "engine", "Propose timeout.");
|
||||
if self.proposal.read().is_none() {
|
||||
// Report the proposer if no proposal was received.
|
||||
let current_proposer = self.view_proposer(self.height.load(AtomicOrdering::SeqCst), self.view.load(AtomicOrdering::SeqCst));
|
||||
let current_proposer = self.view_proposer(&*self.proposal_parent.read(), self.height.load(AtomicOrdering::SeqCst), self.view.load(AtomicOrdering::SeqCst));
|
||||
self.validators.report_benign(¤t_proposer);
|
||||
}
|
||||
Step::Prevote
|
||||
@@ -765,20 +772,25 @@ mod tests {
|
||||
let (spec, tap) = setup();
|
||||
let engine = spec.engine;
|
||||
|
||||
let mut header = Header::default();
|
||||
let validator = insert_and_unlock(&tap, "0");
|
||||
header.set_author(validator);
|
||||
let seal = proposal_seal(&tap, &header, 0);
|
||||
header.set_seal(seal);
|
||||
// Good proposer.
|
||||
assert!(engine.verify_block_unordered(&header.clone(), None).is_ok());
|
||||
let mut parent_header: Header = Header::default();
|
||||
parent_header.set_gas_limit(U256::from_str("222222").unwrap());
|
||||
|
||||
let mut header = Header::default();
|
||||
header.set_number(1);
|
||||
header.set_gas_limit(U256::from_str("222222").unwrap());
|
||||
let validator = insert_and_unlock(&tap, "1");
|
||||
header.set_author(validator);
|
||||
let seal = proposal_seal(&tap, &header, 0);
|
||||
header.set_seal(seal);
|
||||
// Good proposer.
|
||||
assert!(engine.verify_block_family(&header, &parent_header, None).is_ok());
|
||||
|
||||
let validator = insert_and_unlock(&tap, "0");
|
||||
header.set_author(validator);
|
||||
let seal = proposal_seal(&tap, &header, 0);
|
||||
header.set_seal(seal);
|
||||
// Bad proposer.
|
||||
match engine.verify_block_unordered(&header, None) {
|
||||
match engine.verify_block_family(&header, &parent_header, None) {
|
||||
Err(Error::Engine(EngineError::NotProposer(_))) => {},
|
||||
_ => panic!(),
|
||||
}
|
||||
@@ -788,7 +800,7 @@ mod tests {
|
||||
let seal = proposal_seal(&tap, &header, 0);
|
||||
header.set_seal(seal);
|
||||
// Not authority.
|
||||
match engine.verify_block_unordered(&header, None) {
|
||||
match engine.verify_block_family(&header, &parent_header, None) {
|
||||
Err(Error::Engine(EngineError::NotAuthorized(_))) => {},
|
||||
_ => panic!(),
|
||||
};
|
||||
@@ -800,19 +812,24 @@ mod tests {
|
||||
let (spec, tap) = setup();
|
||||
let engine = spec.engine;
|
||||
|
||||
let mut parent_header: Header = Header::default();
|
||||
parent_header.set_gas_limit(U256::from_str("222222").unwrap());
|
||||
|
||||
let mut header = Header::default();
|
||||
header.set_number(2);
|
||||
header.set_gas_limit(U256::from_str("222222").unwrap());
|
||||
let proposer = insert_and_unlock(&tap, "1");
|
||||
header.set_author(proposer);
|
||||
let mut seal = proposal_seal(&tap, &header, 0);
|
||||
|
||||
let vote_info = message_info_rlp(&VoteStep::new(0, 0, Step::Precommit), Some(header.bare_hash()));
|
||||
let vote_info = message_info_rlp(&VoteStep::new(2, 0, Step::Precommit), Some(header.bare_hash()));
|
||||
let signature1 = tap.sign(proposer, None, vote_info.sha3()).unwrap();
|
||||
|
||||
seal[2] = ::rlp::encode(&vec![H520::from(signature1.clone())]).to_vec();
|
||||
header.set_seal(seal.clone());
|
||||
|
||||
// One good signature is not enough.
|
||||
match engine.verify_block_unordered(&header, None) {
|
||||
match engine.verify_block_family(&header, &parent_header, None) {
|
||||
Err(Error::Engine(EngineError::BadSealFieldSize(_))) => {},
|
||||
_ => panic!(),
|
||||
}
|
||||
@@ -823,7 +840,7 @@ mod tests {
|
||||
seal[2] = ::rlp::encode(&vec![H520::from(signature1.clone()), H520::from(signature0.clone())]).to_vec();
|
||||
header.set_seal(seal.clone());
|
||||
|
||||
assert!(engine.verify_block_unordered(&header, None).is_ok());
|
||||
assert!(engine.verify_block_family(&header, &parent_header, None).is_ok());
|
||||
|
||||
let bad_voter = insert_and_unlock(&tap, "101");
|
||||
let bad_signature = tap.sign(bad_voter, None, vote_info.sha3()).unwrap();
|
||||
@@ -832,7 +849,7 @@ mod tests {
|
||||
header.set_seal(seal);
|
||||
|
||||
// One good and one bad signature.
|
||||
match engine.verify_block_unordered(&header, None) {
|
||||
match engine.verify_block_family(&header, &parent_header, None) {
|
||||
Err(Error::Engine(EngineError::NotAuthorized(_))) => {},
|
||||
_ => panic!(),
|
||||
};
|
||||
|
||||
@@ -26,30 +26,30 @@ use super::safe_contract::ValidatorSafeContract;
|
||||
/// The validator contract should have the following interface:
|
||||
/// [{"constant":true,"inputs":[],"name":"getValidators","outputs":[{"name":"","type":"address[]"}],"payable":false,"type":"function"}]
|
||||
pub struct ValidatorContract {
|
||||
validators: Arc<ValidatorSafeContract>,
|
||||
validators: ValidatorSafeContract,
|
||||
provider: RwLock<Option<provider::Contract>>,
|
||||
}
|
||||
|
||||
impl ValidatorContract {
|
||||
pub fn new(contract_address: Address) -> Self {
|
||||
ValidatorContract {
|
||||
validators: Arc::new(ValidatorSafeContract::new(contract_address)),
|
||||
validators: ValidatorSafeContract::new(contract_address),
|
||||
provider: RwLock::new(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ValidatorSet for Arc<ValidatorContract> {
|
||||
fn contains(&self, address: &Address) -> bool {
|
||||
self.validators.contains(address)
|
||||
impl ValidatorSet for ValidatorContract {
|
||||
fn contains(&self, bh: &H256, address: &Address) -> bool {
|
||||
self.validators.contains(bh, address)
|
||||
}
|
||||
|
||||
fn get(&self, nonce: usize) -> Address {
|
||||
self.validators.get(nonce)
|
||||
fn get(&self, bh: &H256, nonce: usize) -> Address {
|
||||
self.validators.get(bh, nonce)
|
||||
}
|
||||
|
||||
fn count(&self) -> usize {
|
||||
self.validators.count()
|
||||
fn count(&self, bh: &H256) -> usize {
|
||||
self.validators.count(bh)
|
||||
}
|
||||
|
||||
fn report_malicious(&self, address: &Address) {
|
||||
@@ -144,6 +144,7 @@ mod tests {
|
||||
use header::Header;
|
||||
use account_provider::AccountProvider;
|
||||
use miner::MinerService;
|
||||
use types::ids::BlockId;
|
||||
use client::BlockChainClient;
|
||||
use tests::helpers::generate_dummy_client_with_spec_and_accounts;
|
||||
use super::super::ValidatorSet;
|
||||
@@ -154,8 +155,9 @@ mod tests {
|
||||
let client = generate_dummy_client_with_spec_and_accounts(Spec::new_validator_contract, None);
|
||||
let vc = Arc::new(ValidatorContract::new(Address::from_str("0000000000000000000000000000000000000005").unwrap()));
|
||||
vc.register_contract(Arc::downgrade(&client));
|
||||
assert!(vc.contains(&Address::from_str("7d577a597b2742b498cb5cf0c26cdcd726d39e6e").unwrap()));
|
||||
assert!(vc.contains(&Address::from_str("82a978b3f5962a5b0957d9ee9eef472ee55b42f1").unwrap()));
|
||||
let last_hash = client.best_block_header().hash();
|
||||
assert!(vc.contains(&last_hash, &Address::from_str("7d577a597b2742b498cb5cf0c26cdcd726d39e6e").unwrap()));
|
||||
assert!(vc.contains(&last_hash, &Address::from_str("82a978b3f5962a5b0957d9ee9eef472ee55b42f1").unwrap()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -171,18 +173,21 @@ mod tests {
|
||||
|
||||
client.miner().set_engine_signer(v1, "".into()).unwrap();
|
||||
let mut header = Header::default();
|
||||
let seal = encode(&vec!(5u8)).to_vec();
|
||||
header.set_seal(vec!(seal));
|
||||
let seal = vec![encode(&5u8).to_vec(), encode(&(&H520::default() as &[u8])).to_vec()];
|
||||
header.set_seal(seal);
|
||||
header.set_author(v1);
|
||||
header.set_number(1);
|
||||
header.set_number(2);
|
||||
header.set_parent_hash(client.chain_info().best_block_hash);
|
||||
|
||||
// `reportBenign` when the designated proposer releases block from the future (bad clock).
|
||||
assert!(client.engine().verify_block_unordered(&header, None).is_err());
|
||||
assert!(client.engine().verify_block_family(&header, &header, None).is_err());
|
||||
// Seal a block.
|
||||
client.engine().step();
|
||||
assert_eq!(client.chain_info().best_block_number, 1);
|
||||
// Check if the unresponsive validator is `disliked`.
|
||||
assert_eq!(client.call_contract(validator_contract, "d8f2e0bf".from_hex().unwrap()).unwrap().to_hex(), "0000000000000000000000007d577a597b2742b498cb5cf0c26cdcd726d39e6e");
|
||||
assert_eq!(client.call_contract(BlockId::Latest, validator_contract, "d8f2e0bf".from_hex().unwrap()).unwrap().to_hex(), "0000000000000000000000007d577a597b2742b498cb5cf0c26cdcd726d39e6e");
|
||||
// Simulate a misbehaving validator by handling a double proposal.
|
||||
let header = client.best_block_header().decode();
|
||||
assert!(client.engine().verify_block_family(&header, &header, None).is_err());
|
||||
// Seal a block.
|
||||
client.engine().step();
|
||||
|
||||
@@ -19,31 +19,36 @@
|
||||
mod simple_list;
|
||||
mod safe_contract;
|
||||
mod contract;
|
||||
mod multi;
|
||||
|
||||
use std::sync::Weak;
|
||||
use util::{Address, Arc};
|
||||
use util::{Address, H256};
|
||||
use ethjson::spec::ValidatorSet as ValidatorSpec;
|
||||
use client::Client;
|
||||
use self::simple_list::SimpleList;
|
||||
use self::contract::ValidatorContract;
|
||||
use self::safe_contract::ValidatorSafeContract;
|
||||
use self::multi::Multi;
|
||||
|
||||
/// Creates a validator set from spec.
|
||||
pub fn new_validator_set(spec: ValidatorSpec) -> Box<ValidatorSet + Send + Sync> {
|
||||
pub fn new_validator_set(spec: ValidatorSpec) -> Box<ValidatorSet> {
|
||||
match spec {
|
||||
ValidatorSpec::List(list) => Box::new(SimpleList::new(list.into_iter().map(Into::into).collect())),
|
||||
ValidatorSpec::SafeContract(address) => Box::new(Arc::new(ValidatorSafeContract::new(address.into()))),
|
||||
ValidatorSpec::Contract(address) => Box::new(Arc::new(ValidatorContract::new(address.into()))),
|
||||
ValidatorSpec::SafeContract(address) => Box::new(ValidatorSafeContract::new(address.into())),
|
||||
ValidatorSpec::Contract(address) => Box::new(ValidatorContract::new(address.into())),
|
||||
ValidatorSpec::Multi(sequence) => Box::new(
|
||||
Multi::new(sequence.into_iter().map(|(block, set)| (block.into(), new_validator_set(set))).collect())
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ValidatorSet {
|
||||
pub trait ValidatorSet: Send + Sync {
|
||||
/// Checks if a given address is a validator.
|
||||
fn contains(&self, address: &Address) -> bool;
|
||||
fn contains(&self, parent_block_hash: &H256, address: &Address) -> bool;
|
||||
/// Draws an validator nonce modulo number of validators.
|
||||
fn get(&self, nonce: usize) -> Address;
|
||||
fn get(&self, parent_block_hash: &H256, nonce: usize) -> Address;
|
||||
/// Returns the current number of validators.
|
||||
fn count(&self) -> usize;
|
||||
fn count(&self, parent_block_hash: &H256) -> usize;
|
||||
/// Notifies about malicious behaviour.
|
||||
fn report_malicious(&self, _validator: &Address) {}
|
||||
/// Notifies about benign misbehaviour.
|
||||
|
||||
158
ethcore/src/engines/validator_set/multi.rs
Normal file
158
ethcore/src/engines/validator_set/multi.rs
Normal file
@@ -0,0 +1,158 @@
|
||||
// Copyright 2015-2017 Parity Technologies (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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
/// Validator set changing at fork blocks.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::Weak;
|
||||
use util::{H256, Address, RwLock};
|
||||
use ids::BlockId;
|
||||
use header::BlockNumber;
|
||||
use client::{Client, BlockChainClient};
|
||||
use super::ValidatorSet;
|
||||
|
||||
type BlockNumberLookup = Box<Fn(&H256) -> Result<BlockNumber, String> + Send + Sync + 'static>;
|
||||
|
||||
pub struct Multi {
|
||||
sets: BTreeMap<BlockNumber, Box<ValidatorSet>>,
|
||||
block_number: RwLock<BlockNumberLookup>,
|
||||
}
|
||||
|
||||
impl Multi {
|
||||
pub fn new(set_map: BTreeMap<BlockNumber, Box<ValidatorSet>>) -> Self {
|
||||
assert!(set_map.get(&0u64).is_some(), "ValidatorSet has to be specified from block 0.");
|
||||
Multi {
|
||||
sets: set_map,
|
||||
block_number: RwLock::new(Box::new(move |_| Err("No client!".into()))),
|
||||
}
|
||||
}
|
||||
|
||||
fn correct_set(&self, bh: &H256) -> Option<&Box<ValidatorSet>> {
|
||||
match self
|
||||
.block_number
|
||||
.read()(bh)
|
||||
.map(|parent_block| self
|
||||
.sets
|
||||
.iter()
|
||||
.rev()
|
||||
.find(|&(block, _)| *block <= parent_block + 1)
|
||||
.expect("constructor validation ensures that there is at least one validator set for block 0;
|
||||
block 0 is less than any uint;
|
||||
qed")
|
||||
) {
|
||||
Ok((block, set)) => {
|
||||
trace!(target: "engine", "Multi ValidatorSet retrieved for block {}.", block);
|
||||
Some(set)
|
||||
},
|
||||
Err(e) => {
|
||||
debug!(target: "engine", "ValidatorSet could not be recovered: {}", e);
|
||||
None
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ValidatorSet for Multi {
|
||||
fn contains(&self, bh: &H256, address: &Address) -> bool {
|
||||
self.correct_set(bh).map_or(false, |set| set.contains(bh, address))
|
||||
}
|
||||
|
||||
fn get(&self, bh: &H256, nonce: usize) -> Address {
|
||||
self.correct_set(bh).map_or_else(Default::default, |set| set.get(bh, nonce))
|
||||
}
|
||||
|
||||
fn count(&self, bh: &H256) -> usize {
|
||||
self.correct_set(bh).map_or_else(usize::max_value, |set| set.count(bh))
|
||||
}
|
||||
|
||||
fn report_malicious(&self, validator: &Address) {
|
||||
for set in self.sets.values() {
|
||||
set.report_malicious(validator);
|
||||
}
|
||||
}
|
||||
|
||||
fn report_benign(&self, validator: &Address) {
|
||||
for set in self.sets.values() {
|
||||
set.report_benign(validator);
|
||||
}
|
||||
}
|
||||
|
||||
fn register_contract(&self, client: Weak<Client>) {
|
||||
for set in self.sets.values() {
|
||||
set.register_contract(client.clone());
|
||||
}
|
||||
*self.block_number.write() = Box::new(move |hash| client
|
||||
.upgrade()
|
||||
.ok_or("No client!".into())
|
||||
.and_then(|c| c.block_number(BlockId::Hash(*hash)).ok_or("Unknown block".into())));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use util::*;
|
||||
use types::ids::BlockId;
|
||||
use spec::Spec;
|
||||
use account_provider::AccountProvider;
|
||||
use client::{BlockChainClient, EngineClient};
|
||||
use ethkey::Secret;
|
||||
use miner::MinerService;
|
||||
use tests::helpers::{generate_dummy_client_with_spec_and_accounts, generate_dummy_client_with_spec_and_data};
|
||||
|
||||
#[test]
|
||||
fn uses_current_set() {
|
||||
::env_logger::init().unwrap();
|
||||
let tap = Arc::new(AccountProvider::transient_provider());
|
||||
let s0 = Secret::from_slice(&"0".sha3()).unwrap();
|
||||
let v0 = tap.insert_account(s0.clone(), "").unwrap();
|
||||
let v1 = tap.insert_account(Secret::from_slice(&"1".sha3()).unwrap(), "").unwrap();
|
||||
let client = generate_dummy_client_with_spec_and_accounts(Spec::new_validator_multi, Some(tap));
|
||||
client.engine().register_client(Arc::downgrade(&client));
|
||||
|
||||
// Make sure txs go through.
|
||||
client.miner().set_gas_floor_target(1_000_000.into());
|
||||
|
||||
// Wrong signer for the first block.
|
||||
client.miner().set_engine_signer(v1, "".into()).unwrap();
|
||||
client.transact_contract(Default::default(), Default::default()).unwrap();
|
||||
client.update_sealing();
|
||||
assert_eq!(client.chain_info().best_block_number, 0);
|
||||
// Right signer for the first block.
|
||||
client.miner().set_engine_signer(v0, "".into()).unwrap();
|
||||
client.update_sealing();
|
||||
assert_eq!(client.chain_info().best_block_number, 1);
|
||||
// This time v0 is wrong.
|
||||
client.transact_contract(Default::default(), Default::default()).unwrap();
|
||||
client.update_sealing();
|
||||
assert_eq!(client.chain_info().best_block_number, 1);
|
||||
client.miner().set_engine_signer(v1, "".into()).unwrap();
|
||||
client.update_sealing();
|
||||
assert_eq!(client.chain_info().best_block_number, 2);
|
||||
// v1 is still good.
|
||||
client.transact_contract(Default::default(), Default::default()).unwrap();
|
||||
client.update_sealing();
|
||||
assert_eq!(client.chain_info().best_block_number, 3);
|
||||
|
||||
// Check syncing.
|
||||
let sync_client = generate_dummy_client_with_spec_and_data(Spec::new_validator_multi, 0, 0, &[]);
|
||||
sync_client.engine().register_client(Arc::downgrade(&sync_client));
|
||||
for i in 1..4 {
|
||||
sync_client.import_block(client.block(BlockId::Number(i)).unwrap().into_inner()).unwrap();
|
||||
}
|
||||
sync_client.flush_queue();
|
||||
assert_eq!(sync_client.chain_info().best_block_number, 3);
|
||||
}
|
||||
}
|
||||
@@ -17,17 +17,23 @@
|
||||
/// Validator set maintained in a contract, updated using `getValidators` method.
|
||||
|
||||
use std::sync::Weak;
|
||||
use ethabi;
|
||||
use util::*;
|
||||
use util::cache::MemoryLruCache;
|
||||
use types::ids::BlockId;
|
||||
use client::{Client, BlockChainClient};
|
||||
use client::chain_notify::ChainNotify;
|
||||
use super::ValidatorSet;
|
||||
use super::simple_list::SimpleList;
|
||||
|
||||
const MEMOIZE_CAPACITY: usize = 500;
|
||||
const CONTRACT_INTERFACE: &'static [u8] = b"[{\"constant\":true,\"inputs\":[],\"name\":\"getValidators\",\"outputs\":[{\"name\":\"\",\"type\":\"address[]\"}],\"payable\":false,\"type\":\"function\"}]";
|
||||
const GET_VALIDATORS: &'static str = "getValidators";
|
||||
|
||||
/// The validator contract should have the following interface:
|
||||
/// [{"constant":true,"inputs":[],"name":"getValidators","outputs":[{"name":"","type":"address[]"}],"payable":false,"type":"function"}]
|
||||
pub struct ValidatorSafeContract {
|
||||
pub address: Address,
|
||||
validators: RwLock<SimpleList>,
|
||||
validators: RwLock<MemoryLruCache<H256, SimpleList>>,
|
||||
provider: RwLock<Option<provider::Contract>>,
|
||||
}
|
||||
|
||||
@@ -35,102 +41,127 @@ impl ValidatorSafeContract {
|
||||
pub fn new(contract_address: Address) -> Self {
|
||||
ValidatorSafeContract {
|
||||
address: contract_address,
|
||||
validators: Default::default(),
|
||||
validators: RwLock::new(MemoryLruCache::new(MEMOIZE_CAPACITY)),
|
||||
provider: RwLock::new(None),
|
||||
}
|
||||
}
|
||||
|
||||
/// Queries the state and updates the set of validators.
|
||||
pub fn update(&self) {
|
||||
/// Queries the state and gets the set of validators.
|
||||
fn get_list(&self, block_hash: H256) -> Option<SimpleList> {
|
||||
if let Some(ref provider) = *self.provider.read() {
|
||||
match provider.get_validators() {
|
||||
match provider.get_validators(BlockId::Hash(block_hash)) {
|
||||
Ok(new) => {
|
||||
debug!(target: "engine", "Set of validators obtained: {:?}", new);
|
||||
*self.validators.write() = SimpleList::new(new);
|
||||
Some(SimpleList::new(new))
|
||||
},
|
||||
Err(s) => {
|
||||
debug!(target: "engine", "Set of validators could not be updated: {}", s);
|
||||
None
|
||||
},
|
||||
Err(s) => warn!(target: "engine", "Set of validators could not be updated: {}", s),
|
||||
}
|
||||
} else {
|
||||
warn!(target: "engine", "Set of validators could not be updated: no provider contract.")
|
||||
warn!(target: "engine", "Set of validators could not be updated: no provider contract.");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks validators on every block.
|
||||
impl ChainNotify for ValidatorSafeContract {
|
||||
fn new_blocks(
|
||||
&self,
|
||||
_: Vec<H256>,
|
||||
_: Vec<H256>,
|
||||
enacted: Vec<H256>,
|
||||
_: Vec<H256>,
|
||||
_: Vec<H256>,
|
||||
_: Vec<Bytes>,
|
||||
_duration: u64) {
|
||||
if !enacted.is_empty() {
|
||||
self.update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ValidatorSet for Arc<ValidatorSafeContract> {
|
||||
fn contains(&self, address: &Address) -> bool {
|
||||
self.validators.read().contains(address)
|
||||
impl ValidatorSet for ValidatorSafeContract {
|
||||
fn contains(&self, block_hash: &H256, address: &Address) -> bool {
|
||||
let mut guard = self.validators.write();
|
||||
let maybe_existing = guard
|
||||
.get_mut(block_hash)
|
||||
.map(|list| list.contains(block_hash, address));
|
||||
maybe_existing
|
||||
.unwrap_or_else(|| self
|
||||
.get_list(block_hash.clone())
|
||||
.map_or(false, |list| {
|
||||
let contains = list.contains(block_hash, address);
|
||||
guard.insert(block_hash.clone(), list);
|
||||
contains
|
||||
}))
|
||||
}
|
||||
|
||||
fn get(&self, nonce: usize) -> Address {
|
||||
self.validators.read().get(nonce)
|
||||
fn get(&self, block_hash: &H256, nonce: usize) -> Address {
|
||||
let mut guard = self.validators.write();
|
||||
let maybe_existing = guard
|
||||
.get_mut(block_hash)
|
||||
.map(|list| list.get(block_hash, nonce));
|
||||
maybe_existing
|
||||
.unwrap_or_else(|| self
|
||||
.get_list(block_hash.clone())
|
||||
.map_or_else(Default::default, |list| {
|
||||
let address = list.get(block_hash, nonce);
|
||||
guard.insert(block_hash.clone(), list);
|
||||
address
|
||||
}))
|
||||
}
|
||||
|
||||
fn count(&self) -> usize {
|
||||
self.validators.read().count()
|
||||
fn count(&self, block_hash: &H256) -> usize {
|
||||
let mut guard = self.validators.write();
|
||||
let maybe_existing = guard
|
||||
.get_mut(block_hash)
|
||||
.map(|list| list.count(block_hash));
|
||||
maybe_existing
|
||||
.unwrap_or_else(|| self
|
||||
.get_list(block_hash.clone())
|
||||
.map_or_else(usize::max_value, |list| {
|
||||
let address = list.count(block_hash);
|
||||
guard.insert(block_hash.clone(), list);
|
||||
address
|
||||
}))
|
||||
}
|
||||
|
||||
fn register_contract(&self, client: Weak<Client>) {
|
||||
if let Some(c) = client.upgrade() {
|
||||
c.add_notify(self.clone());
|
||||
}
|
||||
{
|
||||
*self.provider.write() = Some(provider::Contract::new(self.address, move |a, d| client.upgrade().ok_or("No client!".into()).and_then(|c| c.call_contract(a, d))));
|
||||
}
|
||||
self.update();
|
||||
trace!(target: "engine", "Setting up contract caller.");
|
||||
let contract = ethabi::Contract::new(ethabi::Interface::load(CONTRACT_INTERFACE).expect("JSON interface is valid; qed"));
|
||||
let call = contract.function(GET_VALIDATORS.into()).expect("Method name is valid; qed");
|
||||
let data = call.encode_call(vec![]).expect("get_validators does not take any arguments; qed");
|
||||
let contract_address = self.address.clone();
|
||||
let do_call = move |id| client
|
||||
.upgrade()
|
||||
.ok_or("No client!".into())
|
||||
.and_then(|c| c.call_contract(id, contract_address.clone(), data.clone()))
|
||||
.map(|raw_output| call.decode_output(raw_output).expect("ethabi is correct; qed"));
|
||||
*self.provider.write() = Some(provider::Contract::new(do_call));
|
||||
}
|
||||
}
|
||||
|
||||
mod provider {
|
||||
// Autogenerated from JSON contract definition using Rust contract convertor.
|
||||
#![allow(unused_imports)]
|
||||
use std::string::String;
|
||||
use std::result::Result;
|
||||
use std::fmt;
|
||||
use {util, ethabi};
|
||||
use util::{FixedHash, Uint};
|
||||
use types::ids::BlockId;
|
||||
|
||||
pub struct Contract {
|
||||
contract: ethabi::Contract,
|
||||
address: util::Address,
|
||||
do_call: Box<Fn(util::Address, Vec<u8>) -> Result<Vec<u8>, String> + Send + Sync + 'static>,
|
||||
do_call: Box<Fn(BlockId) -> Result<Vec<ethabi::Token>, String> + Send + Sync + 'static>,
|
||||
}
|
||||
|
||||
impl Contract {
|
||||
pub fn new<F>(address: util::Address, do_call: F) -> Self where F: Fn(util::Address, Vec<u8>) -> Result<Vec<u8>, String> + Send + Sync + 'static {
|
||||
pub fn new<F>(do_call: F) -> Self where F: Fn(BlockId) -> Result<Vec<ethabi::Token>, String> + Send + Sync + 'static {
|
||||
Contract {
|
||||
contract: ethabi::Contract::new(ethabi::Interface::load(b"[{\"constant\":true,\"inputs\":[],\"name\":\"getValidators\",\"outputs\":[{\"name\":\"\",\"type\":\"address[]\"}],\"payable\":false,\"type\":\"function\"}]").expect("JSON is autogenerated; qed")),
|
||||
address: address,
|
||||
do_call: Box::new(do_call),
|
||||
}
|
||||
}
|
||||
fn as_string<T: fmt::Debug>(e: T) -> String { format!("{:?}", e) }
|
||||
|
||||
/// Auto-generated from: `{"constant":true,"inputs":[],"name":"getValidators","outputs":[{"name":"","type":"address[]"}],"payable":false,"type":"function"}`
|
||||
#[allow(dead_code)]
|
||||
pub fn get_validators(&self) -> Result<Vec<util::Address>, String> {
|
||||
let call = self.contract.function("getValidators".into()).map_err(Self::as_string)?;
|
||||
let data = call.encode_call(
|
||||
vec![]
|
||||
).map_err(Self::as_string)?;
|
||||
let output = call.decode_output((self.do_call)(self.address.clone(), data)?).map_err(Self::as_string)?;
|
||||
let mut result = output.into_iter().rev().collect::<Vec<_>>();
|
||||
Ok(({ let r = result.pop().ok_or("Invalid return arity")?; let r = r.to_array().and_then(|v| v.into_iter().map(|a| a.to_address()).collect::<Option<Vec<[u8; 20]>>>()).ok_or("Invalid type returned")?; r.into_iter().map(|a| util::Address::from(a)).collect::<Vec<_>>() }))
|
||||
|
||||
/// Gets validators from contract with interface: `{"constant":true,"inputs":[],"name":"getValidators","outputs":[{"name":"","type":"address[]"}],"payable":false,"type":"function"}`
|
||||
pub fn get_validators(&self, id: BlockId) -> Result<Vec<util::Address>, String> {
|
||||
Ok((self.do_call)(id)?
|
||||
.into_iter()
|
||||
.rev()
|
||||
.collect::<Vec<_>>()
|
||||
.pop()
|
||||
.expect("get_validators returns one argument; qed")
|
||||
.to_array()
|
||||
.and_then(|v| v
|
||||
.into_iter()
|
||||
.map(|a| a.to_address())
|
||||
.collect::<Option<Vec<[u8; 20]>>>())
|
||||
.expect("get_validators returns a list of addresses; qed")
|
||||
.into_iter()
|
||||
.map(util::Address::from)
|
||||
.collect::<Vec<_>>()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -138,13 +169,14 @@ mod provider {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use util::*;
|
||||
use types::ids::BlockId;
|
||||
use spec::Spec;
|
||||
use account_provider::AccountProvider;
|
||||
use transaction::{Transaction, Action};
|
||||
use client::{BlockChainClient, EngineClient};
|
||||
use ethkey::Secret;
|
||||
use miner::MinerService;
|
||||
use tests::helpers::generate_dummy_client_with_spec_and_accounts;
|
||||
use tests::helpers::{generate_dummy_client_with_spec_and_accounts, generate_dummy_client_with_spec_and_data};
|
||||
use super::super::ValidatorSet;
|
||||
use super::ValidatorSafeContract;
|
||||
|
||||
@@ -153,12 +185,13 @@ mod tests {
|
||||
let client = generate_dummy_client_with_spec_and_accounts(Spec::new_validator_safe_contract, None);
|
||||
let vc = Arc::new(ValidatorSafeContract::new(Address::from_str("0000000000000000000000000000000000000005").unwrap()));
|
||||
vc.register_contract(Arc::downgrade(&client));
|
||||
assert!(vc.contains(&Address::from_str("7d577a597b2742b498cb5cf0c26cdcd726d39e6e").unwrap()));
|
||||
assert!(vc.contains(&Address::from_str("82a978b3f5962a5b0957d9ee9eef472ee55b42f1").unwrap()));
|
||||
let last_hash = client.best_block_header().hash();
|
||||
assert!(vc.contains(&last_hash, &Address::from_str("7d577a597b2742b498cb5cf0c26cdcd726d39e6e").unwrap()));
|
||||
assert!(vc.contains(&last_hash, &Address::from_str("82a978b3f5962a5b0957d9ee9eef472ee55b42f1").unwrap()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn updates_validators() {
|
||||
fn knows_validators() {
|
||||
let tap = Arc::new(AccountProvider::transient_provider());
|
||||
let s0 = Secret::from_slice(&"1".sha3()).unwrap();
|
||||
let v0 = tap.insert_account(s0.clone(), "").unwrap();
|
||||
@@ -212,5 +245,14 @@ mod tests {
|
||||
client.update_sealing();
|
||||
// Able to seal again.
|
||||
assert_eq!(client.chain_info().best_block_number, 3);
|
||||
|
||||
// Check syncing.
|
||||
let sync_client = generate_dummy_client_with_spec_and_data(Spec::new_validator_safe_contract, 0, 0, &[]);
|
||||
sync_client.engine().register_client(Arc::downgrade(&sync_client));
|
||||
for i in 1..4 {
|
||||
sync_client.import_block(client.block(BlockId::Number(i)).unwrap().into_inner()).unwrap();
|
||||
}
|
||||
sync_client.flush_queue();
|
||||
assert_eq!(sync_client.chain_info().best_block_number, 3);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
/// Preconfigured validator list.
|
||||
|
||||
use util::Address;
|
||||
use util::{H256, Address, HeapSizeOf};
|
||||
use super::ValidatorSet;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Default)]
|
||||
@@ -34,16 +34,22 @@ impl SimpleList {
|
||||
}
|
||||
}
|
||||
|
||||
impl HeapSizeOf for SimpleList {
|
||||
fn heap_size_of_children(&self) -> usize {
|
||||
self.validators.heap_size_of_children() + self.validator_n.heap_size_of_children()
|
||||
}
|
||||
}
|
||||
|
||||
impl ValidatorSet for SimpleList {
|
||||
fn contains(&self, address: &Address) -> bool {
|
||||
fn contains(&self, _bh: &H256, address: &Address) -> bool {
|
||||
self.validators.contains(address)
|
||||
}
|
||||
|
||||
fn get(&self, nonce: usize) -> Address {
|
||||
fn get(&self, _bh: &H256, nonce: usize) -> Address {
|
||||
self.validators.get(nonce % self.validator_n).expect("There are validator_n authorities; taking number modulo validator_n gives number in validator_n range; qed").clone()
|
||||
}
|
||||
|
||||
fn count(&self) -> usize {
|
||||
fn count(&self, _bh: &H256) -> usize {
|
||||
self.validator_n
|
||||
}
|
||||
}
|
||||
@@ -60,9 +66,9 @@ mod tests {
|
||||
let a1 = Address::from_str("cd1722f3947def4cf144679da39c4c32bdc35681").unwrap();
|
||||
let a2 = Address::from_str("0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6").unwrap();
|
||||
let list = SimpleList::new(vec![a1.clone(), a2.clone()]);
|
||||
assert!(list.contains(&a1));
|
||||
assert_eq!(list.get(0), a1);
|
||||
assert_eq!(list.get(1), a2);
|
||||
assert_eq!(list.get(2), a1);
|
||||
assert!(list.contains(&Default::default(), &a1));
|
||||
assert_eq!(list.get(&Default::default(), 0), a1);
|
||||
assert_eq!(list.get(&Default::default(), 1), a2);
|
||||
assert_eq!(list.get(&Default::default(), 2), a1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,6 +107,7 @@ extern crate ethcore_stratum;
|
||||
extern crate ethabi;
|
||||
extern crate hardware_wallet;
|
||||
extern crate stats;
|
||||
extern crate itertools;
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
@@ -230,7 +230,8 @@ pub struct Miner {
|
||||
impl Miner {
|
||||
/// Push notifier that will handle new jobs
|
||||
pub fn push_notifier(&self, notifier: Box<NotifyWork>) {
|
||||
self.notifiers.write().push(notifier)
|
||||
self.notifiers.write().push(notifier);
|
||||
self.sealing_work.lock().enabled = true;
|
||||
}
|
||||
|
||||
/// Creates new instance of miner Arc.
|
||||
@@ -298,7 +299,7 @@ impl Miner {
|
||||
}
|
||||
|
||||
fn forced_sealing(&self) -> bool {
|
||||
self.options.force_sealing || !self.options.new_work_notify.is_empty()
|
||||
self.options.force_sealing || !self.notifiers.read().is_empty()
|
||||
}
|
||||
|
||||
/// Clear all pending block states
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use types::ids::BlockId;
|
||||
use client::MiningBlockChainClient;
|
||||
use transaction::SignedTransaction;
|
||||
use util::{U256, Uint, Mutex};
|
||||
@@ -45,7 +46,7 @@ impl ServiceTransactionChecker {
|
||||
debug_assert_eq!(tx.gas_price, U256::zero());
|
||||
|
||||
if let Some(ref contract) = *self.contract.lock() {
|
||||
let do_call = |a, d| client.call_contract(a, d);
|
||||
let do_call = |a, d| client.call_contract(BlockId::Latest, a, d);
|
||||
contract.certified(&do_call, &tx.sender())
|
||||
} else {
|
||||
Err("contract is not configured".to_owned())
|
||||
|
||||
@@ -211,6 +211,8 @@ impl From<AddrParseError> for Error {
|
||||
|
||||
impl super::work_notify::NotifyWork for Stratum {
|
||||
fn notify(&self, pow_hash: H256, difficulty: U256, number: u64) {
|
||||
trace!(target: "stratum", "Notify work");
|
||||
|
||||
self.service.push_work_all(
|
||||
self.dispatcher.payload(pow_hash, difficulty, number)
|
||||
).unwrap_or_else(
|
||||
|
||||
@@ -23,6 +23,7 @@ use snapshot::Error;
|
||||
use util::{U256, FixedHash, H256, Bytes, HashDB, SHA3_EMPTY, SHA3_NULL_RLP};
|
||||
use util::trie::{TrieDB, Trie};
|
||||
use rlp::{RlpStream, Stream, UntrustedRlp, View};
|
||||
use itertools::Itertools;
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
@@ -60,55 +61,53 @@ impl CodeState {
|
||||
}
|
||||
}
|
||||
|
||||
// walk the account's storage trie, returning an RLP item containing the
|
||||
// account properties and the storage.
|
||||
pub fn to_fat_rlp(acc: &BasicAccount, acct_db: &AccountDB, used_code: &mut HashSet<H256>) -> Result<Bytes, Error> {
|
||||
// walk the account's storage trie, returning a vector of RLP items containing the
|
||||
// account properties and the storage. Each item contains at most `max_storage_items`
|
||||
// storage records split according to snapshot format definition.
|
||||
pub fn to_fat_rlps(acc: &BasicAccount, acct_db: &AccountDB, used_code: &mut HashSet<H256>, max_storage_items: usize) -> Result<Vec<Bytes>, Error> {
|
||||
if acc == &ACC_EMPTY {
|
||||
return Ok(::rlp::NULL_RLP.to_vec());
|
||||
return Ok(vec![::rlp::NULL_RLP.to_vec()]);
|
||||
}
|
||||
|
||||
let db = TrieDB::new(acct_db, &acc.storage_root)?;
|
||||
|
||||
let mut pairs = Vec::new();
|
||||
let chunks = db.iter()?.chunks(max_storage_items);
|
||||
let pair_chunks = chunks.into_iter().map(|chunk| chunk.collect());
|
||||
pair_chunks.pad_using(1, |_| Vec::new(), ).map(|pairs| {
|
||||
let mut stream = RlpStream::new_list(pairs.len());
|
||||
|
||||
for item in db.iter()? {
|
||||
let (k, v) = item?;
|
||||
pairs.push((k, v));
|
||||
}
|
||||
for r in pairs {
|
||||
let (k, v) = r?;
|
||||
stream.begin_list(2).append(&k).append(&&*v);
|
||||
}
|
||||
|
||||
let mut stream = RlpStream::new_list(pairs.len());
|
||||
let pairs_rlp = stream.out();
|
||||
|
||||
for (k, v) in pairs {
|
||||
stream.begin_list(2).append(&k).append(&&*v);
|
||||
}
|
||||
let mut account_stream = RlpStream::new_list(5);
|
||||
account_stream.append(&acc.nonce)
|
||||
.append(&acc.balance);
|
||||
|
||||
let pairs_rlp = stream.out();
|
||||
|
||||
let mut account_stream = RlpStream::new_list(5);
|
||||
account_stream.append(&acc.nonce)
|
||||
.append(&acc.balance);
|
||||
|
||||
// [has_code, code_hash].
|
||||
if acc.code_hash == SHA3_EMPTY {
|
||||
account_stream.append(&CodeState::Empty.raw()).append_empty_data();
|
||||
} else if used_code.contains(&acc.code_hash) {
|
||||
account_stream.append(&CodeState::Hash.raw()).append(&acc.code_hash);
|
||||
} else {
|
||||
match acct_db.get(&acc.code_hash) {
|
||||
Some(c) => {
|
||||
used_code.insert(acc.code_hash.clone());
|
||||
account_stream.append(&CodeState::Inline.raw()).append(&&*c);
|
||||
}
|
||||
None => {
|
||||
warn!("code lookup failed during snapshot");
|
||||
account_stream.append(&false).append_empty_data();
|
||||
// [has_code, code_hash].
|
||||
if acc.code_hash == SHA3_EMPTY {
|
||||
account_stream.append(&CodeState::Empty.raw()).append_empty_data();
|
||||
} else if used_code.contains(&acc.code_hash) {
|
||||
account_stream.append(&CodeState::Hash.raw()).append(&acc.code_hash);
|
||||
} else {
|
||||
match acct_db.get(&acc.code_hash) {
|
||||
Some(c) => {
|
||||
used_code.insert(acc.code_hash.clone());
|
||||
account_stream.append(&CodeState::Inline.raw()).append(&&*c);
|
||||
}
|
||||
None => {
|
||||
warn!("code lookup failed during snapshot");
|
||||
account_stream.append(&false).append_empty_data();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
account_stream.append_raw(&pairs_rlp, 1);
|
||||
|
||||
Ok(account_stream.out())
|
||||
account_stream.append_raw(&pairs_rlp, 1);
|
||||
Ok(account_stream.out())
|
||||
}).collect()
|
||||
}
|
||||
|
||||
// decode a fat rlp, and rebuild the storage trie as we go.
|
||||
@@ -117,6 +116,7 @@ pub fn to_fat_rlp(acc: &BasicAccount, acct_db: &AccountDB, used_code: &mut HashS
|
||||
pub fn from_fat_rlp(
|
||||
acct_db: &mut AccountDBMut,
|
||||
rlp: UntrustedRlp,
|
||||
mut storage_root: H256,
|
||||
) -> Result<(BasicAccount, Option<Bytes>), Error> {
|
||||
use util::{TrieDBMut, TrieMut};
|
||||
|
||||
@@ -148,10 +148,12 @@ pub fn from_fat_rlp(
|
||||
}
|
||||
};
|
||||
|
||||
let mut storage_root = H256::zero();
|
||||
|
||||
{
|
||||
let mut storage_trie = TrieDBMut::new(acct_db, &mut storage_root);
|
||||
let mut storage_trie = if storage_root.is_zero() {
|
||||
TrieDBMut::new(acct_db, &mut storage_root)
|
||||
} else {
|
||||
TrieDBMut::from_existing(acct_db, &mut storage_root)?
|
||||
};
|
||||
let pairs = rlp.at(4)?;
|
||||
for pair_rlp in pairs.iter() {
|
||||
let k: Bytes = pair_rlp.val_at(0)?;
|
||||
@@ -184,7 +186,7 @@ mod tests {
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
use super::{ACC_EMPTY, to_fat_rlp, from_fat_rlp};
|
||||
use super::{ACC_EMPTY, to_fat_rlps, from_fat_rlp};
|
||||
|
||||
#[test]
|
||||
fn encoding_basic() {
|
||||
@@ -201,9 +203,9 @@ mod tests {
|
||||
let thin_rlp = ::rlp::encode(&account);
|
||||
assert_eq!(::rlp::decode::<BasicAccount>(&thin_rlp), account);
|
||||
|
||||
let fat_rlp = to_fat_rlp(&account, &AccountDB::new(db.as_hashdb(), &addr), &mut Default::default()).unwrap();
|
||||
let fat_rlp = UntrustedRlp::new(&fat_rlp);
|
||||
assert_eq!(from_fat_rlp(&mut AccountDBMut::new(db.as_hashdb_mut(), &addr), fat_rlp).unwrap().0, account);
|
||||
let fat_rlps = to_fat_rlps(&account, &AccountDB::new(db.as_hashdb(), &addr), &mut Default::default(), usize::max_value()).unwrap();
|
||||
let fat_rlp = UntrustedRlp::new(&fat_rlps[0]);
|
||||
assert_eq!(from_fat_rlp(&mut AccountDBMut::new(db.as_hashdb_mut(), &addr), fat_rlp, H256::zero()).unwrap().0, account);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -226,9 +228,40 @@ mod tests {
|
||||
let thin_rlp = ::rlp::encode(&account);
|
||||
assert_eq!(::rlp::decode::<BasicAccount>(&thin_rlp), account);
|
||||
|
||||
let fat_rlp = to_fat_rlp(&account, &AccountDB::new(db.as_hashdb(), &addr), &mut Default::default()).unwrap();
|
||||
let fat_rlp = UntrustedRlp::new(&fat_rlp);
|
||||
assert_eq!(from_fat_rlp(&mut AccountDBMut::new(db.as_hashdb_mut(), &addr), fat_rlp).unwrap().0, account);
|
||||
let fat_rlp = to_fat_rlps(&account, &AccountDB::new(db.as_hashdb(), &addr), &mut Default::default(), usize::max_value()).unwrap();
|
||||
let fat_rlp = UntrustedRlp::new(&fat_rlp[0]);
|
||||
assert_eq!(from_fat_rlp(&mut AccountDBMut::new(db.as_hashdb_mut(), &addr), fat_rlp, H256::zero()).unwrap().0, account);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encoding_storage_split() {
|
||||
let mut db = get_temp_state_db();
|
||||
let addr = Address::random();
|
||||
|
||||
let account = {
|
||||
let acct_db = AccountDBMut::new(db.as_hashdb_mut(), &addr);
|
||||
let mut root = SHA3_NULL_RLP;
|
||||
fill_storage(acct_db, &mut root, &mut H256::zero());
|
||||
BasicAccount {
|
||||
nonce: 25.into(),
|
||||
balance: 987654321.into(),
|
||||
storage_root: root,
|
||||
code_hash: SHA3_EMPTY,
|
||||
}
|
||||
};
|
||||
|
||||
let thin_rlp = ::rlp::encode(&account);
|
||||
assert_eq!(::rlp::decode::<BasicAccount>(&thin_rlp), account);
|
||||
|
||||
let fat_rlps = to_fat_rlps(&account, &AccountDB::new(db.as_hashdb(), &addr), &mut Default::default(), 100).unwrap();
|
||||
let mut root = SHA3_NULL_RLP;
|
||||
let mut restored_account = None;
|
||||
for rlp in fat_rlps {
|
||||
let fat_rlp = UntrustedRlp::new(&rlp);
|
||||
restored_account = Some(from_fat_rlp(&mut AccountDBMut::new(db.as_hashdb_mut(), &addr), fat_rlp, root).unwrap().0);
|
||||
root = restored_account.as_ref().unwrap().storage_root.clone();
|
||||
}
|
||||
assert_eq!(restored_account, Some(account));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -264,18 +297,18 @@ mod tests {
|
||||
|
||||
let mut used_code = HashSet::new();
|
||||
|
||||
let fat_rlp1 = to_fat_rlp(&account1, &AccountDB::new(db.as_hashdb(), &addr1), &mut used_code).unwrap();
|
||||
let fat_rlp2 = to_fat_rlp(&account2, &AccountDB::new(db.as_hashdb(), &addr2), &mut used_code).unwrap();
|
||||
let fat_rlp1 = to_fat_rlps(&account1, &AccountDB::new(db.as_hashdb(), &addr1), &mut used_code, usize::max_value()).unwrap();
|
||||
let fat_rlp2 = to_fat_rlps(&account2, &AccountDB::new(db.as_hashdb(), &addr2), &mut used_code, usize::max_value()).unwrap();
|
||||
assert_eq!(used_code.len(), 1);
|
||||
|
||||
let fat_rlp1 = UntrustedRlp::new(&fat_rlp1);
|
||||
let fat_rlp2 = UntrustedRlp::new(&fat_rlp2);
|
||||
let fat_rlp1 = UntrustedRlp::new(&fat_rlp1[0]);
|
||||
let fat_rlp2 = UntrustedRlp::new(&fat_rlp2[0]);
|
||||
|
||||
let (acc, maybe_code) = from_fat_rlp(&mut AccountDBMut::new(db.as_hashdb_mut(), &addr2), fat_rlp2).unwrap();
|
||||
let (acc, maybe_code) = from_fat_rlp(&mut AccountDBMut::new(db.as_hashdb_mut(), &addr2), fat_rlp2, H256::zero()).unwrap();
|
||||
assert!(maybe_code.is_none());
|
||||
assert_eq!(acc, account2);
|
||||
|
||||
let (acc, maybe_code) = from_fat_rlp(&mut AccountDBMut::new(db.as_hashdb_mut(), &addr1), fat_rlp1).unwrap();
|
||||
let (acc, maybe_code) = from_fat_rlp(&mut AccountDBMut::new(db.as_hashdb_mut(), &addr1), fat_rlp1, H256::zero()).unwrap();
|
||||
assert_eq!(maybe_code, Some(b"this is definitely code".to_vec()));
|
||||
assert_eq!(acc, account1);
|
||||
}
|
||||
@@ -285,7 +318,7 @@ mod tests {
|
||||
let mut db = get_temp_state_db();
|
||||
let mut used_code = HashSet::new();
|
||||
|
||||
assert_eq!(to_fat_rlp(&ACC_EMPTY, &AccountDB::new(db.as_hashdb(), &Address::default()), &mut used_code).unwrap(), ::rlp::NULL_RLP.to_vec());
|
||||
assert_eq!(from_fat_rlp(&mut AccountDBMut::new(db.as_hashdb_mut(), &Address::default()), UntrustedRlp::new(&::rlp::NULL_RLP)).unwrap(), (ACC_EMPTY, None));
|
||||
assert_eq!(to_fat_rlps(&ACC_EMPTY, &AccountDB::new(db.as_hashdb(), &Address::default()), &mut used_code, usize::max_value()).unwrap(), vec![::rlp::NULL_RLP.to_vec()]);
|
||||
assert_eq!(from_fat_rlp(&mut AccountDBMut::new(db.as_hashdb_mut(), &Address::default()), UntrustedRlp::new(&::rlp::NULL_RLP), H256::zero()).unwrap(), (ACC_EMPTY, None));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,8 @@ pub enum Error {
|
||||
Decoder(DecoderError),
|
||||
/// Io error.
|
||||
Io(::std::io::Error),
|
||||
/// Snapshot version is not supported.
|
||||
VersionNotSupported(u64),
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
@@ -73,6 +75,7 @@ impl fmt::Display for Error {
|
||||
Error::Io(ref err) => err.fmt(f),
|
||||
Error::Decoder(ref err) => err.fmt(f),
|
||||
Error::Trie(ref err) => err.fmt(f),
|
||||
Error::VersionNotSupported(ref ver) => write!(f, "Snapshot version {} is not supprted.", ver),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,8 @@ use rlp::{self, Encodable, RlpStream, UntrustedRlp, Stream, View};
|
||||
|
||||
use super::ManifestData;
|
||||
|
||||
const SNAPSHOT_VERSION: u64 = 2;
|
||||
|
||||
/// Something which can write snapshots.
|
||||
/// Writing the same chunk multiple times will lead to implementation-defined
|
||||
/// behavior, and is not advised.
|
||||
@@ -120,8 +122,9 @@ impl SnapshotWriter for PackedWriter {
|
||||
fn finish(mut self, manifest: ManifestData) -> io::Result<()> {
|
||||
// we ignore the hashes fields of the manifest under the assumption that
|
||||
// they are consistent with ours.
|
||||
let mut stream = RlpStream::new_list(5);
|
||||
let mut stream = RlpStream::new_list(6);
|
||||
stream
|
||||
.append(&SNAPSHOT_VERSION)
|
||||
.append(&self.state_hashes)
|
||||
.append(&self.block_hashes)
|
||||
.append(&manifest.state_root)
|
||||
@@ -223,7 +226,7 @@ impl PackedReader {
|
||||
/// Create a new `PackedReader` for the file at the given path.
|
||||
/// This will fail if any io errors are encountered or the file
|
||||
/// is not a valid packed snapshot.
|
||||
pub fn new(path: &Path) -> Result<Option<Self>, ::error::Error> {
|
||||
pub fn new(path: &Path) -> Result<Option<Self>, ::snapshot::error::Error> {
|
||||
let mut file = File::open(path)?;
|
||||
let file_len = file.metadata()?.len();
|
||||
if file_len < 8 {
|
||||
@@ -257,15 +260,26 @@ impl PackedReader {
|
||||
|
||||
let rlp = UntrustedRlp::new(&manifest_buf);
|
||||
|
||||
let state: Vec<ChunkInfo> = rlp.val_at(0)?;
|
||||
let blocks: Vec<ChunkInfo> = rlp.val_at(1)?;
|
||||
let (start, version) = if rlp.item_count() == 5 {
|
||||
(0, 1)
|
||||
} else {
|
||||
(1, rlp.val_at(0)?)
|
||||
};
|
||||
|
||||
if version > SNAPSHOT_VERSION {
|
||||
return Err(::snapshot::error::Error::VersionNotSupported(version));
|
||||
}
|
||||
|
||||
let state: Vec<ChunkInfo> = rlp.val_at(0 + start)?;
|
||||
let blocks: Vec<ChunkInfo> = rlp.val_at(1 + start)?;
|
||||
|
||||
let manifest = ManifestData {
|
||||
version: version,
|
||||
state_hashes: state.iter().map(|c| c.0).collect(),
|
||||
block_hashes: blocks.iter().map(|c| c.0).collect(),
|
||||
state_root: rlp.val_at(2)?,
|
||||
block_number: rlp.val_at(3)?,
|
||||
block_hash: rlp.val_at(4)?,
|
||||
state_root: rlp.val_at(2 + start)?,
|
||||
block_number: rlp.val_at(3 + start)?,
|
||||
block_hash: rlp.val_at(4 + start)?,
|
||||
};
|
||||
|
||||
Ok(Some(PackedReader {
|
||||
@@ -348,7 +362,7 @@ mod tests {
|
||||
use util::sha3::Hashable;
|
||||
|
||||
use snapshot::ManifestData;
|
||||
use super::{SnapshotWriter, SnapshotReader, PackedWriter, PackedReader, LooseWriter, LooseReader};
|
||||
use super::{SnapshotWriter, SnapshotReader, PackedWriter, PackedReader, LooseWriter, LooseReader, SNAPSHOT_VERSION};
|
||||
|
||||
const STATE_CHUNKS: &'static [&'static [u8]] = &[b"dog", b"cat", b"hello world", b"hi", b"notarealchunk"];
|
||||
const BLOCK_CHUNKS: &'static [&'static [u8]] = &[b"hello!", b"goodbye!", b"abcdefg", b"hijklmnop", b"qrstuvwxy", b"and", b"z"];
|
||||
@@ -374,6 +388,7 @@ mod tests {
|
||||
}
|
||||
|
||||
let manifest = ManifestData {
|
||||
version: SNAPSHOT_VERSION,
|
||||
state_hashes: state_hashes,
|
||||
block_hashes: block_hashes,
|
||||
state_root: b"notarealroot".sha3(),
|
||||
@@ -412,6 +427,7 @@ mod tests {
|
||||
}
|
||||
|
||||
let manifest = ManifestData {
|
||||
version: SNAPSHOT_VERSION,
|
||||
state_hashes: state_hashes,
|
||||
block_hashes: block_hashes,
|
||||
state_root: b"notarealroot".sha3(),
|
||||
@@ -428,4 +444,4 @@ mod tests {
|
||||
reader.chunk(hash.clone()).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +56,7 @@ pub use self::traits::SnapshotService;
|
||||
pub use self::watcher::Watcher;
|
||||
pub use types::snapshot_manifest::ManifestData;
|
||||
pub use types::restoration_status::RestorationStatus;
|
||||
pub use types::basic_account::BasicAccount;
|
||||
|
||||
pub mod io;
|
||||
pub mod service;
|
||||
@@ -82,6 +83,9 @@ mod traits {
|
||||
// Try to have chunks be around 4MB (before compression)
|
||||
const PREFERRED_CHUNK_SIZE: usize = 4 * 1024 * 1024;
|
||||
|
||||
// Try to have chunks be around 4MB (before compression)
|
||||
const MAX_STORAGE_ENTRIES_PER_ACCOUNT_RECORD: usize = 80_000;
|
||||
|
||||
// How many blocks to include in a snapshot, starting from the head of the chain.
|
||||
const SNAPSHOT_BLOCKS: u64 = 30000;
|
||||
|
||||
@@ -147,6 +151,7 @@ pub fn take_snapshot<W: SnapshotWriter + Send>(
|
||||
info!("produced {} state chunks and {} block chunks.", state_hashes.len(), block_hashes.len());
|
||||
|
||||
let manifest_data = ManifestData {
|
||||
version: 2,
|
||||
state_hashes: state_hashes,
|
||||
block_hashes: block_hashes,
|
||||
state_root: *state_root,
|
||||
@@ -300,14 +305,14 @@ impl<'a> StateChunker<'a> {
|
||||
//
|
||||
// If the buffer is greater than the desired chunk size,
|
||||
// this will write out the data to disk.
|
||||
fn push(&mut self, account_hash: Bytes, data: Bytes) -> Result<(), Error> {
|
||||
fn push(&mut self, account_hash: Bytes, data: Bytes, force_chunk: bool) -> Result<(), Error> {
|
||||
let pair = {
|
||||
let mut stream = RlpStream::new_list(2);
|
||||
stream.append(&account_hash).append_raw(&data, 1);
|
||||
stream.out()
|
||||
};
|
||||
|
||||
if self.cur_size + pair.len() >= PREFERRED_CHUNK_SIZE {
|
||||
if force_chunk || self.cur_size + pair.len() >= PREFERRED_CHUNK_SIZE {
|
||||
self.write_chunk()?;
|
||||
}
|
||||
|
||||
@@ -372,8 +377,10 @@ pub fn chunk_state<'a>(db: &HashDB, root: &H256, writer: &Mutex<SnapshotWriter +
|
||||
|
||||
let account_db = AccountDB::from_hash(db, account_key_hash);
|
||||
|
||||
let fat_rlp = account::to_fat_rlp(&account, &account_db, &mut used_code)?;
|
||||
chunker.push(account_key, fat_rlp)?;
|
||||
let fat_rlps = account::to_fat_rlps(&account, &account_db, &mut used_code, MAX_STORAGE_ENTRIES_PER_ACCOUNT_RECORD)?;
|
||||
for (i, fat_rlp) in fat_rlps.into_iter().enumerate() {
|
||||
chunker.push(account_key.clone(), fat_rlp, i > 0)?;
|
||||
}
|
||||
}
|
||||
|
||||
if chunker.cur_size != 0 {
|
||||
@@ -390,6 +397,7 @@ pub struct StateRebuilder {
|
||||
known_code: HashMap<H256, H256>, // code hashes mapped to first account with this code.
|
||||
missing_code: HashMap<H256, Vec<H256>>, // maps code hashes to lists of accounts missing that code.
|
||||
bloom: Bloom,
|
||||
known_storage_roots: HashMap<H256, H256>, // maps account hashes to last known storage root. Only filled for last account per chunk.
|
||||
}
|
||||
|
||||
impl StateRebuilder {
|
||||
@@ -401,6 +409,7 @@ impl StateRebuilder {
|
||||
known_code: HashMap::new(),
|
||||
missing_code: HashMap::new(),
|
||||
bloom: StateDB::load_bloom(&*db),
|
||||
known_storage_roots: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -418,6 +427,7 @@ impl StateRebuilder {
|
||||
rlp,
|
||||
&mut pairs,
|
||||
&self.known_code,
|
||||
&mut self.known_storage_roots,
|
||||
flag
|
||||
)?;
|
||||
|
||||
@@ -464,14 +474,18 @@ impl StateRebuilder {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check for accounts missing code. Once all chunks have been fed, there should
|
||||
/// be none.
|
||||
pub fn check_missing(self) -> Result<(), Error> {
|
||||
/// Finalize the restoration. Check for accounts missing code and make a dummy
|
||||
/// journal entry.
|
||||
/// Once all chunks have been fed, there should be nothing missing.
|
||||
pub fn finalize(mut self, era: u64, id: H256) -> Result<(), ::error::Error> {
|
||||
let missing = self.missing_code.keys().cloned().collect::<Vec<_>>();
|
||||
match missing.is_empty() {
|
||||
true => Ok(()),
|
||||
false => Err(Error::MissingCode(missing)),
|
||||
}
|
||||
if !missing.is_empty() { return Err(Error::MissingCode(missing).into()) }
|
||||
|
||||
let mut batch = self.db.backing().transaction();
|
||||
self.db.journal_under(&mut batch, era, &id)?;
|
||||
self.db.backing().write_buffered(batch);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get the state root of the rebuilder.
|
||||
@@ -492,10 +506,11 @@ fn rebuild_accounts(
|
||||
account_fat_rlps: UntrustedRlp,
|
||||
out_chunk: &mut [(H256, Bytes)],
|
||||
known_code: &HashMap<H256, H256>,
|
||||
known_storage_roots: &mut HashMap<H256, H256>,
|
||||
abort_flag: &AtomicBool,
|
||||
) -> Result<RebuiltStatus, ::error::Error> {
|
||||
let mut status = RebuiltStatus::default();
|
||||
for (account_rlp, out) in account_fat_rlps.into_iter().zip(out_chunk) {
|
||||
for (account_rlp, out) in account_fat_rlps.into_iter().zip(out_chunk.iter_mut()) {
|
||||
if !abort_flag.load(Ordering::SeqCst) { return Err(Error::RestorationAborted.into()) }
|
||||
|
||||
let hash: H256 = account_rlp.val_at(0)?;
|
||||
@@ -506,7 +521,8 @@ fn rebuild_accounts(
|
||||
// fill out the storage trie and code while decoding.
|
||||
let (acc, maybe_code) = {
|
||||
let mut acct_db = AccountDBMut::from_hash(db, hash);
|
||||
account::from_fat_rlp(&mut acct_db, fat_rlp)?
|
||||
let storage_root = known_storage_roots.get(&hash).cloned().unwrap_or(H256::zero());
|
||||
account::from_fat_rlp(&mut acct_db, fat_rlp, storage_root)?
|
||||
};
|
||||
|
||||
let code_hash = acc.code_hash.clone();
|
||||
@@ -538,6 +554,12 @@ fn rebuild_accounts(
|
||||
|
||||
*out = (hash, thin_rlp);
|
||||
}
|
||||
if let Some(&(ref hash, ref rlp)) = out_chunk.iter().last() {
|
||||
known_storage_roots.insert(*hash, ::rlp::decode::<BasicAccount>(rlp).storage_root);
|
||||
}
|
||||
if let Some(&(ref hash, ref rlp)) = out_chunk.iter().next() {
|
||||
known_storage_roots.insert(*hash, ::rlp::decode::<BasicAccount>(rlp).storage_root);
|
||||
}
|
||||
Ok(status)
|
||||
}
|
||||
|
||||
|
||||
@@ -166,7 +166,7 @@ impl Restoration {
|
||||
}
|
||||
|
||||
// check for missing code.
|
||||
self.state.check_missing()?;
|
||||
self.state.finalize(self.manifest.block_number, self.manifest.block_hash)?;
|
||||
|
||||
// connect out-of-order chunks and verify chain integrity.
|
||||
self.blocks.finalize(self.canonical_hashes)?;
|
||||
@@ -656,6 +656,7 @@ mod tests {
|
||||
assert_eq!(service.status(), RestorationStatus::Inactive);
|
||||
|
||||
let manifest = ManifestData {
|
||||
version: 2,
|
||||
state_hashes: vec![],
|
||||
block_hashes: vec![],
|
||||
state_root: Default::default(),
|
||||
|
||||
@@ -63,6 +63,7 @@ fn chunk_and_restore(amount: u64) {
|
||||
let writer = Mutex::new(PackedWriter::new(&snapshot_path).unwrap());
|
||||
let block_hashes = chunk_blocks(&bc, best_hash, &writer, &Progress::default()).unwrap();
|
||||
let manifest = ::snapshot::ManifestData {
|
||||
version: 2,
|
||||
state_hashes: Vec::new(),
|
||||
block_hashes: block_hashes,
|
||||
state_root: ::util::sha3::SHA3_NULL_RLP,
|
||||
@@ -125,6 +126,7 @@ fn checks_flag() {
|
||||
let chain = BlockChain::new(Default::default(), &genesis, db.clone());
|
||||
|
||||
let manifest = ::snapshot::ManifestData {
|
||||
version: 2,
|
||||
state_hashes: Vec::new(),
|
||||
block_hashes: Vec::new(),
|
||||
state_root: ::util::sha3::SHA3_NULL_RLP,
|
||||
|
||||
@@ -27,6 +27,7 @@ use super::ManifestData;
|
||||
#[test]
|
||||
fn manifest_rlp() {
|
||||
let manifest = ManifestData {
|
||||
version: 2,
|
||||
block_hashes: Vec::new(),
|
||||
state_hashes: Vec::new(),
|
||||
block_number: 1234567,
|
||||
@@ -35,4 +36,4 @@ fn manifest_rlp() {
|
||||
};
|
||||
let raw = manifest.clone().into_rlp();
|
||||
assert_eq!(ManifestData::from_rlp(&raw).unwrap(), manifest);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,6 +122,7 @@ fn guards_delete_folders() {
|
||||
path.push("restoration");
|
||||
|
||||
let manifest = ManifestData {
|
||||
version: 2,
|
||||
state_hashes: vec![],
|
||||
block_hashes: vec![],
|
||||
block_number: 0,
|
||||
|
||||
@@ -58,10 +58,11 @@ fn snap_and_restore() {
|
||||
let state_hashes = chunk_state(&old_db, &state_root, &writer, &Progress::default()).unwrap();
|
||||
|
||||
writer.into_inner().finish(::snapshot::ManifestData {
|
||||
version: 2,
|
||||
state_hashes: state_hashes,
|
||||
block_hashes: Vec::new(),
|
||||
state_root: state_root,
|
||||
block_number: 0,
|
||||
block_number: 1000,
|
||||
block_hash: H256::default(),
|
||||
}).unwrap();
|
||||
|
||||
@@ -69,7 +70,7 @@ fn snap_and_restore() {
|
||||
db_path.push("db");
|
||||
let db = {
|
||||
let new_db = Arc::new(Database::open(&db_cfg, &db_path.to_string_lossy()).unwrap());
|
||||
let mut rebuilder = StateRebuilder::new(new_db.clone(), Algorithm::Archive);
|
||||
let mut rebuilder = StateRebuilder::new(new_db.clone(), Algorithm::OverlayRecent);
|
||||
let reader = PackedReader::new(&snap_file).unwrap().unwrap();
|
||||
|
||||
let flag = AtomicBool::new(true);
|
||||
@@ -82,12 +83,13 @@ fn snap_and_restore() {
|
||||
}
|
||||
|
||||
assert_eq!(rebuilder.state_root(), state_root);
|
||||
rebuilder.check_missing().unwrap();
|
||||
rebuilder.finalize(1000, H256::default()).unwrap();
|
||||
|
||||
new_db
|
||||
};
|
||||
|
||||
let new_db = journaldb::new(db, Algorithm::Archive, ::db::COL_STATE);
|
||||
let new_db = journaldb::new(db, Algorithm::OverlayRecent, ::db::COL_STATE);
|
||||
assert_eq!(new_db.earliest_era(), Some(1000));
|
||||
|
||||
compare_dbs(&old_db, new_db.as_hashdb());
|
||||
}
|
||||
@@ -120,10 +122,10 @@ fn get_code_from_prev_chunk() {
|
||||
let mut db = MemoryDB::new();
|
||||
AccountDBMut::from_hash(&mut db, hash).insert(&code[..]);
|
||||
|
||||
let fat_rlp = account::to_fat_rlp(&acc, &AccountDB::from_hash(&db, hash), &mut used_code).unwrap();
|
||||
let fat_rlp = account::to_fat_rlps(&acc, &AccountDB::from_hash(&db, hash), &mut used_code, usize::max_value()).unwrap();
|
||||
|
||||
let mut stream = RlpStream::new_list(1);
|
||||
stream.begin_list(2).append(&hash).append_raw(&fat_rlp, 1);
|
||||
stream.begin_list(2).append(&hash).append_raw(&fat_rlp[0], 1);
|
||||
stream.out()
|
||||
};
|
||||
|
||||
@@ -134,13 +136,18 @@ fn get_code_from_prev_chunk() {
|
||||
let db_cfg = DatabaseConfig::with_columns(::db::NUM_COLUMNS);
|
||||
let new_db = Arc::new(Database::open(&db_cfg, &db_path.to_string_lossy()).unwrap());
|
||||
|
||||
let mut rebuilder = StateRebuilder::new(new_db, Algorithm::Archive);
|
||||
let flag = AtomicBool::new(true);
|
||||
{
|
||||
let mut rebuilder = StateRebuilder::new(new_db.clone(), Algorithm::OverlayRecent);
|
||||
let flag = AtomicBool::new(true);
|
||||
|
||||
rebuilder.feed(&chunk1, &flag).unwrap();
|
||||
rebuilder.feed(&chunk2, &flag).unwrap();
|
||||
rebuilder.feed(&chunk1, &flag).unwrap();
|
||||
rebuilder.feed(&chunk2, &flag).unwrap();
|
||||
|
||||
rebuilder.check_missing().unwrap();
|
||||
rebuilder.finalize(1000, H256::random()).unwrap();
|
||||
}
|
||||
|
||||
let state_db = journaldb::new(new_db, Algorithm::OverlayRecent, ::db::COL_STATE);
|
||||
assert_eq!(state_db.earliest_era(), Some(1000));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -164,6 +171,7 @@ fn checks_flag() {
|
||||
let state_hashes = chunk_state(&old_db, &state_root, &writer, &Progress::default()).unwrap();
|
||||
|
||||
writer.into_inner().finish(::snapshot::ManifestData {
|
||||
version: 2,
|
||||
state_hashes: state_hashes,
|
||||
block_hashes: Vec::new(),
|
||||
state_root: state_root,
|
||||
@@ -175,7 +183,7 @@ fn checks_flag() {
|
||||
db_path.push("db");
|
||||
{
|
||||
let new_db = Arc::new(Database::open(&db_cfg, &db_path.to_string_lossy()).unwrap());
|
||||
let mut rebuilder = StateRebuilder::new(new_db.clone(), Algorithm::Archive);
|
||||
let mut rebuilder = StateRebuilder::new(new_db.clone(), Algorithm::OverlayRecent);
|
||||
let reader = PackedReader::new(&snap_file).unwrap().unwrap();
|
||||
|
||||
let flag = AtomicBool::new(false);
|
||||
|
||||
@@ -55,6 +55,8 @@ pub struct CommonParams {
|
||||
pub fork_block: Option<(BlockNumber, H256)>,
|
||||
/// Number of first block where EIP-98 rules begin.
|
||||
pub eip98_transition: BlockNumber,
|
||||
/// Validate block receipts root.
|
||||
pub validate_receipts: bool,
|
||||
}
|
||||
|
||||
impl From<ethjson::spec::Params> for CommonParams {
|
||||
@@ -68,6 +70,7 @@ impl From<ethjson::spec::Params> for CommonParams {
|
||||
min_gas_limit: p.min_gas_limit.into(),
|
||||
fork_block: if let (Some(n), Some(h)) = (p.fork_block, p.fork_hash) { Some((n.into(), h.into())) } else { None },
|
||||
eip98_transition: p.eip98_transition.map_or(0, Into::into),
|
||||
validate_receipts: p.validate_receipts.unwrap_or(true),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -357,6 +360,10 @@ impl Spec {
|
||||
/// Account is marked with `reportBenign` it can be checked as disliked with "0xd8f2e0bf".
|
||||
/// Validator can be removed with `reportMalicious`.
|
||||
pub fn new_validator_contract() -> Self { load_bundled!("validator_contract") }
|
||||
|
||||
/// Create a new Spec with BasicAuthority which uses multiple validator sets changing with height.
|
||||
/// Account with secrets "0".sha3() is the validator for block 1 and with "1".sha3() onwards.
|
||||
pub fn new_validator_multi() -> Self { load_bundled!("validator_multi") }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -24,6 +24,8 @@ use util::Bytes;
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "ipc", binary)]
|
||||
pub struct ManifestData {
|
||||
/// Snapshot format version.
|
||||
pub version: u64,
|
||||
/// List of state chunk hashes.
|
||||
pub state_hashes: Vec<H256>,
|
||||
/// List of block chunk hashes.
|
||||
@@ -39,7 +41,8 @@ pub struct ManifestData {
|
||||
impl ManifestData {
|
||||
/// Encode the manifest data to rlp.
|
||||
pub fn into_rlp(self) -> Bytes {
|
||||
let mut stream = RlpStream::new_list(5);
|
||||
let mut stream = RlpStream::new_list(6);
|
||||
stream.append(&self.version);
|
||||
stream.append(&self.state_hashes);
|
||||
stream.append(&self.block_hashes);
|
||||
stream.append(&self.state_root);
|
||||
@@ -52,14 +55,20 @@ impl ManifestData {
|
||||
/// Try to restore manifest data from raw bytes, interpreted as RLP.
|
||||
pub fn from_rlp(raw: &[u8]) -> Result<Self, DecoderError> {
|
||||
let decoder = UntrustedRlp::new(raw);
|
||||
let (start, version) = if decoder.item_count() == 5 {
|
||||
(0, 1)
|
||||
} else {
|
||||
(1, decoder.val_at(0)?)
|
||||
};
|
||||
|
||||
let state_hashes: Vec<H256> = decoder.val_at(0)?;
|
||||
let block_hashes: Vec<H256> = decoder.val_at(1)?;
|
||||
let state_root: H256 = decoder.val_at(2)?;
|
||||
let block_number: u64 = decoder.val_at(3)?;
|
||||
let block_hash: H256 = decoder.val_at(4)?;
|
||||
let state_hashes: Vec<H256> = decoder.val_at(start + 0)?;
|
||||
let block_hashes: Vec<H256> = decoder.val_at(start + 1)?;
|
||||
let state_root: H256 = decoder.val_at(start + 2)?;
|
||||
let block_number: u64 = decoder.val_at(start + 3)?;
|
||||
let block_hash: H256 = decoder.val_at(start + 4)?;
|
||||
|
||||
Ok(ManifestData {
|
||||
version: version,
|
||||
state_hashes: state_hashes,
|
||||
block_hashes: block_hashes,
|
||||
state_root: state_root,
|
||||
|
||||
@@ -31,7 +31,7 @@ impl Verifier for CanonVerifier {
|
||||
verification::verify_block_family(header, bytes, engine, bc)
|
||||
}
|
||||
|
||||
fn verify_block_final(&self, expected: &Header, got: &Header) -> Result<(), Error> {
|
||||
verification::verify_block_final(expected, got)
|
||||
fn verify_block_final(&self, expected: &Header, got: &Header, receipts: bool) -> Result<(), Error> {
|
||||
verification::verify_block_final(expected, got, receipts)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ impl Verifier for NoopVerifier {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn verify_block_final(&self, _expected: &Header, _got: &Header) -> Result<(), Error> {
|
||||
fn verify_block_final(&self, _expected: &Header, _got: &Header, _receipts: bool) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,7 +178,7 @@ pub fn verify_block_family(header: &Header, bytes: &[u8], engine: &Engine, bc: &
|
||||
}
|
||||
|
||||
/// Phase 4 verification. Check block information against transaction enactment results,
|
||||
pub fn verify_block_final(expected: &Header, got: &Header) -> Result<(), Error> {
|
||||
pub fn verify_block_final(expected: &Header, got: &Header, check_receipts: bool) -> Result<(), Error> {
|
||||
if expected.gas_used() != got.gas_used() {
|
||||
return Err(From::from(BlockError::InvalidGasUsed(Mismatch { expected: expected.gas_used().clone(), found: got.gas_used().clone() })))
|
||||
}
|
||||
@@ -188,7 +188,7 @@ pub fn verify_block_final(expected: &Header, got: &Header) -> Result<(), Error>
|
||||
if expected.state_root() != got.state_root() {
|
||||
return Err(From::from(BlockError::InvalidStateRoot(Mismatch { expected: expected.state_root().clone(), found: got.state_root().clone() })))
|
||||
}
|
||||
if expected.receipts_root() != got.receipts_root() {
|
||||
if check_receipts && expected.receipts_root() != got.receipts_root() {
|
||||
return Err(From::from(BlockError::InvalidReceiptsRoot(Mismatch { expected: expected.receipts_root().clone(), found: got.receipts_root().clone() })))
|
||||
}
|
||||
Ok(())
|
||||
|
||||
@@ -26,5 +26,5 @@ pub trait Verifier: Send + Sync {
|
||||
/// Verify a block relative to its parent and uncles.
|
||||
fn verify_block_family(&self, header: &Header, bytes: &[u8], engine: &Engine, bc: &BlockProvider) -> Result<(), Error>;
|
||||
/// Do a final verification check for an enacted header vs its expected counterpart.
|
||||
fn verify_block_final(&self, expected: &Header, got: &Header) -> Result<(), Error>;
|
||||
fn verify_block_final(&self, expected: &Header, got: &Header, receipts: bool) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
@@ -5,11 +5,11 @@ version = "0.1.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
|
||||
[lib]
|
||||
name = "evm"
|
||||
name = "evmbin"
|
||||
path = "./src/main.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "evm"
|
||||
name = "parity-evm"
|
||||
path = "./src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -271,7 +271,7 @@ impl Manager {
|
||||
chunk_size += size;
|
||||
}
|
||||
trace!("writing {:?}", &hid_chunk[..]);
|
||||
let n = handle.write(&hid_chunk[0..chunk_size])?;
|
||||
let n = handle.write(&hid_chunk[..])?;
|
||||
if n < chunk_size {
|
||||
return Err(Error::Protocol("Write data size mismatch"));
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ build = "build.rs"
|
||||
|
||||
[dependencies]
|
||||
ethcore-ipc = { path = "../rpc" }
|
||||
nanomsg = { git = "https://github.com/ethcore/nanomsg.rs.git" }
|
||||
nanomsg = { git = "https://github.com/ethcore/nanomsg.rs.git", branch = "parity-1.7" }
|
||||
ethcore-ipc-nano = { path = "../nano" }
|
||||
semver = "0.5"
|
||||
log = "0.3"
|
||||
|
||||
@@ -8,6 +8,6 @@ license = "GPL-3.0"
|
||||
|
||||
[dependencies]
|
||||
ethcore-ipc = { path = "../rpc" }
|
||||
nanomsg = { git = "https://github.com/ethcore/nanomsg.rs.git" }
|
||||
nanomsg = { git = "https://github.com/ethcore/nanomsg.rs.git", branch = "parity-1.7" }
|
||||
log = "0.3"
|
||||
lazy_static = "0.2"
|
||||
|
||||
@@ -8,6 +8,6 @@ license = "GPL-3.0"
|
||||
|
||||
[dependencies]
|
||||
ethcore-devtools = { path = "../../devtools" }
|
||||
nanomsg = { git = "https://github.com/ethcore/nanomsg.rs.git" }
|
||||
nanomsg = { git = "https://github.com/ethcore/nanomsg.rs.git", branch = "parity-1.7" }
|
||||
ethcore-util = { path = "../../util" }
|
||||
semver = "0.5"
|
||||
|
||||
@@ -11,7 +11,7 @@ path = "run.rs"
|
||||
ethcore-ipc = { path = "../rpc" }
|
||||
ethcore-devtools = { path = "../../devtools" }
|
||||
semver = "0.5"
|
||||
nanomsg = { git = "https://github.com/ethcore/nanomsg.rs.git" }
|
||||
nanomsg = { git = "https://github.com/ethcore/nanomsg.rs.git", branch = "parity-1.7" }
|
||||
ethcore-ipc-nano = { path = "../nano" }
|
||||
ethcore-util = { path = "../../util" }
|
||||
log = "0.3"
|
||||
|
||||
@@ -107,34 +107,26 @@ export default class Contract {
|
||||
});
|
||||
}
|
||||
|
||||
deploy (options, values, statecb) {
|
||||
const setState = (state) => {
|
||||
if (!statecb) {
|
||||
return;
|
||||
}
|
||||
|
||||
return statecb(null, state);
|
||||
};
|
||||
|
||||
setState({ state: 'estimateGas' });
|
||||
deploy (options, values, statecb = () => {}) {
|
||||
statecb(null, { state: 'estimateGas' });
|
||||
|
||||
return this
|
||||
.deployEstimateGas(options, values)
|
||||
.then(([gasEst, gas]) => {
|
||||
options.gas = gas.toFixed(0);
|
||||
|
||||
setState({ state: 'postTransaction', gas });
|
||||
statecb(null, { state: 'postTransaction', gas });
|
||||
|
||||
const _options = this._encodeOptions(this.constructors[0], options, values);
|
||||
const encodedOptions = this._encodeOptions(this.constructors[0], options, values);
|
||||
|
||||
return this._api.parity
|
||||
.postTransaction(_options)
|
||||
.postTransaction(encodedOptions)
|
||||
.then((requestId) => {
|
||||
setState({ state: 'checkRequest', requestId });
|
||||
statecb(null, { state: 'checkRequest', requestId });
|
||||
return this._pollCheckRequest(requestId);
|
||||
})
|
||||
.then((txhash) => {
|
||||
setState({ state: 'getTransactionReceipt', txhash });
|
||||
statecb(null, { state: 'getTransactionReceipt', txhash });
|
||||
return this._pollTransactionReceipt(txhash, gas);
|
||||
})
|
||||
.then((receipt) => {
|
||||
@@ -142,23 +134,23 @@ export default class Contract {
|
||||
throw new Error(`Contract not deployed, gasUsed == ${gas.toFixed(0)}`);
|
||||
}
|
||||
|
||||
setState({ state: 'hasReceipt', receipt });
|
||||
statecb(null, { state: 'hasReceipt', receipt });
|
||||
this._receipt = receipt;
|
||||
this._address = receipt.contractAddress;
|
||||
return this._address;
|
||||
});
|
||||
})
|
||||
.then((address) => {
|
||||
setState({ state: 'getCode' });
|
||||
return this._api.eth.getCode(this._address);
|
||||
})
|
||||
.then((code) => {
|
||||
if (code === '0x') {
|
||||
throw new Error('Contract not deployed, getCode returned 0x');
|
||||
}
|
||||
})
|
||||
.then((address) => {
|
||||
statecb(null, { state: 'getCode' });
|
||||
return this._api.eth.getCode(this._address);
|
||||
})
|
||||
.then((code) => {
|
||||
if (code === '0x') {
|
||||
throw new Error('Contract not deployed, getCode returned 0x');
|
||||
}
|
||||
|
||||
setState({ state: 'completed' });
|
||||
return this._address;
|
||||
statecb(null, { state: 'completed' });
|
||||
return this._address;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -143,8 +143,15 @@ export function inOptions (options) {
|
||||
if (options) {
|
||||
Object.keys(options).forEach((key) => {
|
||||
switch (key) {
|
||||
case 'from':
|
||||
case 'to':
|
||||
// Don't encode the `to` option if it's empty
|
||||
// (eg. contract deployments)
|
||||
if (options[key]) {
|
||||
options[key] = inAddress(options[key]);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'from':
|
||||
options[key] = inAddress(options[key]);
|
||||
break;
|
||||
|
||||
|
||||
@@ -208,6 +208,13 @@ describe('api/format/input', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('does not encode an empty `to` value', () => {
|
||||
const options = { to: '' };
|
||||
const formatted = inOptions(options);
|
||||
|
||||
expect(formatted.to).to.equal('');
|
||||
});
|
||||
|
||||
['gas', 'gasPrice', 'value', 'minBlock', 'nonce'].forEach((input) => {
|
||||
it(`formats ${input} number as hexnumber`, () => {
|
||||
const block = {};
|
||||
|
||||
466
js/src/contracts/abi/old-wallet.json
Normal file
466
js/src/contracts/abi/old-wallet.json
Normal file
@@ -0,0 +1,466 @@
|
||||
[
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_owner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "removeOwner",
|
||||
"outputs": [],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_addr",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "isOwner",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "m_numOwners",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "m_lastDay",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [],
|
||||
"name": "resetSpentToday",
|
||||
"outputs": [],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "m_spentToday",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_owner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "addOwner",
|
||||
"outputs": [],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "m_required",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_h",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"name": "confirm",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_newLimit",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "setDailyLimit",
|
||||
"outputs": [],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_value",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "_data",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"name": "execute",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "_r",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_operation",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"name": "revoke",
|
||||
"outputs": [],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_newRequired",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "changeRequirement",
|
||||
"outputs": [],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_operation",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"name": "_owner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "hasConfirmed",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "ownerIndex",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "getOwner",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_to",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "kill",
|
||||
"outputs": [],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_to",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "changeOwner",
|
||||
"outputs": [],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "m_dailyLimit",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_owners",
|
||||
"type": "address[]"
|
||||
},
|
||||
{
|
||||
"name": "_required",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "_daylimit",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"type": "constructor"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "operation",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"name": "Confirmation",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "operation",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"name": "Revoke",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "oldOwner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "newOwner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "OwnerChanged",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "newOwner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "OwnerAdded",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "oldOwner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "OwnerRemoved",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "newRequirement",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "RequirementChanged",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "_from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Deposit",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "value",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "data",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"name": "SingleTransact",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "operation",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "value",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "data",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"name": "MultiTransact",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "operation",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "initiator",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "value",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "data",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"name": "ConfirmationNeeded",
|
||||
"type": "event"
|
||||
}
|
||||
]
|
||||
@@ -1 +1,476 @@
|
||||
[{"constant":false,"inputs":[{"name":"_owner","type":"address"}],"name":"removeOwner","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_addr","type":"address"}],"name":"isOwner","outputs":[{"name":"","type":"bool"}],"type":"function"},{"constant":true,"inputs":[],"name":"m_numOwners","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":true,"inputs":[],"name":"m_lastDay","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[],"name":"resetSpentToday","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"m_spentToday","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[{"name":"_owner","type":"address"}],"name":"addOwner","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"m_required","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"constant":false,"inputs":[{"name":"_h","type":"bytes32"}],"name":"confirm","outputs":[{"name":"","type":"bool"}],"type":"function"},{"constant":false,"inputs":[{"name":"_newLimit","type":"uint256"}],"name":"setDailyLimit","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"},{"name":"_data","type":"bytes"}],"name":"execute","outputs":[{"name":"_r","type":"bytes32"}],"type":"function"},{"constant":false,"inputs":[{"name":"_operation","type":"bytes32"}],"name":"revoke","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_newRequired","type":"uint256"}],"name":"changeRequirement","outputs":[],"type":"function"},{"constant":true,"inputs":[{"name":"_operation","type":"bytes32"},{"name":"_owner","type":"address"}],"name":"hasConfirmed","outputs":[{"name":"","type":"bool"}],"type":"function"},{"constant":true,"inputs":[{"name":"ownerIndex","type":"uint256"}],"name":"getOwner","outputs":[{"name":"","type":"address"}],"type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"}],"name":"kill","outputs":[],"type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"}],"name":"changeOwner","outputs":[],"type":"function"},{"constant":true,"inputs":[],"name":"m_dailyLimit","outputs":[{"name":"","type":"uint256"}],"type":"function"},{"inputs":[{"name":"_owners","type":"address[]"},{"name":"_required","type":"uint256"},{"name":"_daylimit","type":"uint256"}],"type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"operation","type":"bytes32"}],"name":"Confirmation","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"operation","type":"bytes32"}],"name":"Revoke","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"oldOwner","type":"address"},{"indexed":false,"name":"newOwner","type":"address"}],"name":"OwnerChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"newOwner","type":"address"}],"name":"OwnerAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"oldOwner","type":"address"}],"name":"OwnerRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"newRequirement","type":"uint256"}],"name":"RequirementChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_from","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"to","type":"address"},{"indexed":false,"name":"data","type":"bytes"}],"name":"SingleTransact","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"operation","type":"bytes32"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"to","type":"address"},{"indexed":false,"name":"data","type":"bytes"}],"name":"MultiTransact","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"operation","type":"bytes32"},{"indexed":false,"name":"initiator","type":"address"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":false,"name":"to","type":"address"},{"indexed":false,"name":"data","type":"bytes"}],"name":"ConfirmationNeeded","type":"event"}]
|
||||
[
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_owner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "removeOwner",
|
||||
"outputs": [],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_addr",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "isOwner",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "m_numOwners",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "m_lastDay",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [],
|
||||
"name": "resetSpentToday",
|
||||
"outputs": [],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "m_spentToday",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_owner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "addOwner",
|
||||
"outputs": [],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "m_required",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_h",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"name": "confirm",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_newLimit",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "setDailyLimit",
|
||||
"outputs": [],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_value",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "_data",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"name": "execute",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "_r",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_operation",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"name": "revoke",
|
||||
"outputs": [],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_newRequired",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "changeRequirement",
|
||||
"outputs": [],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_operation",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"name": "_owner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "hasConfirmed",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "ownerIndex",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "getOwner",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_to",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "kill",
|
||||
"outputs": [],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_to",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "changeOwner",
|
||||
"outputs": [],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "m_dailyLimit",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_owners",
|
||||
"type": "address[]"
|
||||
},
|
||||
{
|
||||
"name": "_required",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"name": "_daylimit",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"type": "constructor"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "operation",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"name": "Confirmation",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "operation",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"name": "Revoke",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "oldOwner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "newOwner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "OwnerChanged",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "newOwner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "OwnerAdded",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "oldOwner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "OwnerRemoved",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "newRequirement",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "RequirementChanged",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "_from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Deposit",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "value",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "data",
|
||||
"type": "bytes"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "created",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "SingleTransact",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "operation",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "value",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "data",
|
||||
"type": "bytes"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "created",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "MultiTransact",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "operation",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "initiator",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "value",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "data",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"name": "ConfirmationNeeded",
|
||||
"type": "event"
|
||||
}
|
||||
]
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -16,7 +16,14 @@
|
||||
|
||||
import * as abis from './abi';
|
||||
|
||||
const REGISTRY_V1_HASHES = [
|
||||
'0x34f7c51bbb1b1902fbdabfdf04811100f5c9f998f26dd535d2f6f977492c748e', // ropsten
|
||||
'0x64c3ee34851517a9faecd995c102b339f03e564ad6772dc43a26f993238b20ec' // homestead
|
||||
];
|
||||
|
||||
export default class Registry {
|
||||
_registryContract = null;
|
||||
|
||||
constructor (api) {
|
||||
this._api = api;
|
||||
|
||||
@@ -43,11 +50,10 @@ export default class Registry {
|
||||
|
||||
this._fetching = true;
|
||||
|
||||
return this._api.parity
|
||||
.registryAddress()
|
||||
.then((address) => {
|
||||
return this.fetchContract()
|
||||
.then((contract) => {
|
||||
this._fetching = false;
|
||||
this._instance = this._api.newContract(abis.registry, address).instance;
|
||||
this._instance = contract.instance;
|
||||
|
||||
this._queue.forEach((queued) => {
|
||||
queued.resolve(this._instance);
|
||||
@@ -89,6 +95,47 @@ export default class Registry {
|
||||
.then((contract) => contract.instance);
|
||||
}
|
||||
|
||||
fetchContract () {
|
||||
if (this._registryContract) {
|
||||
return Promise.resolve(this._registryContract);
|
||||
}
|
||||
|
||||
return this._api.parity
|
||||
.registryAddress()
|
||||
.then((address) => Promise.all([ address, this._api.eth.getCode(address) ]))
|
||||
.then(([ address, code ]) => {
|
||||
const codeHash = this._api.util.sha3(code);
|
||||
const version = REGISTRY_V1_HASHES.includes(codeHash)
|
||||
? 1
|
||||
: 2;
|
||||
const abi = version === 1
|
||||
? abis.registry
|
||||
: abis.registry2;
|
||||
const contract = this._api.newContract(abi, address);
|
||||
|
||||
// Add support for previous `set` and `get` methods
|
||||
if (!contract.instance.get && contract.instance.getData) {
|
||||
contract.instance.get = contract.instance.getData;
|
||||
}
|
||||
|
||||
if (contract.instance.get && !contract.instance.getData) {
|
||||
contract.instance.getData = contract.instance.get;
|
||||
}
|
||||
|
||||
if (!contract.instance.set && contract.instance.setData) {
|
||||
contract.instance.set = contract.instance.setData;
|
||||
}
|
||||
|
||||
if (contract.instance.set && !contract.instance.setData) {
|
||||
contract.instance.setData = contract.instance.set;
|
||||
}
|
||||
|
||||
console.log(`registry at ${address}, code ${codeHash}, version ${version}`);
|
||||
this._registryContract = contract;
|
||||
return this._registryContract;
|
||||
});
|
||||
}
|
||||
|
||||
_createGetParams (_name, key) {
|
||||
const name = _name.toLowerCase();
|
||||
const sha3 = this._api.util.sha3.text(name);
|
||||
|
||||
@@ -35,6 +35,9 @@ function create () {
|
||||
}
|
||||
};
|
||||
api = {
|
||||
eth: {
|
||||
getCode: sinon.stub().resolves(0)
|
||||
},
|
||||
parity: {
|
||||
registryAddress: sinon.stub().resolves('testRegistryAddress')
|
||||
},
|
||||
|
||||
@@ -8,453 +8,454 @@
|
||||
// use modifiers onlyowner (just own owned) or onlymanyowners(hash), whereby the same hash must be provided by
|
||||
// some number (specified in constructor) of the set of owners (specified in the constructor, modifiable) before the
|
||||
// interior is executed.
|
||||
pragma solidity ^0.4.6;
|
||||
|
||||
contract multisig {
|
||||
// EVENTS
|
||||
pragma solidity ^0.4.9;
|
||||
|
||||
// this contract can accept a confirmation, in which case
|
||||
// we record owner and operation (hash) alongside it.
|
||||
event Confirmation(address owner, bytes32 operation);
|
||||
event Revoke(address owner, bytes32 operation);
|
||||
contract WalletEvents {
|
||||
// EVENTS
|
||||
|
||||
// some others are in the case of an owner changing.
|
||||
event OwnerChanged(address oldOwner, address newOwner);
|
||||
event OwnerAdded(address newOwner);
|
||||
event OwnerRemoved(address oldOwner);
|
||||
// this contract only has six types of events: it can accept a confirmation, in which case
|
||||
// we record owner and operation (hash) alongside it.
|
||||
event Confirmation(address owner, bytes32 operation);
|
||||
event Revoke(address owner, bytes32 operation);
|
||||
|
||||
// the last one is emitted if the required signatures change
|
||||
event RequirementChanged(uint newRequirement);
|
||||
// some others are in the case of an owner changing.
|
||||
event OwnerChanged(address oldOwner, address newOwner);
|
||||
event OwnerAdded(address newOwner);
|
||||
event OwnerRemoved(address oldOwner);
|
||||
|
||||
// Funds has arrived into the wallet (record how much).
|
||||
event Deposit(address _from, uint value);
|
||||
// Single transaction going out of the wallet (record who signed for it, how much, and to whom it's going).
|
||||
event SingleTransact(address owner, uint value, address to, bytes data);
|
||||
// Multi-sig transaction going out of the wallet (record who signed for it last, the operation hash, how much, and to whom it's going).
|
||||
event MultiTransact(address owner, bytes32 operation, uint value, address to, bytes data);
|
||||
// Confirmation still needed for a transaction.
|
||||
event ConfirmationNeeded(bytes32 operation, address initiator, uint value, address to, bytes data);
|
||||
// the last one is emitted if the required signatures change
|
||||
event RequirementChanged(uint newRequirement);
|
||||
|
||||
// Funds has arrived into the wallet (record how much).
|
||||
event Deposit(address _from, uint value);
|
||||
// Single transaction going out of the wallet (record who signed for it, how much, and to whom it's going).
|
||||
event SingleTransact(address owner, uint value, address to, bytes data, address created);
|
||||
// Multi-sig transaction going out of the wallet (record who signed for it last, the operation hash, how much, and to whom it's going).
|
||||
event MultiTransact(address owner, bytes32 operation, uint value, address to, bytes data, address created);
|
||||
// Confirmation still needed for a transaction.
|
||||
event ConfirmationNeeded(bytes32 operation, address initiator, uint value, address to, bytes data);
|
||||
}
|
||||
|
||||
contract multisigAbi is multisig {
|
||||
function isOwner(address _addr) returns (bool);
|
||||
contract WalletAbi {
|
||||
// Revokes a prior confirmation of the given operation
|
||||
function revoke(bytes32 _operation) external;
|
||||
|
||||
function hasConfirmed(bytes32 _operation, address _owner) constant returns (bool);
|
||||
// Replaces an owner `_from` with another `_to`.
|
||||
function changeOwner(address _from, address _to) external;
|
||||
|
||||
function confirm(bytes32 _h) returns(bool);
|
||||
function addOwner(address _owner) external;
|
||||
|
||||
// (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today.
|
||||
function setDailyLimit(uint _newLimit);
|
||||
function removeOwner(address _owner) external;
|
||||
|
||||
function addOwner(address _owner);
|
||||
function changeRequirement(uint _newRequired) external;
|
||||
|
||||
function removeOwner(address _owner);
|
||||
function isOwner(address _addr) constant returns (bool);
|
||||
|
||||
function changeRequirement(uint _newRequired);
|
||||
function hasConfirmed(bytes32 _operation, address _owner) external constant returns (bool);
|
||||
|
||||
// Revokes a prior confirmation of the given operation
|
||||
function revoke(bytes32 _operation);
|
||||
// (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today.
|
||||
function setDailyLimit(uint _newLimit) external;
|
||||
|
||||
function changeOwner(address _from, address _to);
|
||||
|
||||
function execute(address _to, uint _value, bytes _data) returns(bool);
|
||||
function execute(address _to, uint _value, bytes _data) external returns (bytes32 o_hash);
|
||||
function confirm(bytes32 _h) returns (bool o_success);
|
||||
}
|
||||
|
||||
contract WalletLibrary is multisig {
|
||||
// TYPES
|
||||
contract WalletLibrary is WalletEvents {
|
||||
// TYPES
|
||||
|
||||
// struct for the status of a pending operation.
|
||||
struct PendingState {
|
||||
uint yetNeeded;
|
||||
uint ownersDone;
|
||||
uint index;
|
||||
// struct for the status of a pending operation.
|
||||
struct PendingState {
|
||||
uint yetNeeded;
|
||||
uint ownersDone;
|
||||
uint index;
|
||||
}
|
||||
|
||||
// Transaction structure to remember details of transaction lest it need be saved for a later call.
|
||||
struct Transaction {
|
||||
address to;
|
||||
uint value;
|
||||
bytes data;
|
||||
}
|
||||
|
||||
// MODIFIERS
|
||||
|
||||
// simple single-sig function modifier.
|
||||
modifier onlyowner {
|
||||
if (isOwner(msg.sender))
|
||||
_;
|
||||
}
|
||||
// multi-sig function modifier: the operation must have an intrinsic hash in order
|
||||
// that later attempts can be realised as the same underlying operation and
|
||||
// thus count as confirmations.
|
||||
modifier onlymanyowners(bytes32 _operation) {
|
||||
if (confirmAndCheck(_operation))
|
||||
_;
|
||||
}
|
||||
|
||||
// METHODS
|
||||
|
||||
// gets called when no other function matches
|
||||
function() payable {
|
||||
// just being sent some cash?
|
||||
if (msg.value > 0)
|
||||
Deposit(msg.sender, msg.value);
|
||||
}
|
||||
|
||||
// constructor is given number of sigs required to do protected "onlymanyowners" transactions
|
||||
// as well as the selection of addresses capable of confirming them.
|
||||
function initMultiowned(address[] _owners, uint _required) {
|
||||
m_numOwners = _owners.length + 1;
|
||||
m_owners[1] = uint(msg.sender);
|
||||
m_ownerIndex[uint(msg.sender)] = 1;
|
||||
for (uint i = 0; i < _owners.length; ++i)
|
||||
{
|
||||
m_owners[2 + i] = uint(_owners[i]);
|
||||
m_ownerIndex[uint(_owners[i])] = 2 + i;
|
||||
}
|
||||
m_required = _required;
|
||||
}
|
||||
|
||||
// Revokes a prior confirmation of the given operation
|
||||
function revoke(bytes32 _operation) external {
|
||||
uint ownerIndex = m_ownerIndex[uint(msg.sender)];
|
||||
// make sure they're an owner
|
||||
if (ownerIndex == 0) return;
|
||||
uint ownerIndexBit = 2**ownerIndex;
|
||||
var pending = m_pending[_operation];
|
||||
if (pending.ownersDone & ownerIndexBit > 0) {
|
||||
pending.yetNeeded++;
|
||||
pending.ownersDone -= ownerIndexBit;
|
||||
Revoke(msg.sender, _operation);
|
||||
}
|
||||
}
|
||||
|
||||
// Replaces an owner `_from` with another `_to`.
|
||||
function changeOwner(address _from, address _to) onlymanyowners(sha3(msg.data)) external {
|
||||
if (isOwner(_to)) return;
|
||||
uint ownerIndex = m_ownerIndex[uint(_from)];
|
||||
if (ownerIndex == 0) return;
|
||||
|
||||
clearPending();
|
||||
m_owners[ownerIndex] = uint(_to);
|
||||
m_ownerIndex[uint(_from)] = 0;
|
||||
m_ownerIndex[uint(_to)] = ownerIndex;
|
||||
OwnerChanged(_from, _to);
|
||||
}
|
||||
|
||||
function addOwner(address _owner) onlymanyowners(sha3(msg.data)) external {
|
||||
if (isOwner(_owner)) return;
|
||||
|
||||
clearPending();
|
||||
if (m_numOwners >= c_maxOwners)
|
||||
reorganizeOwners();
|
||||
if (m_numOwners >= c_maxOwners)
|
||||
return;
|
||||
m_numOwners++;
|
||||
m_owners[m_numOwners] = uint(_owner);
|
||||
m_ownerIndex[uint(_owner)] = m_numOwners;
|
||||
OwnerAdded(_owner);
|
||||
}
|
||||
|
||||
function removeOwner(address _owner) onlymanyowners(sha3(msg.data)) external {
|
||||
uint ownerIndex = m_ownerIndex[uint(_owner)];
|
||||
if (ownerIndex == 0) return;
|
||||
if (m_required > m_numOwners - 1) return;
|
||||
|
||||
m_owners[ownerIndex] = 0;
|
||||
m_ownerIndex[uint(_owner)] = 0;
|
||||
clearPending();
|
||||
reorganizeOwners(); //make sure m_numOwner is equal to the number of owners and always points to the optimal free slot
|
||||
OwnerRemoved(_owner);
|
||||
}
|
||||
|
||||
function changeRequirement(uint _newRequired) onlymanyowners(sha3(msg.data)) external {
|
||||
if (_newRequired > m_numOwners) return;
|
||||
m_required = _newRequired;
|
||||
clearPending();
|
||||
RequirementChanged(_newRequired);
|
||||
}
|
||||
|
||||
// Gets an owner by 0-indexed position (using numOwners as the count)
|
||||
function getOwner(uint ownerIndex) external constant returns (address) {
|
||||
return address(m_owners[ownerIndex + 1]);
|
||||
}
|
||||
|
||||
function isOwner(address _addr) constant returns (bool) {
|
||||
return m_ownerIndex[uint(_addr)] > 0;
|
||||
}
|
||||
|
||||
function hasConfirmed(bytes32 _operation, address _owner) external constant returns (bool) {
|
||||
var pending = m_pending[_operation];
|
||||
uint ownerIndex = m_ownerIndex[uint(_owner)];
|
||||
|
||||
// make sure they're an owner
|
||||
if (ownerIndex == 0) return false;
|
||||
|
||||
// determine the bit to set for this owner.
|
||||
uint ownerIndexBit = 2**ownerIndex;
|
||||
return !(pending.ownersDone & ownerIndexBit == 0);
|
||||
}
|
||||
|
||||
// constructor - stores initial daily limit and records the present day's index.
|
||||
function initDaylimit(uint _limit) {
|
||||
m_dailyLimit = _limit;
|
||||
m_lastDay = today();
|
||||
}
|
||||
// (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today.
|
||||
function setDailyLimit(uint _newLimit) onlymanyowners(sha3(msg.data)) external {
|
||||
m_dailyLimit = _newLimit;
|
||||
}
|
||||
// resets the amount already spent today. needs many of the owners to confirm.
|
||||
function resetSpentToday() onlymanyowners(sha3(msg.data)) external {
|
||||
m_spentToday = 0;
|
||||
}
|
||||
|
||||
// constructor - just pass on the owner array to the multiowned and
|
||||
// the limit to daylimit
|
||||
function initWallet(address[] _owners, uint _required, uint _daylimit) {
|
||||
initDaylimit(_daylimit);
|
||||
initMultiowned(_owners, _required);
|
||||
}
|
||||
|
||||
// kills the contract sending everything to `_to`.
|
||||
function kill(address _to) onlymanyowners(sha3(msg.data)) external {
|
||||
suicide(_to);
|
||||
}
|
||||
|
||||
// Outside-visible transact entry point. Executes transaction immediately if below daily spend limit.
|
||||
// If not, goes into multisig process. We provide a hash on return to allow the sender to provide
|
||||
// shortcuts for the other confirmations (allowing them to avoid replicating the _to, _value
|
||||
// and _data arguments). They still get the option of using them if they want, anyways.
|
||||
function execute(address _to, uint _value, bytes _data) external onlyowner returns (bytes32 o_hash) {
|
||||
// first, take the opportunity to check that we're under the daily limit.
|
||||
if ((_data.length == 0 && underLimit(_value)) || m_required == 1) {
|
||||
// yes - just execute the call.
|
||||
address created;
|
||||
if (_to == 0) {
|
||||
created = create(_value, _data);
|
||||
} else {
|
||||
if (!_to.call.value(_value)(_data))
|
||||
throw;
|
||||
}
|
||||
SingleTransact(msg.sender, _value, _to, _data, created);
|
||||
} else {
|
||||
// determine our operation hash.
|
||||
o_hash = sha3(msg.data, block.number);
|
||||
// store if it's new
|
||||
if (m_txs[o_hash].to == 0 && m_txs[o_hash].value == 0 && m_txs[o_hash].data.length == 0) {
|
||||
m_txs[o_hash].to = _to;
|
||||
m_txs[o_hash].value = _value;
|
||||
m_txs[o_hash].data = _data;
|
||||
}
|
||||
if (!confirm(o_hash)) {
|
||||
ConfirmationNeeded(o_hash, msg.sender, _value, _to, _data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function create(uint _value, bytes _code) internal returns (address o_addr) {
|
||||
assembly {
|
||||
o_addr := create(_value, add(_code, 0x20), mload(_code))
|
||||
jumpi(invalidJumpLabel, iszero(extcodesize(o_addr)))
|
||||
}
|
||||
}
|
||||
|
||||
// confirm a transaction through just the hash. we use the previous transactions map, m_txs, in order
|
||||
// to determine the body of the transaction from the hash provided.
|
||||
function confirm(bytes32 _h) onlymanyowners(_h) returns (bool o_success) {
|
||||
if (m_txs[_h].to != 0 || m_txs[_h].value != 0 || m_txs[_h].data.length != 0) {
|
||||
address created;
|
||||
if (m_txs[_h].to == 0) {
|
||||
created = create(m_txs[_h].value, m_txs[_h].data);
|
||||
} else {
|
||||
if (!m_txs[_h].to.call.value(m_txs[_h].value)(m_txs[_h].data))
|
||||
throw;
|
||||
}
|
||||
|
||||
MultiTransact(msg.sender, _h, m_txs[_h].value, m_txs[_h].to, m_txs[_h].data, created);
|
||||
delete m_txs[_h];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// INTERNAL METHODS
|
||||
|
||||
function confirmAndCheck(bytes32 _operation) internal returns (bool) {
|
||||
// determine what index the present sender is:
|
||||
uint ownerIndex = m_ownerIndex[uint(msg.sender)];
|
||||
// make sure they're an owner
|
||||
if (ownerIndex == 0) return;
|
||||
|
||||
var pending = m_pending[_operation];
|
||||
// if we're not yet working on this operation, switch over and reset the confirmation status.
|
||||
if (pending.yetNeeded == 0) {
|
||||
// reset count of confirmations needed.
|
||||
pending.yetNeeded = m_required;
|
||||
// reset which owners have confirmed (none) - set our bitmap to 0.
|
||||
pending.ownersDone = 0;
|
||||
pending.index = m_pendingIndex.length++;
|
||||
m_pendingIndex[pending.index] = _operation;
|
||||
}
|
||||
// determine the bit to set for this owner.
|
||||
uint ownerIndexBit = 2**ownerIndex;
|
||||
// make sure we (the message sender) haven't confirmed this operation previously.
|
||||
if (pending.ownersDone & ownerIndexBit == 0) {
|
||||
Confirmation(msg.sender, _operation);
|
||||
// ok - check if count is enough to go ahead.
|
||||
if (pending.yetNeeded <= 1) {
|
||||
// enough confirmations: reset and run interior.
|
||||
delete m_pendingIndex[m_pending[_operation].index];
|
||||
delete m_pending[_operation];
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// not enough: record that this owner in particular confirmed.
|
||||
pending.yetNeeded--;
|
||||
pending.ownersDone |= ownerIndexBit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function reorganizeOwners() private {
|
||||
uint free = 1;
|
||||
while (free < m_numOwners)
|
||||
{
|
||||
while (free < m_numOwners && m_owners[free] != 0) free++;
|
||||
while (m_numOwners > 1 && m_owners[m_numOwners] == 0) m_numOwners--;
|
||||
if (free < m_numOwners && m_owners[m_numOwners] != 0 && m_owners[free] == 0)
|
||||
{
|
||||
m_owners[free] = m_owners[m_numOwners];
|
||||
m_ownerIndex[m_owners[free]] = free;
|
||||
m_owners[m_numOwners] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// checks to see if there is at least `_value` left from the daily limit today. if there is, subtracts it and
|
||||
// returns true. otherwise just returns false.
|
||||
function underLimit(uint _value) internal onlyowner returns (bool) {
|
||||
// reset the spend limit if we're on a different day to last time.
|
||||
if (today() > m_lastDay) {
|
||||
m_spentToday = 0;
|
||||
m_lastDay = today();
|
||||
}
|
||||
// check to see if there's enough left - if so, subtract and return true.
|
||||
// overflow protection // dailyLimit check
|
||||
if (m_spentToday + _value >= m_spentToday && m_spentToday + _value <= m_dailyLimit) {
|
||||
m_spentToday += _value;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// determines today's index.
|
||||
function today() private constant returns (uint) { return now / 1 days; }
|
||||
|
||||
function clearPending() internal {
|
||||
uint length = m_pendingIndex.length;
|
||||
|
||||
for (uint i = 0; i < length; ++i) {
|
||||
delete m_txs[m_pendingIndex[i]];
|
||||
|
||||
if (m_pendingIndex[i] != 0)
|
||||
delete m_pending[m_pendingIndex[i]];
|
||||
}
|
||||
|
||||
// Transaction structure to remember details of transaction lest it need be saved for a later call.
|
||||
struct Transaction {
|
||||
address to;
|
||||
uint value;
|
||||
bytes data;
|
||||
}
|
||||
delete m_pendingIndex;
|
||||
}
|
||||
|
||||
/******************************
|
||||
***** MULTI OWNED SECTION ****
|
||||
******************************/
|
||||
// FIELDS
|
||||
address constant _walletLibrary = 0xcafecafecafecafecafecafecafecafecafecafe;
|
||||
|
||||
// MODIFIERS
|
||||
// the number of owners that must confirm the same operation before it is run.
|
||||
uint public m_required;
|
||||
// pointer used to find a free slot in m_owners
|
||||
uint public m_numOwners;
|
||||
|
||||
// simple single-sig function modifier.
|
||||
modifier onlyowner {
|
||||
if (isOwner(msg.sender))
|
||||
_;
|
||||
}
|
||||
// multi-sig function modifier: the operation must have an intrinsic hash in order
|
||||
// that later attempts can be realised as the same underlying operation and
|
||||
// thus count as confirmations.
|
||||
modifier onlymanyowners(bytes32 _operation) {
|
||||
if (confirmAndCheck(_operation))
|
||||
_;
|
||||
}
|
||||
uint public m_dailyLimit;
|
||||
uint public m_spentToday;
|
||||
uint public m_lastDay;
|
||||
|
||||
// METHODS
|
||||
// list of owners
|
||||
uint[256] m_owners;
|
||||
|
||||
// constructor is given number of sigs required to do protected "onlymanyowners" transactions
|
||||
// as well as the selection of addresses capable of confirming them.
|
||||
function initMultiowned(address[] _owners, uint _required) {
|
||||
m_numOwners = _owners.length + 1;
|
||||
m_owners[1] = uint(msg.sender);
|
||||
m_ownerIndex[uint(msg.sender)] = 1;
|
||||
m_required = _required;
|
||||
uint constant c_maxOwners = 250;
|
||||
// index on the list of owners to allow reverse lookup
|
||||
mapping(uint => uint) m_ownerIndex;
|
||||
// the ongoing operations.
|
||||
mapping(bytes32 => PendingState) m_pending;
|
||||
bytes32[] m_pendingIndex;
|
||||
|
||||
for (uint i = 0; i < _owners.length; ++i)
|
||||
{
|
||||
m_owners[2 + i] = uint(_owners[i]);
|
||||
m_ownerIndex[uint(_owners[i])] = 2 + i;
|
||||
}
|
||||
}
|
||||
|
||||
// Revokes a prior confirmation of the given operation
|
||||
function revoke(bytes32 _operation) {
|
||||
uint ownerIndex = m_ownerIndex[uint(msg.sender)];
|
||||
// make sure they're an owner
|
||||
if (ownerIndex == 0) return;
|
||||
uint ownerIndexBit = 2**ownerIndex;
|
||||
var pending = m_pending[_operation];
|
||||
if (pending.ownersDone & ownerIndexBit > 0) {
|
||||
pending.yetNeeded++;
|
||||
pending.ownersDone -= ownerIndexBit;
|
||||
Revoke(msg.sender, _operation);
|
||||
}
|
||||
}
|
||||
|
||||
// Replaces an owner `_from` with another `_to`.
|
||||
function changeOwner(address _from, address _to) onlymanyowners(sha3(msg.data)) {
|
||||
if (isOwner(_to)) return;
|
||||
uint ownerIndex = m_ownerIndex[uint(_from)];
|
||||
if (ownerIndex == 0) return;
|
||||
|
||||
clearPending();
|
||||
m_owners[ownerIndex] = uint(_to);
|
||||
m_ownerIndex[uint(_from)] = 0;
|
||||
m_ownerIndex[uint(_to)] = ownerIndex;
|
||||
OwnerChanged(_from, _to);
|
||||
}
|
||||
|
||||
function addOwner(address _owner) onlymanyowners(sha3(msg.data)) {
|
||||
if (isOwner(_owner)) return;
|
||||
|
||||
clearPending();
|
||||
if (m_numOwners >= c_maxOwners)
|
||||
reorganizeOwners();
|
||||
if (m_numOwners >= c_maxOwners)
|
||||
return;
|
||||
m_numOwners++;
|
||||
m_owners[m_numOwners] = uint(_owner);
|
||||
m_ownerIndex[uint(_owner)] = m_numOwners;
|
||||
OwnerAdded(_owner);
|
||||
}
|
||||
|
||||
function removeOwner(address _owner) onlymanyowners(sha3(msg.data)) {
|
||||
uint ownerIndex = m_ownerIndex[uint(_owner)];
|
||||
if (ownerIndex == 0) return;
|
||||
if (m_required > m_numOwners - 1) return;
|
||||
|
||||
m_owners[ownerIndex] = 0;
|
||||
m_ownerIndex[uint(_owner)] = 0;
|
||||
clearPending();
|
||||
reorganizeOwners(); //make sure m_numOwner is equal to the number of owners and always points to the optimal free slot
|
||||
OwnerRemoved(_owner);
|
||||
}
|
||||
|
||||
function changeRequirement(uint _newRequired) onlymanyowners(sha3(msg.data)) {
|
||||
if (_newRequired > m_numOwners) return;
|
||||
m_required = _newRequired;
|
||||
clearPending();
|
||||
RequirementChanged(_newRequired);
|
||||
}
|
||||
|
||||
function isOwner(address _addr) returns (bool) {
|
||||
return m_ownerIndex[uint(_addr)] > 0;
|
||||
}
|
||||
|
||||
|
||||
function hasConfirmed(bytes32 _operation, address _owner) constant returns (bool) {
|
||||
var pending = m_pending[_operation];
|
||||
uint ownerIndex = m_ownerIndex[uint(_owner)];
|
||||
|
||||
// make sure they're an owner
|
||||
if (ownerIndex == 0) return false;
|
||||
|
||||
// determine the bit to set for this owner.
|
||||
uint ownerIndexBit = 2**ownerIndex;
|
||||
return !(pending.ownersDone & ownerIndexBit == 0);
|
||||
}
|
||||
|
||||
// INTERNAL METHODS
|
||||
|
||||
function confirmAndCheck(bytes32 _operation) internal returns (bool) {
|
||||
// determine what index the present sender is:
|
||||
uint ownerIndex = m_ownerIndex[uint(msg.sender)];
|
||||
// make sure they're an owner
|
||||
if (ownerIndex == 0) return;
|
||||
|
||||
var pending = m_pending[_operation];
|
||||
// if we're not yet working on this operation, switch over and reset the confirmation status.
|
||||
if (pending.yetNeeded == 0) {
|
||||
// reset count of confirmations needed.
|
||||
pending.yetNeeded = m_required;
|
||||
// reset which owners have confirmed (none) - set our bitmap to 0.
|
||||
pending.ownersDone = 0;
|
||||
pending.index = m_pendingIndex.length++;
|
||||
m_pendingIndex[pending.index] = _operation;
|
||||
}
|
||||
// determine the bit to set for this owner.
|
||||
uint ownerIndexBit = 2**ownerIndex;
|
||||
// make sure we (the message sender) haven't confirmed this operation previously.
|
||||
if (pending.ownersDone & ownerIndexBit == 0) {
|
||||
Confirmation(msg.sender, _operation);
|
||||
// ok - check if count is enough to go ahead.
|
||||
if (pending.yetNeeded <= 1) {
|
||||
// enough confirmations: reset and run interior.
|
||||
delete m_pendingIndex[m_pending[_operation].index];
|
||||
delete m_pending[_operation];
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// not enough: record that this owner in particular confirmed.
|
||||
pending.yetNeeded--;
|
||||
pending.ownersDone |= ownerIndexBit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function reorganizeOwners() private {
|
||||
uint free = 1;
|
||||
while (free < m_numOwners)
|
||||
{
|
||||
while (free < m_numOwners && m_owners[free] != 0) free++;
|
||||
while (m_numOwners > 1 && m_owners[m_numOwners] == 0) m_numOwners--;
|
||||
if (free < m_numOwners && m_owners[m_numOwners] != 0 && m_owners[free] == 0)
|
||||
{
|
||||
m_owners[free] = m_owners[m_numOwners];
|
||||
m_ownerIndex[m_owners[free]] = free;
|
||||
m_owners[m_numOwners] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function clearPending() internal {
|
||||
uint length = m_pendingIndex.length;
|
||||
for (uint i = 0; i < length; ++i)
|
||||
if (m_pendingIndex[i] != 0)
|
||||
delete m_pending[m_pendingIndex[i]];
|
||||
delete m_pendingIndex;
|
||||
}
|
||||
|
||||
|
||||
/******************************
|
||||
****** DAY LIMIT SECTION *****
|
||||
******************************/
|
||||
|
||||
// MODIFIERS
|
||||
|
||||
// simple modifier for daily limit.
|
||||
modifier limitedDaily(uint _value) {
|
||||
if (underLimit(_value))
|
||||
_;
|
||||
}
|
||||
|
||||
// METHODS
|
||||
|
||||
// constructor - stores initial daily limit and records the present day's index.
|
||||
function initDaylimit(uint _limit) {
|
||||
m_dailyLimit = _limit;
|
||||
m_lastDay = today();
|
||||
}
|
||||
// (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today.
|
||||
function setDailyLimit(uint _newLimit) onlymanyowners(sha3(msg.data)) {
|
||||
m_dailyLimit = _newLimit;
|
||||
}
|
||||
// resets the amount already spent today. needs many of the owners to confirm.
|
||||
function resetSpentToday() onlymanyowners(sha3(msg.data)) {
|
||||
m_spentToday = 0;
|
||||
}
|
||||
|
||||
// INTERNAL METHODS
|
||||
|
||||
// checks to see if there is at least `_value` left from the daily limit today. if there is, subtracts it and
|
||||
// returns true. otherwise just returns false.
|
||||
function underLimit(uint _value) internal onlyowner returns (bool) {
|
||||
// reset the spend limit if we're on a different day to last time.
|
||||
if (today() > m_lastDay) {
|
||||
m_spentToday = 0;
|
||||
m_lastDay = today();
|
||||
}
|
||||
// check to see if there's enough left - if so, subtract and return true.
|
||||
// overflow protection // dailyLimit check
|
||||
if (m_spentToday + _value >= m_spentToday && m_spentToday + _value <= m_dailyLimit) {
|
||||
m_spentToday += _value;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// determines today's index.
|
||||
function today() private constant returns (uint) { return now / 1 days; }
|
||||
|
||||
|
||||
/******************************
|
||||
********* WALLET SECTION *****
|
||||
******************************/
|
||||
|
||||
// METHODS
|
||||
|
||||
// constructor - just pass on the owner array to the multiowned and
|
||||
// the limit to daylimit
|
||||
function initWallet(address[] _owners, uint _required, uint _daylimit) {
|
||||
initMultiowned(_owners, _required);
|
||||
initDaylimit(_daylimit) ;
|
||||
}
|
||||
|
||||
// kills the contract sending everything to `_to`.
|
||||
function kill(address _to) onlymanyowners(sha3(msg.data)) {
|
||||
suicide(_to);
|
||||
}
|
||||
|
||||
// Outside-visible transact entry point. Executes transaction immediately if below daily spend limit.
|
||||
// If not, goes into multisig process. We provide a hash on return to allow the sender to provide
|
||||
// shortcuts for the other confirmations (allowing them to avoid replicating the _to, _value
|
||||
// and _data arguments). They still get the option of using them if they want, anyways.
|
||||
function execute(address _to, uint _value, bytes _data) onlyowner returns(bool _callValue) {
|
||||
// first, take the opportunity to check that we're under the daily limit.
|
||||
if (underLimit(_value)) {
|
||||
SingleTransact(msg.sender, _value, _to, _data);
|
||||
// yes - just execute the call.
|
||||
_callValue =_to.call.value(_value)(_data);
|
||||
} else {
|
||||
// determine our operation hash.
|
||||
bytes32 _r = sha3(msg.data, block.number);
|
||||
if (!confirm(_r) && m_txs[_r].to == 0) {
|
||||
m_txs[_r].to = _to;
|
||||
m_txs[_r].value = _value;
|
||||
m_txs[_r].data = _data;
|
||||
ConfirmationNeeded(_r, msg.sender, _value, _to, _data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// confirm a transaction through just the hash. we use the previous transactions map, m_txs, in order
|
||||
// to determine the body of the transaction from the hash provided.
|
||||
function confirm(bytes32 _h) onlymanyowners(_h) returns (bool) {
|
||||
if (m_txs[_h].to != 0) {
|
||||
m_txs[_h].to.call.value(m_txs[_h].value)(m_txs[_h].data);
|
||||
MultiTransact(msg.sender, _h, m_txs[_h].value, m_txs[_h].to, m_txs[_h].data);
|
||||
delete m_txs[_h];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// INTERNAL METHODS
|
||||
|
||||
function clearWalletPending() internal {
|
||||
uint length = m_pendingIndex.length;
|
||||
for (uint i = 0; i < length; ++i)
|
||||
delete m_txs[m_pendingIndex[i]];
|
||||
clearPending();
|
||||
}
|
||||
|
||||
// FIELDS
|
||||
address constant _walletLibrary = 0xcafecafecafecafecafecafecafecafecafecafe;
|
||||
|
||||
// the number of owners that must confirm the same operation before it is run.
|
||||
uint m_required;
|
||||
// pointer used to find a free slot in m_owners
|
||||
uint m_numOwners;
|
||||
|
||||
uint public m_dailyLimit;
|
||||
uint public m_spentToday;
|
||||
uint public m_lastDay;
|
||||
|
||||
// list of owners
|
||||
uint[256] m_owners;
|
||||
uint constant c_maxOwners = 250;
|
||||
|
||||
// index on the list of owners to allow reverse lookup
|
||||
mapping(uint => uint) m_ownerIndex;
|
||||
// the ongoing operations.
|
||||
mapping(bytes32 => PendingState) m_pending;
|
||||
bytes32[] m_pendingIndex;
|
||||
|
||||
// pending transactions we have at present.
|
||||
mapping (bytes32 => Transaction) m_txs;
|
||||
// pending transactions we have at present.
|
||||
mapping (bytes32 => Transaction) m_txs;
|
||||
}
|
||||
|
||||
contract Wallet is WalletEvents {
|
||||
|
||||
contract Wallet is multisig {
|
||||
// WALLET CONSTRUCTOR
|
||||
// calls the `initWallet` method of the Library in this context
|
||||
function Wallet(address[] _owners, uint _required, uint _daylimit) {
|
||||
// Signature of the Wallet Library's init function
|
||||
bytes4 sig = bytes4(sha3("initWallet(address[],uint256,uint256)"));
|
||||
address target = _walletLibrary;
|
||||
|
||||
// WALLET CONSTRUCTOR
|
||||
// calls the `initWallet` method of the Library in this context
|
||||
function Wallet(address[] _owners, uint _required, uint _daylimit) {
|
||||
// Signature of the Wallet Library's init function
|
||||
bytes4 sig = bytes4(sha3("initWallet(address[],uint256,uint256)"));
|
||||
address target = _walletLibrary;
|
||||
// Compute the size of the call data : arrays has 2
|
||||
// 32bytes for offset and length, plus 32bytes per element ;
|
||||
// plus 2 32bytes for each uint
|
||||
uint argarraysize = (2 + _owners.length);
|
||||
uint argsize = (2 + argarraysize) * 32;
|
||||
|
||||
// Compute the size of the call data : arrays has 2
|
||||
// 32bytes for offset and length, plus 32bytes per element ;
|
||||
// plus 2 32bytes for each uint
|
||||
uint argarraysize = (2 + _owners.length);
|
||||
uint argsize = (2 + argarraysize) * 32;
|
||||
|
||||
assembly {
|
||||
// Add the signature first to memory
|
||||
mstore(0x0, sig)
|
||||
// Add the call data, which is at the end of the
|
||||
// code
|
||||
codecopy(0x4, sub(codesize, argsize), argsize)
|
||||
// Delegate call to the library
|
||||
delegatecall(sub(gas, 10000), target, 0x0, add(argsize, 0x4), 0x0, 0x0)
|
||||
}
|
||||
assembly {
|
||||
// Add the signature first to memory
|
||||
mstore(0x0, sig)
|
||||
// Add the call data, which is at the end of the
|
||||
// code
|
||||
codecopy(0x4, sub(codesize, argsize), argsize)
|
||||
// Delegate call to the library
|
||||
delegatecall(sub(gas, 10000), target, 0x0, add(argsize, 0x4), 0x0, 0x0)
|
||||
}
|
||||
}
|
||||
|
||||
// METHODS
|
||||
// METHODS
|
||||
|
||||
// gets called when no other function matches
|
||||
function() payable {
|
||||
// just being sent some cash?
|
||||
if (msg.value > 0)
|
||||
Deposit(msg.sender, msg.value);
|
||||
else if (msg.data.length > 0)
|
||||
_walletLibrary.delegatecall(msg.data);
|
||||
}
|
||||
// gets called when no other function matches
|
||||
function() payable {
|
||||
// just being sent some cash?
|
||||
if (msg.value > 0)
|
||||
Deposit(msg.sender, msg.value);
|
||||
else if (msg.data.length > 0)
|
||||
_walletLibrary.delegatecall(msg.data);
|
||||
}
|
||||
|
||||
// Gets an owner by 0-indexed position (using numOwners as the count)
|
||||
function getOwner(uint ownerIndex) constant returns (address) {
|
||||
return address(m_owners[ownerIndex + 1]);
|
||||
}
|
||||
// Gets an owner by 0-indexed position (using numOwners as the count)
|
||||
function getOwner(uint ownerIndex) constant returns (address) {
|
||||
return address(m_owners[ownerIndex + 1]);
|
||||
}
|
||||
|
||||
// As return statement unavailable in fallback, explicit the method here
|
||||
// As return statement unavailable in fallback, explicit the method here
|
||||
|
||||
function hasConfirmed(bytes32 _operation, address _owner) constant returns (bool) {
|
||||
return _walletLibrary.delegatecall(msg.data);
|
||||
}
|
||||
function hasConfirmed(bytes32 _operation, address _owner) external constant returns (bool) {
|
||||
return _walletLibrary.delegatecall(msg.data);
|
||||
}
|
||||
|
||||
function isOwner(address _addr) returns (bool) {
|
||||
return _walletLibrary.delegatecall(msg.data);
|
||||
}
|
||||
function isOwner(address _addr) constant returns (bool) {
|
||||
return _walletLibrary.delegatecall(msg.data);
|
||||
}
|
||||
|
||||
// FIELDS
|
||||
address constant _walletLibrary = 0xcafecafecafecafecafecafecafecafecafecafe;
|
||||
// FIELDS
|
||||
address constant _walletLibrary = 0xcafecafecafecafecafecafecafecafecafecafe;
|
||||
|
||||
// the number of owners that must confirm the same operation before it is run.
|
||||
uint public m_required;
|
||||
// pointer used to find a free slot in m_owners
|
||||
uint public m_numOwners;
|
||||
// the number of owners that must confirm the same operation before it is run.
|
||||
uint public m_required;
|
||||
// pointer used to find a free slot in m_owners
|
||||
uint public m_numOwners;
|
||||
|
||||
uint public m_dailyLimit;
|
||||
uint public m_spentToday;
|
||||
uint public m_lastDay;
|
||||
uint public m_dailyLimit;
|
||||
uint public m_spentToday;
|
||||
uint public m_lastDay;
|
||||
|
||||
// list of owners
|
||||
uint[256] m_owners;
|
||||
// list of owners
|
||||
uint[256] m_owners;
|
||||
}
|
||||
|
||||
@@ -8,221 +8,222 @@
|
||||
// use modifiers onlyowner (just own owned) or onlymanyowners(hash), whereby the same hash must be provided by
|
||||
// some number (specified in constructor) of the set of owners (specified in the constructor, modifiable) before the
|
||||
// interior is executed.
|
||||
pragma solidity ^0.4.6;
|
||||
|
||||
pragma solidity ^0.4.9;
|
||||
|
||||
contract multiowned {
|
||||
|
||||
// TYPES
|
||||
// TYPES
|
||||
|
||||
// struct for the status of a pending operation.
|
||||
struct PendingState {
|
||||
uint yetNeeded;
|
||||
uint ownersDone;
|
||||
uint index;
|
||||
// struct for the status of a pending operation.
|
||||
struct PendingState {
|
||||
uint yetNeeded;
|
||||
uint ownersDone;
|
||||
uint index;
|
||||
}
|
||||
|
||||
// EVENTS
|
||||
|
||||
// this contract only has six types of events: it can accept a confirmation, in which case
|
||||
// we record owner and operation (hash) alongside it.
|
||||
event Confirmation(address owner, bytes32 operation);
|
||||
event Revoke(address owner, bytes32 operation);
|
||||
// some others are in the case of an owner changing.
|
||||
event OwnerChanged(address oldOwner, address newOwner);
|
||||
event OwnerAdded(address newOwner);
|
||||
event OwnerRemoved(address oldOwner);
|
||||
// the last one is emitted if the required signatures change
|
||||
event RequirementChanged(uint newRequirement);
|
||||
|
||||
// MODIFIERS
|
||||
|
||||
// simple single-sig function modifier.
|
||||
modifier onlyowner {
|
||||
if (isOwner(msg.sender))
|
||||
_;
|
||||
}
|
||||
// multi-sig function modifier: the operation must have an intrinsic hash in order
|
||||
// that later attempts can be realised as the same underlying operation and
|
||||
// thus count as confirmations.
|
||||
modifier onlymanyowners(bytes32 _operation) {
|
||||
if (confirmAndCheck(_operation))
|
||||
_;
|
||||
}
|
||||
|
||||
// METHODS
|
||||
|
||||
// constructor is given number of sigs required to do protected "onlymanyowners" transactions
|
||||
// as well as the selection of addresses capable of confirming them.
|
||||
function multiowned(address[] _owners, uint _required) {
|
||||
m_numOwners = _owners.length + 1;
|
||||
m_owners[1] = uint(msg.sender);
|
||||
m_ownerIndex[uint(msg.sender)] = 1;
|
||||
for (uint i = 0; i < _owners.length; ++i)
|
||||
{
|
||||
m_owners[2 + i] = uint(_owners[i]);
|
||||
m_ownerIndex[uint(_owners[i])] = 2 + i;
|
||||
}
|
||||
m_required = _required;
|
||||
}
|
||||
|
||||
// EVENTS
|
||||
|
||||
// this contract only has six types of events: it can accept a confirmation, in which case
|
||||
// we record owner and operation (hash) alongside it.
|
||||
event Confirmation(address owner, bytes32 operation);
|
||||
event Revoke(address owner, bytes32 operation);
|
||||
// some others are in the case of an owner changing.
|
||||
event OwnerChanged(address oldOwner, address newOwner);
|
||||
event OwnerAdded(address newOwner);
|
||||
event OwnerRemoved(address oldOwner);
|
||||
// the last one is emitted if the required signatures change
|
||||
event RequirementChanged(uint newRequirement);
|
||||
|
||||
// MODIFIERS
|
||||
|
||||
// simple single-sig function modifier.
|
||||
modifier onlyowner {
|
||||
if (isOwner(msg.sender))
|
||||
_;
|
||||
// Revokes a prior confirmation of the given operation
|
||||
function revoke(bytes32 _operation) external {
|
||||
uint ownerIndex = m_ownerIndex[uint(msg.sender)];
|
||||
// make sure they're an owner
|
||||
if (ownerIndex == 0) return;
|
||||
uint ownerIndexBit = 2**ownerIndex;
|
||||
var pending = m_pending[_operation];
|
||||
if (pending.ownersDone & ownerIndexBit > 0) {
|
||||
pending.yetNeeded++;
|
||||
pending.ownersDone -= ownerIndexBit;
|
||||
Revoke(msg.sender, _operation);
|
||||
}
|
||||
// multi-sig function modifier: the operation must have an intrinsic hash in order
|
||||
// that later attempts can be realised as the same underlying operation and
|
||||
// thus count as confirmations.
|
||||
modifier onlymanyowners(bytes32 _operation) {
|
||||
if (confirmAndCheck(_operation))
|
||||
_;
|
||||
}
|
||||
|
||||
// Replaces an owner `_from` with another `_to`.
|
||||
function changeOwner(address _from, address _to) onlymanyowners(sha3(msg.data)) external {
|
||||
if (isOwner(_to)) return;
|
||||
uint ownerIndex = m_ownerIndex[uint(_from)];
|
||||
if (ownerIndex == 0) return;
|
||||
|
||||
clearPending();
|
||||
m_owners[ownerIndex] = uint(_to);
|
||||
m_ownerIndex[uint(_from)] = 0;
|
||||
m_ownerIndex[uint(_to)] = ownerIndex;
|
||||
OwnerChanged(_from, _to);
|
||||
}
|
||||
|
||||
function addOwner(address _owner) onlymanyowners(sha3(msg.data)) external {
|
||||
if (isOwner(_owner)) return;
|
||||
|
||||
clearPending();
|
||||
if (m_numOwners >= c_maxOwners)
|
||||
reorganizeOwners();
|
||||
if (m_numOwners >= c_maxOwners)
|
||||
return;
|
||||
m_numOwners++;
|
||||
m_owners[m_numOwners] = uint(_owner);
|
||||
m_ownerIndex[uint(_owner)] = m_numOwners;
|
||||
OwnerAdded(_owner);
|
||||
}
|
||||
|
||||
function removeOwner(address _owner) onlymanyowners(sha3(msg.data)) external {
|
||||
uint ownerIndex = m_ownerIndex[uint(_owner)];
|
||||
if (ownerIndex == 0) return;
|
||||
if (m_required > m_numOwners - 1) return;
|
||||
|
||||
m_owners[ownerIndex] = 0;
|
||||
m_ownerIndex[uint(_owner)] = 0;
|
||||
clearPending();
|
||||
reorganizeOwners(); //make sure m_numOwner is equal to the number of owners and always points to the optimal free slot
|
||||
OwnerRemoved(_owner);
|
||||
}
|
||||
|
||||
function changeRequirement(uint _newRequired) onlymanyowners(sha3(msg.data)) external {
|
||||
if (_newRequired > m_numOwners) return;
|
||||
m_required = _newRequired;
|
||||
clearPending();
|
||||
RequirementChanged(_newRequired);
|
||||
}
|
||||
|
||||
// Gets an owner by 0-indexed position (using numOwners as the count)
|
||||
function getOwner(uint ownerIndex) external constant returns (address) {
|
||||
return address(m_owners[ownerIndex + 1]);
|
||||
}
|
||||
|
||||
function isOwner(address _addr) constant returns (bool) {
|
||||
return m_ownerIndex[uint(_addr)] > 0;
|
||||
}
|
||||
|
||||
function hasConfirmed(bytes32 _operation, address _owner) constant returns (bool) {
|
||||
var pending = m_pending[_operation];
|
||||
uint ownerIndex = m_ownerIndex[uint(_owner)];
|
||||
|
||||
// make sure they're an owner
|
||||
if (ownerIndex == 0) return false;
|
||||
|
||||
// determine the bit to set for this owner.
|
||||
uint ownerIndexBit = 2**ownerIndex;
|
||||
return !(pending.ownersDone & ownerIndexBit == 0);
|
||||
}
|
||||
|
||||
// INTERNAL METHODS
|
||||
|
||||
function confirmAndCheck(bytes32 _operation) internal returns (bool) {
|
||||
// determine what index the present sender is:
|
||||
uint ownerIndex = m_ownerIndex[uint(msg.sender)];
|
||||
// make sure they're an owner
|
||||
if (ownerIndex == 0) return;
|
||||
|
||||
var pending = m_pending[_operation];
|
||||
// if we're not yet working on this operation, switch over and reset the confirmation status.
|
||||
if (pending.yetNeeded == 0) {
|
||||
// reset count of confirmations needed.
|
||||
pending.yetNeeded = m_required;
|
||||
// reset which owners have confirmed (none) - set our bitmap to 0.
|
||||
pending.ownersDone = 0;
|
||||
pending.index = m_pendingIndex.length++;
|
||||
m_pendingIndex[pending.index] = _operation;
|
||||
}
|
||||
|
||||
// METHODS
|
||||
|
||||
// constructor is given number of sigs required to do protected "onlymanyowners" transactions
|
||||
// as well as the selection of addresses capable of confirming them.
|
||||
function multiowned(address[] _owners, uint _required) {
|
||||
m_numOwners = _owners.length + 1;
|
||||
m_owners[1] = uint(msg.sender);
|
||||
m_ownerIndex[uint(msg.sender)] = 1;
|
||||
for (uint i = 0; i < _owners.length; ++i)
|
||||
{
|
||||
m_owners[2 + i] = uint(_owners[i]);
|
||||
m_ownerIndex[uint(_owners[i])] = 2 + i;
|
||||
}
|
||||
m_required = _required;
|
||||
// determine the bit to set for this owner.
|
||||
uint ownerIndexBit = 2**ownerIndex;
|
||||
// make sure we (the message sender) haven't confirmed this operation previously.
|
||||
if (pending.ownersDone & ownerIndexBit == 0) {
|
||||
Confirmation(msg.sender, _operation);
|
||||
// ok - check if count is enough to go ahead.
|
||||
if (pending.yetNeeded <= 1) {
|
||||
// enough confirmations: reset and run interior.
|
||||
delete m_pendingIndex[m_pending[_operation].index];
|
||||
delete m_pending[_operation];
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// not enough: record that this owner in particular confirmed.
|
||||
pending.yetNeeded--;
|
||||
pending.ownersDone |= ownerIndexBit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Revokes a prior confirmation of the given operation
|
||||
function revoke(bytes32 _operation) external {
|
||||
uint ownerIndex = m_ownerIndex[uint(msg.sender)];
|
||||
// make sure they're an owner
|
||||
if (ownerIndex == 0) return;
|
||||
uint ownerIndexBit = 2**ownerIndex;
|
||||
var pending = m_pending[_operation];
|
||||
if (pending.ownersDone & ownerIndexBit > 0) {
|
||||
pending.yetNeeded++;
|
||||
pending.ownersDone -= ownerIndexBit;
|
||||
Revoke(msg.sender, _operation);
|
||||
}
|
||||
function reorganizeOwners() private {
|
||||
uint free = 1;
|
||||
while (free < m_numOwners)
|
||||
{
|
||||
while (free < m_numOwners && m_owners[free] != 0) free++;
|
||||
while (m_numOwners > 1 && m_owners[m_numOwners] == 0) m_numOwners--;
|
||||
if (free < m_numOwners && m_owners[m_numOwners] != 0 && m_owners[free] == 0)
|
||||
{
|
||||
m_owners[free] = m_owners[m_numOwners];
|
||||
m_ownerIndex[m_owners[free]] = free;
|
||||
m_owners[m_numOwners] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Replaces an owner `_from` with another `_to`.
|
||||
function changeOwner(address _from, address _to) onlymanyowners(sha3(msg.data)) external {
|
||||
if (isOwner(_to)) return;
|
||||
uint ownerIndex = m_ownerIndex[uint(_from)];
|
||||
if (ownerIndex == 0) return;
|
||||
function clearPending() internal {
|
||||
uint length = m_pendingIndex.length;
|
||||
for (uint i = 0; i < length; ++i)
|
||||
if (m_pendingIndex[i] != 0)
|
||||
delete m_pending[m_pendingIndex[i]];
|
||||
delete m_pendingIndex;
|
||||
}
|
||||
|
||||
clearPending();
|
||||
m_owners[ownerIndex] = uint(_to);
|
||||
m_ownerIndex[uint(_from)] = 0;
|
||||
m_ownerIndex[uint(_to)] = ownerIndex;
|
||||
OwnerChanged(_from, _to);
|
||||
}
|
||||
// FIELDS
|
||||
|
||||
function addOwner(address _owner) onlymanyowners(sha3(msg.data)) external {
|
||||
if (isOwner(_owner)) return;
|
||||
// the number of owners that must confirm the same operation before it is run.
|
||||
uint public m_required;
|
||||
// pointer used to find a free slot in m_owners
|
||||
uint public m_numOwners;
|
||||
|
||||
clearPending();
|
||||
if (m_numOwners >= c_maxOwners)
|
||||
reorganizeOwners();
|
||||
if (m_numOwners >= c_maxOwners)
|
||||
return;
|
||||
m_numOwners++;
|
||||
m_owners[m_numOwners] = uint(_owner);
|
||||
m_ownerIndex[uint(_owner)] = m_numOwners;
|
||||
OwnerAdded(_owner);
|
||||
}
|
||||
|
||||
function removeOwner(address _owner) onlymanyowners(sha3(msg.data)) external {
|
||||
uint ownerIndex = m_ownerIndex[uint(_owner)];
|
||||
if (ownerIndex == 0) return;
|
||||
if (m_required > m_numOwners - 1) return;
|
||||
|
||||
m_owners[ownerIndex] = 0;
|
||||
m_ownerIndex[uint(_owner)] = 0;
|
||||
clearPending();
|
||||
reorganizeOwners(); //make sure m_numOwner is equal to the number of owners and always points to the optimal free slot
|
||||
OwnerRemoved(_owner);
|
||||
}
|
||||
|
||||
function changeRequirement(uint _newRequired) onlymanyowners(sha3(msg.data)) external {
|
||||
if (_newRequired > m_numOwners) return;
|
||||
m_required = _newRequired;
|
||||
clearPending();
|
||||
RequirementChanged(_newRequired);
|
||||
}
|
||||
|
||||
// Gets an owner by 0-indexed position (using numOwners as the count)
|
||||
function getOwner(uint ownerIndex) external constant returns (address) {
|
||||
return address(m_owners[ownerIndex + 1]);
|
||||
}
|
||||
|
||||
function isOwner(address _addr) returns (bool) {
|
||||
return m_ownerIndex[uint(_addr)] > 0;
|
||||
}
|
||||
|
||||
function hasConfirmed(bytes32 _operation, address _owner) constant returns (bool) {
|
||||
var pending = m_pending[_operation];
|
||||
uint ownerIndex = m_ownerIndex[uint(_owner)];
|
||||
|
||||
// make sure they're an owner
|
||||
if (ownerIndex == 0) return false;
|
||||
|
||||
// determine the bit to set for this owner.
|
||||
uint ownerIndexBit = 2**ownerIndex;
|
||||
return !(pending.ownersDone & ownerIndexBit == 0);
|
||||
}
|
||||
|
||||
// INTERNAL METHODS
|
||||
|
||||
function confirmAndCheck(bytes32 _operation) internal returns (bool) {
|
||||
// determine what index the present sender is:
|
||||
uint ownerIndex = m_ownerIndex[uint(msg.sender)];
|
||||
// make sure they're an owner
|
||||
if (ownerIndex == 0) return;
|
||||
|
||||
var pending = m_pending[_operation];
|
||||
// if we're not yet working on this operation, switch over and reset the confirmation status.
|
||||
if (pending.yetNeeded == 0) {
|
||||
// reset count of confirmations needed.
|
||||
pending.yetNeeded = m_required;
|
||||
// reset which owners have confirmed (none) - set our bitmap to 0.
|
||||
pending.ownersDone = 0;
|
||||
pending.index = m_pendingIndex.length++;
|
||||
m_pendingIndex[pending.index] = _operation;
|
||||
}
|
||||
// determine the bit to set for this owner.
|
||||
uint ownerIndexBit = 2**ownerIndex;
|
||||
// make sure we (the message sender) haven't confirmed this operation previously.
|
||||
if (pending.ownersDone & ownerIndexBit == 0) {
|
||||
Confirmation(msg.sender, _operation);
|
||||
// ok - check if count is enough to go ahead.
|
||||
if (pending.yetNeeded <= 1) {
|
||||
// enough confirmations: reset and run interior.
|
||||
delete m_pendingIndex[m_pending[_operation].index];
|
||||
delete m_pending[_operation];
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// not enough: record that this owner in particular confirmed.
|
||||
pending.yetNeeded--;
|
||||
pending.ownersDone |= ownerIndexBit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function reorganizeOwners() private {
|
||||
uint free = 1;
|
||||
while (free < m_numOwners)
|
||||
{
|
||||
while (free < m_numOwners && m_owners[free] != 0) free++;
|
||||
while (m_numOwners > 1 && m_owners[m_numOwners] == 0) m_numOwners--;
|
||||
if (free < m_numOwners && m_owners[m_numOwners] != 0 && m_owners[free] == 0)
|
||||
{
|
||||
m_owners[free] = m_owners[m_numOwners];
|
||||
m_ownerIndex[m_owners[free]] = free;
|
||||
m_owners[m_numOwners] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function clearPending() internal {
|
||||
uint length = m_pendingIndex.length;
|
||||
for (uint i = 0; i < length; ++i)
|
||||
if (m_pendingIndex[i] != 0)
|
||||
delete m_pending[m_pendingIndex[i]];
|
||||
delete m_pendingIndex;
|
||||
}
|
||||
|
||||
// FIELDS
|
||||
|
||||
// the number of owners that must confirm the same operation before it is run.
|
||||
uint public m_required;
|
||||
// pointer used to find a free slot in m_owners
|
||||
uint public m_numOwners;
|
||||
|
||||
// list of owners
|
||||
uint[256] m_owners;
|
||||
uint constant c_maxOwners = 250;
|
||||
// index on the list of owners to allow reverse lookup
|
||||
mapping(uint => uint) m_ownerIndex;
|
||||
// the ongoing operations.
|
||||
mapping(bytes32 => PendingState) m_pending;
|
||||
bytes32[] m_pendingIndex;
|
||||
// list of owners
|
||||
uint[256] m_owners;
|
||||
uint constant c_maxOwners = 250;
|
||||
// index on the list of owners to allow reverse lookup
|
||||
mapping(uint => uint) m_ownerIndex;
|
||||
// the ongoing operations.
|
||||
mapping(bytes32 => PendingState) m_pending;
|
||||
bytes32[] m_pendingIndex;
|
||||
}
|
||||
|
||||
// inheritable "property" contract that enables methods to be protected by placing a linear limit (specifiable)
|
||||
@@ -230,79 +231,70 @@ contract multiowned {
|
||||
// uses is specified in the modifier.
|
||||
contract daylimit is multiowned {
|
||||
|
||||
// MODIFIERS
|
||||
// METHODS
|
||||
|
||||
// simple modifier for daily limit.
|
||||
modifier limitedDaily(uint _value) {
|
||||
if (underLimit(_value))
|
||||
_;
|
||||
// constructor - stores initial daily limit and records the present day's index.
|
||||
function daylimit(uint _limit) {
|
||||
m_dailyLimit = _limit;
|
||||
m_lastDay = today();
|
||||
}
|
||||
// (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today.
|
||||
function setDailyLimit(uint _newLimit) onlymanyowners(sha3(msg.data)) external {
|
||||
m_dailyLimit = _newLimit;
|
||||
}
|
||||
// resets the amount already spent today. needs many of the owners to confirm.
|
||||
function resetSpentToday() onlymanyowners(sha3(msg.data)) external {
|
||||
m_spentToday = 0;
|
||||
}
|
||||
|
||||
// INTERNAL METHODS
|
||||
|
||||
// checks to see if there is at least `_value` left from the daily limit today. if there is, subtracts it and
|
||||
// returns true. otherwise just returns false.
|
||||
function underLimit(uint _value) internal onlyowner returns (bool) {
|
||||
// reset the spend limit if we're on a different day to last time.
|
||||
if (today() > m_lastDay) {
|
||||
m_spentToday = 0;
|
||||
m_lastDay = today();
|
||||
}
|
||||
|
||||
// METHODS
|
||||
|
||||
// constructor - stores initial daily limit and records the present day's index.
|
||||
function daylimit(uint _limit) {
|
||||
m_dailyLimit = _limit;
|
||||
m_lastDay = today();
|
||||
}
|
||||
// (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today.
|
||||
function setDailyLimit(uint _newLimit) onlymanyowners(sha3(msg.data)) external {
|
||||
m_dailyLimit = _newLimit;
|
||||
}
|
||||
// resets the amount already spent today. needs many of the owners to confirm.
|
||||
function resetSpentToday() onlymanyowners(sha3(msg.data)) external {
|
||||
m_spentToday = 0;
|
||||
// check to see if there's enough left - if so, subtract and return true.
|
||||
// overflow protection // dailyLimit check
|
||||
if (m_spentToday + _value >= m_spentToday && m_spentToday + _value <= m_dailyLimit) {
|
||||
m_spentToday += _value;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// determines today's index.
|
||||
function today() private constant returns (uint) { return now / 1 days; }
|
||||
|
||||
// INTERNAL METHODS
|
||||
// FIELDS
|
||||
|
||||
// checks to see if there is at least `_value` left from the daily limit today. if there is, subtracts it and
|
||||
// returns true. otherwise just returns false.
|
||||
function underLimit(uint _value) internal onlyowner returns (bool) {
|
||||
// reset the spend limit if we're on a different day to last time.
|
||||
if (today() > m_lastDay) {
|
||||
m_spentToday = 0;
|
||||
m_lastDay = today();
|
||||
}
|
||||
// check to see if there's enough left - if so, subtract and return true.
|
||||
// overflow protection // dailyLimit check
|
||||
if (m_spentToday + _value >= m_spentToday && m_spentToday + _value <= m_dailyLimit) {
|
||||
m_spentToday += _value;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// determines today's index.
|
||||
function today() private constant returns (uint) { return now / 1 days; }
|
||||
|
||||
// FIELDS
|
||||
|
||||
uint public m_dailyLimit;
|
||||
uint public m_spentToday;
|
||||
uint public m_lastDay;
|
||||
uint public m_dailyLimit;
|
||||
uint public m_spentToday;
|
||||
uint public m_lastDay;
|
||||
}
|
||||
|
||||
// interface contract for multisig proxy contracts; see below for docs.
|
||||
contract multisig {
|
||||
|
||||
// EVENTS
|
||||
// EVENTS
|
||||
|
||||
// logged events:
|
||||
// Funds has arrived into the wallet (record how much).
|
||||
event Deposit(address _from, uint value);
|
||||
// Single transaction going out of the wallet (record who signed for it, how much, and to whom it's going).
|
||||
event SingleTransact(address owner, uint value, address to, bytes data);
|
||||
// Multi-sig transaction going out of the wallet (record who signed for it last, the operation hash, how much, and to whom it's going).
|
||||
event MultiTransact(address owner, bytes32 operation, uint value, address to, bytes data);
|
||||
// Confirmation still needed for a transaction.
|
||||
event ConfirmationNeeded(bytes32 operation, address initiator, uint value, address to, bytes data);
|
||||
// logged events:
|
||||
// Funds has arrived into the wallet (record how much).
|
||||
event Deposit(address _from, uint value);
|
||||
// Single transaction going out of the wallet (record who signed for it, how much, and to whom it's going).
|
||||
event SingleTransact(address owner, uint value, address to, bytes data, address created);
|
||||
// Multi-sig transaction going out of the wallet (record who signed for it last, the operation hash, how much, and to whom it's going).
|
||||
event MultiTransact(address owner, bytes32 operation, uint value, address to, bytes data, address created);
|
||||
// Confirmation still needed for a transaction.
|
||||
event ConfirmationNeeded(bytes32 operation, address initiator, uint value, address to, bytes data);
|
||||
|
||||
// FUNCTIONS
|
||||
// FUNCTIONS
|
||||
|
||||
// TODO: document
|
||||
function changeOwner(address _from, address _to) external;
|
||||
function execute(address _to, uint _value, bytes _data) external returns (bytes32);
|
||||
function confirm(bytes32 _h) returns (bool);
|
||||
// TODO: document
|
||||
function execute(address _to, uint _value, bytes _data) external returns (bytes32 o_hash);
|
||||
function confirm(bytes32 _h) external returns (bool o_success);
|
||||
}
|
||||
|
||||
// usage:
|
||||
@@ -310,79 +302,102 @@ contract multisig {
|
||||
// Wallet(w).from(anotherOwner).confirm(h);
|
||||
contract Wallet is multisig, multiowned, daylimit {
|
||||
|
||||
// TYPES
|
||||
// TYPES
|
||||
|
||||
// Transaction structure to remember details of transaction lest it need be saved for a later call.
|
||||
struct Transaction {
|
||||
address to;
|
||||
uint value;
|
||||
bytes data;
|
||||
// Transaction structure to remember details of transaction lest it need be saved for a later call.
|
||||
struct Transaction {
|
||||
address to;
|
||||
uint value;
|
||||
bytes data;
|
||||
}
|
||||
|
||||
// METHODS
|
||||
|
||||
// constructor - just pass on the owner array to the multiowned and
|
||||
// the limit to daylimit
|
||||
function Wallet(address[] _owners, uint _required, uint _daylimit)
|
||||
multiowned(_owners, _required) daylimit(_daylimit) {
|
||||
}
|
||||
|
||||
// kills the contract sending everything to `_to`.
|
||||
function kill(address _to) onlymanyowners(sha3(msg.data)) external {
|
||||
suicide(_to);
|
||||
}
|
||||
|
||||
// gets called when no other function matches
|
||||
function() payable {
|
||||
// just being sent some cash?
|
||||
if (msg.value > 0)
|
||||
Deposit(msg.sender, msg.value);
|
||||
}
|
||||
|
||||
// Outside-visible transact entry point. Executes transaction immediately if below daily spend limit.
|
||||
// If not, goes into multisig process. We provide a hash on return to allow the sender to provide
|
||||
// shortcuts for the other confirmations (allowing them to avoid replicating the _to, _value
|
||||
// and _data arguments). They still get the option of using them if they want, anyways.
|
||||
function execute(address _to, uint _value, bytes _data) external onlyowner returns (bytes32 o_hash) {
|
||||
// first, take the opportunity to check that we're under the daily limit.
|
||||
if ((_data.length == 0 && underLimit(_value)) || m_required == 1) {
|
||||
// yes - just execute the call.
|
||||
address created;
|
||||
if (_to == 0) {
|
||||
created = create(_value, _data);
|
||||
} else {
|
||||
if (!_to.call.value(_value)(_data))
|
||||
throw;
|
||||
}
|
||||
SingleTransact(msg.sender, _value, _to, _data, created);
|
||||
} else {
|
||||
// determine our operation hash.
|
||||
o_hash = sha3(msg.data, block.number);
|
||||
// store if it's new
|
||||
if (m_txs[o_hash].to == 0 && m_txs[o_hash].value == 0 && m_txs[o_hash].data.length == 0) {
|
||||
m_txs[o_hash].to = _to;
|
||||
m_txs[o_hash].value = _value;
|
||||
m_txs[o_hash].data = _data;
|
||||
}
|
||||
if (!confirm(o_hash)) {
|
||||
ConfirmationNeeded(o_hash, msg.sender, _value, _to, _data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// METHODS
|
||||
|
||||
// constructor - just pass on the owner array to the multiowned and
|
||||
// the limit to daylimit
|
||||
function Wallet(address[] _owners, uint _required, uint _daylimit)
|
||||
multiowned(_owners, _required) daylimit(_daylimit) {
|
||||
function create(uint _value, bytes _code) internal returns (address o_addr) {
|
||||
assembly {
|
||||
o_addr := create(_value, add(_code, 0x20), mload(_code))
|
||||
jumpi(invalidJumpLabel, iszero(extcodesize(o_addr)))
|
||||
}
|
||||
}
|
||||
|
||||
// kills the contract sending everything to `_to`.
|
||||
function kill(address _to) onlymanyowners(sha3(msg.data)) external {
|
||||
suicide(_to);
|
||||
// confirm a transaction through just the hash. we use the previous transactions map, m_txs, in order
|
||||
// to determine the body of the transaction from the hash provided.
|
||||
function confirm(bytes32 _h) onlymanyowners(_h) returns (bool o_success) {
|
||||
if (m_txs[_h].to != 0 || m_txs[_h].value != 0 || m_txs[_h].data.length != 0) {
|
||||
address created;
|
||||
if (m_txs[_h].to == 0) {
|
||||
created = create(m_txs[_h].value, m_txs[_h].data);
|
||||
} else {
|
||||
if (!m_txs[_h].to.call.value(m_txs[_h].value)(m_txs[_h].data))
|
||||
throw;
|
||||
}
|
||||
|
||||
MultiTransact(msg.sender, _h, m_txs[_h].value, m_txs[_h].to, m_txs[_h].data, created);
|
||||
delete m_txs[_h];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// gets called when no other function matches
|
||||
function() payable {
|
||||
// just being sent some cash?
|
||||
if (msg.value > 0)
|
||||
Deposit(msg.sender, msg.value);
|
||||
}
|
||||
// INTERNAL METHODS
|
||||
|
||||
// Outside-visible transact entry point. Executes transaction immediately if below daily spend limit.
|
||||
// If not, goes into multisig process. We provide a hash on return to allow the sender to provide
|
||||
// shortcuts for the other confirmations (allowing them to avoid replicating the _to, _value
|
||||
// and _data arguments). They still get the option of using them if they want, anyways.
|
||||
function execute(address _to, uint _value, bytes _data) external onlyowner returns (bytes32 _r) {
|
||||
// first, take the opportunity to check that we're under the daily limit.
|
||||
if (underLimit(_value)) {
|
||||
SingleTransact(msg.sender, _value, _to, _data);
|
||||
// yes - just execute the call.
|
||||
_to.call.value(_value)(_data);
|
||||
return 0;
|
||||
}
|
||||
// determine our operation hash.
|
||||
_r = sha3(msg.data, block.number);
|
||||
if (!confirm(_r) && m_txs[_r].to == 0) {
|
||||
m_txs[_r].to = _to;
|
||||
m_txs[_r].value = _value;
|
||||
m_txs[_r].data = _data;
|
||||
ConfirmationNeeded(_r, msg.sender, _value, _to, _data);
|
||||
}
|
||||
}
|
||||
function clearPending() internal {
|
||||
uint length = m_pendingIndex.length;
|
||||
for (uint i = 0; i < length; ++i)
|
||||
delete m_txs[m_pendingIndex[i]];
|
||||
super.clearPending();
|
||||
}
|
||||
|
||||
// confirm a transaction through just the hash. we use the previous transactions map, m_txs, in order
|
||||
// to determine the body of the transaction from the hash provided.
|
||||
function confirm(bytes32 _h) onlymanyowners(_h) returns (bool) {
|
||||
if (m_txs[_h].to != 0) {
|
||||
m_txs[_h].to.call.value(m_txs[_h].value)(m_txs[_h].data);
|
||||
MultiTransact(msg.sender, _h, m_txs[_h].value, m_txs[_h].to, m_txs[_h].data);
|
||||
delete m_txs[_h];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// FIELDS
|
||||
|
||||
// INTERNAL METHODS
|
||||
|
||||
function clearPending() internal {
|
||||
uint length = m_pendingIndex.length;
|
||||
for (uint i = 0; i < length; ++i)
|
||||
delete m_txs[m_pendingIndex[i]];
|
||||
super.clearPending();
|
||||
}
|
||||
|
||||
// FIELDS
|
||||
|
||||
// pending transactions we have at present.
|
||||
mapping (bytes32 => Transaction) m_txs;
|
||||
// pending transactions we have at present.
|
||||
mapping (bytes32 => Transaction) m_txs;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
import ReactDOM from 'react-dom';
|
||||
import React from 'react';
|
||||
import { AppContainer } from 'react-hot-loader';
|
||||
|
||||
import injectTapEventPlugin from 'react-tap-event-plugin';
|
||||
injectTapEventPlugin();
|
||||
@@ -27,6 +28,21 @@ import '../../assets/fonts/RobotoMono/font.css';
|
||||
import './style.css';
|
||||
|
||||
ReactDOM.render(
|
||||
<Application />,
|
||||
<AppContainer>
|
||||
<Application />
|
||||
</AppContainer>,
|
||||
document.querySelector('#container')
|
||||
);
|
||||
|
||||
if (module.hot) {
|
||||
module.hot.accept('./githubhint/Application/index.js', () => {
|
||||
require('./githubhint/Application/index.js');
|
||||
|
||||
ReactDOM.render(
|
||||
<AppContainer>
|
||||
<Application />
|
||||
</AppContainer>,
|
||||
document.querySelector('#container')
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
import ReactDOM from 'react-dom';
|
||||
import React from 'react';
|
||||
import { AppContainer } from 'react-hot-loader';
|
||||
|
||||
import injectTapEventPlugin from 'react-tap-event-plugin';
|
||||
injectTapEventPlugin();
|
||||
@@ -27,6 +28,21 @@ import '../../assets/fonts/RobotoMono/font.css';
|
||||
import './style.css';
|
||||
|
||||
ReactDOM.render(
|
||||
<Application />,
|
||||
<AppContainer>
|
||||
<Application />
|
||||
</AppContainer>,
|
||||
document.querySelector('#container')
|
||||
);
|
||||
|
||||
if (module.hot) {
|
||||
module.hot.accept('./localtx/Application/index.js', () => {
|
||||
require('./localtx/Application/index.js');
|
||||
|
||||
ReactDOM.render(
|
||||
<AppContainer>
|
||||
<Application />
|
||||
</AppContainer>,
|
||||
document.querySelector('#container')
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -15,5 +15,29 @@
|
||||
th {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
td {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: rgba(0, 136, 170, 1);
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
color: white;
|
||||
font-size: 1rem;
|
||||
padding: 0.5em 1em;
|
||||
width: 100%;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(0, 136, 170, 0.8);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
input {
|
||||
font-size: 1rem;
|
||||
padding: 0.5em 1em;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,6 @@ export default class Application extends Component {
|
||||
local[tx.hash].transaction = tx;
|
||||
local[tx.hash].stats = data.stats;
|
||||
});
|
||||
|
||||
// Convert local transactions to array
|
||||
const localTransactions = Object.keys(local).map(hash => {
|
||||
const data = local[hash];
|
||||
|
||||
@@ -6,6 +6,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
.txhash {
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
padding-right: 3ch;
|
||||
text-overflow: ellipsis;
|
||||
width: 10ch;
|
||||
}
|
||||
|
||||
.transaction {
|
||||
td {
|
||||
padding: 7px 15px;
|
||||
|
||||
@@ -31,8 +31,8 @@ class BaseTransaction extends Component {
|
||||
|
||||
renderHash (hash) {
|
||||
return (
|
||||
<code title={ hash }>
|
||||
{ this.shortHash(hash) }
|
||||
<code title={ hash } className={ styles.txhash }>
|
||||
{ hash }
|
||||
</code>
|
||||
);
|
||||
}
|
||||
@@ -206,7 +206,10 @@ export class LocalTransaction extends BaseTransaction {
|
||||
From
|
||||
</th>
|
||||
<th>
|
||||
Gas Price / Gas
|
||||
Gas Price
|
||||
</th>
|
||||
<th>
|
||||
Gas
|
||||
</th>
|
||||
<th>
|
||||
Status
|
||||
@@ -224,18 +227,18 @@ export class LocalTransaction extends BaseTransaction {
|
||||
|
||||
toggleResubmit = () => {
|
||||
const { transaction } = this.props;
|
||||
const { isResubmitting, gasPrice } = this.state;
|
||||
const { isResubmitting } = this.state;
|
||||
|
||||
this.setState({
|
||||
const nextState = {
|
||||
isResubmitting: !isResubmitting
|
||||
});
|
||||
};
|
||||
|
||||
if (gasPrice === null) {
|
||||
this.setState({
|
||||
gasPrice: `0x${transaction.gasPrice.toString(16)}`,
|
||||
gas: `0x${transaction.gas.toString(16)}`
|
||||
});
|
||||
if (!isResubmitting) {
|
||||
nextState.gasPrice = api.util.fromWei(transaction.gasPrice, 'shannon').toNumber();
|
||||
nextState.gas = transaction.gas.div(1000000).toNumber();
|
||||
}
|
||||
|
||||
this.setState(nextState);
|
||||
};
|
||||
|
||||
setGasPrice = el => {
|
||||
@@ -251,16 +254,15 @@ export class LocalTransaction extends BaseTransaction {
|
||||
};
|
||||
|
||||
sendTransaction = () => {
|
||||
const { transaction } = this.props;
|
||||
const { transaction, status } = this.props;
|
||||
const { gasPrice, gas } = this.state;
|
||||
|
||||
const newTransaction = {
|
||||
from: transaction.from,
|
||||
to: transaction.to,
|
||||
nonce: transaction.nonce,
|
||||
value: transaction.value,
|
||||
data: transaction.input,
|
||||
gasPrice, gas
|
||||
gasPrice: api.util.toWei(gasPrice, 'shannon'),
|
||||
gas: new BigNumber(gas).mul(1000000)
|
||||
};
|
||||
|
||||
this.setState({
|
||||
@@ -268,11 +270,21 @@ export class LocalTransaction extends BaseTransaction {
|
||||
isSending: true
|
||||
});
|
||||
|
||||
const closeSending = () => this.setState({
|
||||
isSending: false,
|
||||
gasPrice: null,
|
||||
gas: null
|
||||
});
|
||||
const closeSending = () => {
|
||||
this.setState({
|
||||
isSending: false,
|
||||
gasPrice: null,
|
||||
gas: null
|
||||
});
|
||||
};
|
||||
|
||||
if (transaction.to) {
|
||||
newTransaction.to = transaction.to;
|
||||
}
|
||||
|
||||
if (!['mined', 'replaced'].includes(status)) {
|
||||
newTransaction.nonce = transaction.nonce;
|
||||
}
|
||||
|
||||
api.eth.sendTransaction(newTransaction)
|
||||
.then(closeSending)
|
||||
@@ -290,9 +302,9 @@ export class LocalTransaction extends BaseTransaction {
|
||||
const resubmit = isSending ? (
|
||||
'sending...'
|
||||
) : (
|
||||
<a href='javascript:void' onClick={ this.toggleResubmit }>
|
||||
<button onClick={ this.toggleResubmit }>
|
||||
resubmit
|
||||
</a>
|
||||
</button>
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -308,7 +320,8 @@ export class LocalTransaction extends BaseTransaction {
|
||||
</td>
|
||||
<td>
|
||||
{ this.renderGasPrice(transaction) }
|
||||
<br />
|
||||
</td>
|
||||
<td>
|
||||
{ this.renderGas(transaction) }
|
||||
</td>
|
||||
<td>
|
||||
@@ -345,9 +358,9 @@ export class LocalTransaction extends BaseTransaction {
|
||||
return (
|
||||
<tr className={ styles.transaction }>
|
||||
<td>
|
||||
<a href='javascript:void' onClick={ this.toggleResubmit }>
|
||||
<button onClick={ this.toggleResubmit }>
|
||||
cancel
|
||||
</a>
|
||||
</button>
|
||||
</td>
|
||||
<td>
|
||||
{ this.renderHash(transaction.hash) }
|
||||
@@ -357,20 +370,24 @@ export class LocalTransaction extends BaseTransaction {
|
||||
</td>
|
||||
<td className={ styles.edit }>
|
||||
<input
|
||||
type='text'
|
||||
type='number'
|
||||
value={ gasPrice }
|
||||
onChange={ this.setGasPrice }
|
||||
/>
|
||||
<span>shannon</span>
|
||||
</td>
|
||||
<td className={ styles.edit }>
|
||||
<input
|
||||
type='text'
|
||||
type='number'
|
||||
value={ gas }
|
||||
onChange={ this.setGas }
|
||||
/>
|
||||
<span>MGas</span>
|
||||
</td>
|
||||
<td colSpan='2'>
|
||||
<a href='javascript:void' onClick={ this.sendTransaction }>
|
||||
<button onClick={ this.sendTransaction }>
|
||||
Send
|
||||
</a>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { sha3 } from '../parity.js';
|
||||
import { api, sha3 } from '../parity.js';
|
||||
import { getOwner } from '../util/registry';
|
||||
|
||||
export const clear = () => ({ type: 'lookup clear' });
|
||||
@@ -34,14 +34,21 @@ export const lookup = (name, key) => (dispatch, getState) => {
|
||||
return;
|
||||
}
|
||||
|
||||
const getAddress = contract.functions
|
||||
.find((f) => f.name === 'getAddress');
|
||||
const method = key === 'A'
|
||||
? contract.instance.getAddress
|
||||
: contract.instance.getData;
|
||||
|
||||
name = name.toLowerCase();
|
||||
dispatch(lookupStart(name, key));
|
||||
|
||||
getAddress.call({}, [ sha3.text(name), key ])
|
||||
.then((address) => dispatch(success('lookup', address)))
|
||||
method.call({}, [ sha3.text(name), key ])
|
||||
.then((result) => {
|
||||
if (key !== 'A') {
|
||||
result = api.util.bytesToHex(result);
|
||||
}
|
||||
|
||||
dispatch(success('lookup', result));
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(`could not lookup ${key} for ${name}`);
|
||||
if (err) {
|
||||
|
||||
@@ -46,8 +46,9 @@ export const update = (name, key, value) => (dispatch, getState) => {
|
||||
throw new Error(`you are not the owner of "${name}"`);
|
||||
}
|
||||
|
||||
const fnName = key === 'A' ? 'setAddress' : 'set';
|
||||
const method = contract.instance[fnName];
|
||||
const method = key === 'A'
|
||||
? contract.instance.setAddress
|
||||
: contract.instance.setData || contract.instance.set;
|
||||
|
||||
const options = {
|
||||
from: account.address
|
||||
|
||||
@@ -34,7 +34,7 @@ export default (address) => {
|
||||
|
||||
return (
|
||||
<img
|
||||
src={ `${parityNode}/${address}/` }
|
||||
src={ `${parityNode}/api/content/${address.replace(/^0x/, '')}` }
|
||||
alt={ address }
|
||||
style={ styles }
|
||||
/>
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
import ReactDOM from 'react-dom';
|
||||
import React from 'react';
|
||||
import { AppContainer } from 'react-hot-loader';
|
||||
|
||||
import injectTapEventPlugin from 'react-tap-event-plugin';
|
||||
injectTapEventPlugin();
|
||||
@@ -27,6 +28,21 @@ import '../../assets/fonts/RobotoMono/font.css';
|
||||
import './style.css';
|
||||
|
||||
ReactDOM.render(
|
||||
<Application />,
|
||||
<AppContainer>
|
||||
<Application />
|
||||
</AppContainer>,
|
||||
document.querySelector('#container')
|
||||
);
|
||||
|
||||
if (module.hot) {
|
||||
module.hot.accept('./signaturereg/Application/index.js', () => {
|
||||
require('./signaturereg/Application/index.js');
|
||||
|
||||
ReactDOM.render(
|
||||
<AppContainer>
|
||||
<Application />
|
||||
</AppContainer>,
|
||||
document.querySelector('#container')
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
import ReactDOM from 'react-dom';
|
||||
import React from 'react';
|
||||
import { Redirect, Router, Route, hashHistory } from 'react-router';
|
||||
import { AppContainer } from 'react-hot-loader';
|
||||
|
||||
import injectTapEventPlugin from 'react-tap-event-plugin';
|
||||
injectTapEventPlugin();
|
||||
@@ -31,13 +32,37 @@ import '../../assets/fonts/RobotoMono/font.css';
|
||||
import './style.css';
|
||||
|
||||
ReactDOM.render(
|
||||
<Router history={ hashHistory }>
|
||||
<Redirect from='/' to='/overview' />
|
||||
<Route path='/' component={ Application }>
|
||||
<Route path='deploy' component={ Deploy } />
|
||||
<Route path='overview' component={ Overview } />
|
||||
<Route path='transfer' component={ Transfer } />
|
||||
</Route>
|
||||
</Router>,
|
||||
<AppContainer>
|
||||
<Router history={ hashHistory }>
|
||||
<Redirect from='/' to='/overview' />
|
||||
<Route path='/' component={ Application }>
|
||||
<Route path='deploy' component={ Deploy } />
|
||||
<Route path='overview' component={ Overview } />
|
||||
<Route path='transfer' component={ Transfer } />
|
||||
</Route>
|
||||
</Router>
|
||||
</AppContainer>,
|
||||
document.querySelector('#container')
|
||||
);
|
||||
|
||||
if (module.hot) {
|
||||
module.hot.accept('./tokendeploy/Application/index.js', () => {
|
||||
require('./tokendeploy/Application/index.js');
|
||||
require('./tokendeploy/Overview/index.js');
|
||||
require('./tokendeploy/Transfer/index.js');
|
||||
|
||||
ReactDOM.render(
|
||||
<AppContainer>
|
||||
<Router history={ hashHistory }>
|
||||
<Redirect from='/' to='/overview' />
|
||||
<Route path='/' component={ Application }>
|
||||
<Route path='deploy' component={ Deploy } />
|
||||
<Route path='overview' component={ Overview } />
|
||||
<Route path='transfer' component={ Transfer } />
|
||||
</Route>
|
||||
</Router>
|
||||
</AppContainer>,
|
||||
document.querySelector('#container')
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ export function attachInstances () {
|
||||
.all([
|
||||
api.parity.registryAddress(),
|
||||
api.parity.netChain(),
|
||||
api.partiy.netVersion()
|
||||
api.net.version()
|
||||
])
|
||||
.then(([registryAddress, netChain, _netVersion]) => {
|
||||
const registry = api.newContract(abis.registry, registryAddress).instance;
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
import ReactDOM from 'react-dom';
|
||||
import React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import { AppContainer } from 'react-hot-loader';
|
||||
|
||||
import injectTapEventPlugin from 'react-tap-event-plugin';
|
||||
injectTapEventPlugin();
|
||||
@@ -29,10 +30,25 @@ import '../../assets/fonts/RobotoMono/font.css';
|
||||
import './style.css';
|
||||
|
||||
ReactDOM.render(
|
||||
(
|
||||
<AppContainer>
|
||||
<Provider store={ store }>
|
||||
<Container />
|
||||
</Provider>
|
||||
),
|
||||
</AppContainer>,
|
||||
document.querySelector('#container')
|
||||
);
|
||||
|
||||
if (module.hot) {
|
||||
module.hot.accept('./tokenreg/Container.js', () => {
|
||||
require('./tokenreg/Container.js');
|
||||
|
||||
ReactDOM.render(
|
||||
<AppContainer>
|
||||
<Provider store={ store }>
|
||||
<Container />
|
||||
</Provider>
|
||||
</AppContainer>,
|
||||
document.querySelector('#container')
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import React, { Component, PropTypes } from 'react';
|
||||
|
||||
import { Chip } from 'material-ui';
|
||||
|
||||
import IdentityIcon from '../IdentityIcon' ;
|
||||
import IdentityIcon from '../IdentityIcon';
|
||||
|
||||
import styles from './chip.css';
|
||||
|
||||
|
||||
@@ -102,3 +102,4 @@ if (module.hot) {
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -120,20 +120,22 @@ export default class HardwareStore {
|
||||
});
|
||||
}
|
||||
|
||||
createAccountInfo (entry) {
|
||||
createAccountInfo (entry, original = {}) {
|
||||
const { address, manufacturer, name } = entry;
|
||||
|
||||
return Promise
|
||||
.all([
|
||||
this._api.parity.setAccountName(address, name),
|
||||
this._api.parity.setAccountMeta(address, {
|
||||
original.name
|
||||
? Promise.resolve(true)
|
||||
: this._api.parity.setAccountName(address, name),
|
||||
this._api.parity.setAccountMeta(address, Object.assign({
|
||||
description: `${manufacturer} ${name}`,
|
||||
hardware: {
|
||||
manufacturer
|
||||
},
|
||||
tags: ['hardware'],
|
||||
timestamp: Date.now()
|
||||
})
|
||||
}, original.meta || {}))
|
||||
])
|
||||
.catch((error) => {
|
||||
console.warn('HardwareStore::createEntry', error);
|
||||
|
||||
@@ -130,25 +130,58 @@ describe('mobx/HardwareStore', () => {
|
||||
|
||||
describe('operations', () => {
|
||||
describe('createAccountInfo', () => {
|
||||
beforeEach(() => {
|
||||
return store.createAccountInfo({
|
||||
address: 'testAddr',
|
||||
manufacturer: 'testMfg',
|
||||
name: 'testName'
|
||||
describe('when not existing', () => {
|
||||
beforeEach(() => {
|
||||
return store.createAccountInfo({
|
||||
address: 'testAddr',
|
||||
manufacturer: 'testMfg',
|
||||
name: 'testName'
|
||||
});
|
||||
});
|
||||
|
||||
it('calls into parity_setAccountName', () => {
|
||||
expect(api.parity.setAccountName).to.have.been.calledWith('testAddr', 'testName');
|
||||
});
|
||||
|
||||
it('calls into parity_setAccountMeta', () => {
|
||||
expect(api.parity.setAccountMeta).to.have.been.calledWith('testAddr', sinon.match({
|
||||
description: 'testMfg testName',
|
||||
hardware: {
|
||||
manufacturer: 'testMfg'
|
||||
},
|
||||
tags: ['hardware']
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
it('calls into parity_setAccountName', () => {
|
||||
expect(api.parity.setAccountName).to.have.been.calledWith('testAddr', 'testName');
|
||||
});
|
||||
describe('when already exists', () => {
|
||||
beforeEach(() => {
|
||||
return store.createAccountInfo({
|
||||
address: 'testAddr',
|
||||
manufacturer: 'testMfg',
|
||||
name: 'testName'
|
||||
}, {
|
||||
name: 'originalName',
|
||||
meta: {
|
||||
description: 'originalDescription',
|
||||
tags: ['tagA', 'tagB']
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('calls into parity_setAccountMeta', () => {
|
||||
expect(api.parity.setAccountMeta).to.have.been.calledWith('testAddr', sinon.match({
|
||||
description: 'testMfg testName',
|
||||
hardware: {
|
||||
manufacturer: 'testMfg'
|
||||
}
|
||||
}));
|
||||
it('does not call into parity_setAccountName', () => {
|
||||
expect(api.parity.setAccountName).not.to.have.been.called;
|
||||
});
|
||||
|
||||
it('calls into parity_setAccountMeta', () => {
|
||||
expect(api.parity.setAccountMeta).to.have.been.calledWith('testAddr', sinon.match({
|
||||
description: 'originalDescription',
|
||||
hardware: {
|
||||
manufacturer: 'testMfg'
|
||||
},
|
||||
tags: ['tagA', 'tagB']
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { omitBy } from 'lodash';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
@@ -106,9 +105,6 @@ export default class WalletDetails extends Component {
|
||||
renderMultisigDetails () {
|
||||
const { accounts, wallet, errors } = this.props;
|
||||
|
||||
// Wallets cannot create contracts
|
||||
const _accounts = omitBy(accounts, (a) => a.wallet);
|
||||
|
||||
return (
|
||||
<Form>
|
||||
<Input
|
||||
@@ -148,7 +144,7 @@ export default class WalletDetails extends Component {
|
||||
/>
|
||||
|
||||
<AddressSelect
|
||||
accounts={ _accounts }
|
||||
accounts={ accounts }
|
||||
error={ errors.account }
|
||||
hint={
|
||||
<FormattedMessage
|
||||
|
||||
@@ -22,10 +22,11 @@ import Contract from '~/api/contract';
|
||||
import { ERROR_CODES } from '~/api/transport/error';
|
||||
import Contracts from '~/contracts';
|
||||
import { wallet as walletAbi } from '~/contracts/abi';
|
||||
import { wallet as walletCode, walletLibraryRegKey, fullWalletCode } from '~/contracts/code/wallet';
|
||||
import { wallet as walletCode, walletLibrary as walletLibraryCode, walletLibraryRegKey, fullWalletCode } from '~/contracts/code/wallet';
|
||||
|
||||
import { validateUint, validateAddress, validateName } from '~/util/validation';
|
||||
import { toWei } from '~/api/util/wei';
|
||||
import { deploy } from '~/util/tx';
|
||||
import WalletsUtils from '~/util/wallets';
|
||||
|
||||
const STEPS = {
|
||||
@@ -179,6 +180,8 @@ export default class CreateWalletStore {
|
||||
this.wallet.owners = owners;
|
||||
this.wallet.required = require.toNumber();
|
||||
this.wallet.dailylimit = dailylimit.limit;
|
||||
|
||||
this.wallet = this.getWalletWithMeta(this.wallet);
|
||||
});
|
||||
|
||||
return this.addWallet(this.wallet);
|
||||
@@ -202,21 +205,51 @@ export default class CreateWalletStore {
|
||||
return null; // exception when registry is not available
|
||||
})
|
||||
.then((address) => {
|
||||
const walletLibraryAddress = (address || '').replace(/^0x/, '').toLowerCase();
|
||||
const code = walletLibraryAddress.length && !/^0+$/.test(walletLibraryAddress)
|
||||
? walletCode.replace(/(_)+WalletLibrary(_)+/g, walletLibraryAddress)
|
||||
: fullWalletCode;
|
||||
if (!address || /^(0x)?0*$/.test(address)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check that it's actually the expected code
|
||||
return this.api.eth
|
||||
.getCode(address)
|
||||
.then((code) => {
|
||||
const strippedCode = code.replace(/^0x/, '');
|
||||
|
||||
// The actual deployed code is included in the wallet
|
||||
// library code (which might have some more data)
|
||||
if (walletLibraryCode.indexOf(strippedCode) >= 0) {
|
||||
return address;
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
})
|
||||
.then((address) => {
|
||||
let code = fullWalletCode;
|
||||
|
||||
if (address) {
|
||||
const walletLibraryAddress = address.replace(/^0x/, '').toLowerCase();
|
||||
|
||||
code = walletCode.replace(/(_)+WalletLibrary(_)+/g, walletLibraryAddress);
|
||||
} else {
|
||||
console.warn('wallet library has not been found in the registry');
|
||||
}
|
||||
|
||||
const options = {
|
||||
data: code,
|
||||
from: account
|
||||
};
|
||||
|
||||
return this.api
|
||||
.newContract(walletAbi)
|
||||
.deploy(options, [ owners, required, daylimit ], this.onDeploymentState);
|
||||
const contract = this.api.newContract(walletAbi);
|
||||
|
||||
this.wallet = this.getWalletWithMeta(this.wallet);
|
||||
return deploy(contract, options, [ owners, required, daylimit ], this.wallet.metadata, this.onDeploymentState);
|
||||
})
|
||||
.then((address) => {
|
||||
if (!address || /^(0x)?0*$/.test(address)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.deployed = true;
|
||||
this.wallet.address = address;
|
||||
return this.addWallet(this.wallet);
|
||||
@@ -233,26 +266,37 @@ export default class CreateWalletStore {
|
||||
}
|
||||
|
||||
@action addWallet = (wallet) => {
|
||||
const { address, name, description } = wallet;
|
||||
const { address, name, metadata } = wallet;
|
||||
|
||||
return Promise
|
||||
.all([
|
||||
this.api.parity.setAccountName(address, name),
|
||||
this.api.parity.setAccountMeta(address, {
|
||||
abi: walletAbi,
|
||||
wallet: true,
|
||||
timestamp: Date.now(),
|
||||
deleted: false,
|
||||
description,
|
||||
name,
|
||||
tags: ['wallet']
|
||||
})
|
||||
this.api.parity.setAccountMeta(address, metadata)
|
||||
])
|
||||
.then(() => {
|
||||
this.step = 'INFO';
|
||||
});
|
||||
}
|
||||
|
||||
getWalletWithMeta = (wallet) => {
|
||||
const { name, description } = wallet;
|
||||
|
||||
const metadata = {
|
||||
abi: walletAbi,
|
||||
wallet: true,
|
||||
timestamp: Date.now(),
|
||||
deleted: false,
|
||||
tags: [ 'wallet' ],
|
||||
description,
|
||||
name
|
||||
};
|
||||
|
||||
return {
|
||||
...wallet,
|
||||
metadata
|
||||
};
|
||||
}
|
||||
|
||||
onDeploymentState = (error, data) => {
|
||||
if (error) {
|
||||
return console.error('createWallet::onDeploymentState', error);
|
||||
@@ -298,6 +342,15 @@ export default class CreateWalletStore {
|
||||
);
|
||||
return;
|
||||
|
||||
case 'confirmationNeeded':
|
||||
this.deployState = (
|
||||
<FormattedMessage
|
||||
id='createWallet.states.confirmationNeeded'
|
||||
defaultMessage='The contract deployment needs confirmations from other owners of the Wallet'
|
||||
/>
|
||||
);
|
||||
return;
|
||||
|
||||
case 'completed':
|
||||
this.deployState = (
|
||||
<FormattedMessage
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { pick, omitBy } from 'lodash';
|
||||
import { pick } from 'lodash';
|
||||
import { observer } from 'mobx-react';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
@@ -23,6 +23,7 @@ import { connect } from 'react-redux';
|
||||
import { BusyStep, Button, CompletedStep, CopyToClipboard, GasPriceEditor, IdentityIcon, Portal, TxHash, Warning } from '~/ui';
|
||||
import { CancelIcon, DoneIcon } from '~/ui/Icons';
|
||||
import { ERRORS, validateAbi, validateCode, validateName } from '~/util/validation';
|
||||
import { deploy, deployEstimateGas } from '~/util/tx';
|
||||
|
||||
import DetailsStep from './DetailsStep';
|
||||
import ParametersStep from './ParametersStep';
|
||||
@@ -73,7 +74,7 @@ class DeployContract extends Component {
|
||||
static contextTypes = {
|
||||
api: PropTypes.object.isRequired,
|
||||
store: PropTypes.object.isRequired
|
||||
}
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
accounts: PropTypes.object.isRequired,
|
||||
@@ -141,12 +142,13 @@ class DeployContract extends Component {
|
||||
render () {
|
||||
const { step, deployError, rejected, inputs } = this.state;
|
||||
|
||||
const realStep = Object.keys(STEPS).findIndex((k) => k === step);
|
||||
const realSteps = deployError || rejected
|
||||
? null
|
||||
: Object.keys(STEPS)
|
||||
.filter((k) => k !== 'CONTRACT_PARAMETERS' || inputs.length > 0)
|
||||
.map((k) => STEPS[k]);
|
||||
const realStepKeys = deployError || rejected
|
||||
? []
|
||||
: Object.keys(STEPS).filter((k) => k !== 'CONTRACT_PARAMETERS' || inputs.length > 0);
|
||||
const realStep = realStepKeys.findIndex((k) => k === step);
|
||||
const realSteps = realStepKeys.length
|
||||
? realStepKeys.map((k) => STEPS[k])
|
||||
: null;
|
||||
|
||||
const title = realSteps
|
||||
? null
|
||||
@@ -422,9 +424,9 @@ class DeployContract extends Component {
|
||||
from: fromAddress
|
||||
};
|
||||
|
||||
api
|
||||
.newContract(abiParsed)
|
||||
.deployEstimateGas(options, params)
|
||||
const contract = api.newContract(abiParsed);
|
||||
|
||||
deployEstimateGas(contract, options, params)
|
||||
.then(([gasEst, gas]) => {
|
||||
this.gasStore.setEstimated(gasEst.toFixed(0));
|
||||
this.gasStore.setGas(gas.toFixed(0));
|
||||
@@ -490,6 +492,17 @@ class DeployContract extends Component {
|
||||
const { api, store } = this.context;
|
||||
const { source } = this.props;
|
||||
const { abiParsed, code, description, name, params, fromAddress } = this.state;
|
||||
|
||||
const metadata = {
|
||||
abi: abiParsed,
|
||||
contract: true,
|
||||
deleted: false,
|
||||
timestamp: Date.now(),
|
||||
name,
|
||||
description,
|
||||
source
|
||||
};
|
||||
|
||||
const options = {
|
||||
data: code,
|
||||
from: fromAddress
|
||||
@@ -499,28 +512,25 @@ class DeployContract extends Component {
|
||||
|
||||
const contract = api.newContract(abiParsed);
|
||||
|
||||
contract
|
||||
.deploy(options, params, this.onDeploymentState)
|
||||
deploy(contract, options, params, metadata, this.onDeploymentState)
|
||||
.then((address) => {
|
||||
const blockNumber = contract._receipt
|
||||
// No contract address given, might need some confirmations
|
||||
// from the wallet owners...
|
||||
if (!address || /^(0x)?0*$/.test(address)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
metadata.blockNumber = contract._receipt
|
||||
? contract.receipt.blockNumber.toNumber()
|
||||
: null;
|
||||
|
||||
return Promise.all([
|
||||
api.parity.setAccountName(address, name),
|
||||
api.parity.setAccountMeta(address, {
|
||||
abi: abiParsed,
|
||||
contract: true,
|
||||
timestamp: Date.now(),
|
||||
deleted: false,
|
||||
blockNumber,
|
||||
description,
|
||||
source
|
||||
})
|
||||
api.parity.setAccountMeta(address, metadata)
|
||||
])
|
||||
.then(() => {
|
||||
console.log(`contract deployed at ${address}`);
|
||||
this.setState({ step: 'DEPLOYMENT', address });
|
||||
this.setState({ step: 'COMPLETED', address });
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
@@ -589,6 +599,17 @@ class DeployContract extends Component {
|
||||
});
|
||||
return;
|
||||
|
||||
case 'confirmationNeeded':
|
||||
this.setState({
|
||||
deployState: (
|
||||
<FormattedMessage
|
||||
id='deployContract.state.confirmationNeeded'
|
||||
defaultMessage='The operation needs confirmations from the other owners of the contract'
|
||||
/>
|
||||
)
|
||||
});
|
||||
return;
|
||||
|
||||
case 'completed':
|
||||
this.setState({
|
||||
deployState: (
|
||||
@@ -614,17 +635,14 @@ class DeployContract extends Component {
|
||||
function mapStateToProps (initState, initProps) {
|
||||
const { accounts } = initProps;
|
||||
|
||||
// Skip Wallet accounts : they can't create Contracts
|
||||
const _accounts = omitBy(accounts, (a) => a.wallet);
|
||||
|
||||
const fromAddresses = Object.keys(_accounts);
|
||||
const fromAddresses = Object.keys(accounts);
|
||||
|
||||
return (state) => {
|
||||
const balances = pick(state.balances.balances, fromAddresses);
|
||||
const { gasLimit } = state.nodeStatus;
|
||||
|
||||
return {
|
||||
accounts: _accounts,
|
||||
accounts,
|
||||
balances,
|
||||
gasLimit
|
||||
};
|
||||
|
||||
162
js/src/modals/Faucet/faucet.js
Normal file
162
js/src/modals/Faucet/faucet.js
Normal file
@@ -0,0 +1,162 @@
|
||||
// Copyright 2015-2017 Parity Technologies (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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { observer } from 'mobx-react';
|
||||
import React, { Component, PropTypes } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { txLink } from '~/3rdparty/etherscan/links';
|
||||
import { Button, ModalBox, Portal, ShortenedHash } from '~/ui';
|
||||
import { CloseIcon, DialIcon, DoneIcon, ErrorIcon, SendIcon } from '~/ui/Icons';
|
||||
|
||||
import Store from './store';
|
||||
|
||||
@observer
|
||||
export default class Faucet extends Component {
|
||||
static propTypes = {
|
||||
address: PropTypes.string.isRequired,
|
||||
netVersion: PropTypes.string.isRequired,
|
||||
onClose: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
store = new Store(this.props.netVersion, this.props.address);
|
||||
|
||||
render () {
|
||||
const { error, isBusy, isCompleted } = this.store;
|
||||
|
||||
let icon = <DialIcon />;
|
||||
|
||||
if (isCompleted) {
|
||||
icon = error
|
||||
? <ErrorIcon />
|
||||
: <DoneIcon />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Portal
|
||||
buttons={ this.renderActions() }
|
||||
busy={ isBusy }
|
||||
isSmallModal
|
||||
onClose={ this.onClose }
|
||||
open
|
||||
title={
|
||||
<FormattedMessage
|
||||
id='faucet.title'
|
||||
defaultMessage='Kovan ETH Faucet'
|
||||
/>
|
||||
}
|
||||
>
|
||||
<ModalBox
|
||||
icon={ icon }
|
||||
summary={
|
||||
isCompleted
|
||||
? this.renderSummaryDone()
|
||||
: this.renderSummaryRequest()
|
||||
}
|
||||
/>
|
||||
</Portal>
|
||||
);
|
||||
}
|
||||
|
||||
renderActions = () => {
|
||||
const { canTransact, isBusy, isCompleted } = this.store;
|
||||
|
||||
return isCompleted || isBusy
|
||||
? (
|
||||
<Button
|
||||
disabled={ isBusy }
|
||||
icon={ <DoneIcon /> }
|
||||
key='done'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='faucet.buttons.done'
|
||||
defaultMessage='close'
|
||||
/>
|
||||
}
|
||||
onClick={ this.onClose }
|
||||
/>
|
||||
)
|
||||
: [
|
||||
<Button
|
||||
icon={ <CloseIcon /> }
|
||||
key='close'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='faucet.buttons.close'
|
||||
defaultMessage='close'
|
||||
/>
|
||||
}
|
||||
onClick={ this.onClose }
|
||||
/>,
|
||||
<Button
|
||||
disabled={ !canTransact }
|
||||
icon={ <SendIcon /> }
|
||||
key='request'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='faucet.buttons.request'
|
||||
defaultMessage='request'
|
||||
/>
|
||||
}
|
||||
onClick={ this.onExecute }
|
||||
/>
|
||||
];
|
||||
}
|
||||
|
||||
renderSummaryDone () {
|
||||
const { error, responseText, responseTxHash } = this.store;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<FormattedMessage
|
||||
id='faucet.summary.done'
|
||||
defaultMessage='Your Kovan ETH has been requested from the faucet which responded with -'
|
||||
/>
|
||||
{
|
||||
error
|
||||
? (
|
||||
<p>{ error }</p>
|
||||
)
|
||||
: (
|
||||
<p>
|
||||
<span>{ responseText } </span>
|
||||
<a href={ txLink(responseTxHash, false, '42') } target='_blank'>
|
||||
<ShortenedHash data={ responseTxHash } />
|
||||
</a>
|
||||
</p>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderSummaryRequest () {
|
||||
return (
|
||||
<FormattedMessage
|
||||
id='faucet.summary.info'
|
||||
defaultMessage='To request a deposit of Kovan ETH to this address, you need to ensure that the address is sms-verified on the mainnet. Once executed the faucet will deposit Kovan ETH into the current account.'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
onClose = () => {
|
||||
this.props.onClose();
|
||||
}
|
||||
|
||||
onExecute = () => {
|
||||
return this.store.makeItRain();
|
||||
}
|
||||
}
|
||||
17
js/src/modals/Faucet/index.js
Normal file
17
js/src/modals/Faucet/index.js
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright 2015-2017 Parity Technologies (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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
export default from './faucet';
|
||||
126
js/src/modals/Faucet/store.js
Normal file
126
js/src/modals/Faucet/store.js
Normal file
@@ -0,0 +1,126 @@
|
||||
// Copyright 2015-2017 Parity Technologies (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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { action, computed, observable, transaction } from 'mobx';
|
||||
import apiutil from '~/api/util';
|
||||
|
||||
const ENDPOINT = 'http://faucet.kovan.network/api/';
|
||||
|
||||
export default class Store {
|
||||
@observable addressReceive = null;
|
||||
@observable addressVerified = null;
|
||||
@observable error = null;
|
||||
@observable responseText = null;
|
||||
@observable responseTxHash = null;
|
||||
@observable isBusy = false;
|
||||
@observable isCompleted = false;
|
||||
@observable isDestination = false;
|
||||
@observable isDone = false;
|
||||
|
||||
constructor (netVersion, address) {
|
||||
transaction(() => {
|
||||
this.setDestination(netVersion === '42');
|
||||
|
||||
this.setAddressReceive(address);
|
||||
this.setAddressVerified(address);
|
||||
});
|
||||
}
|
||||
|
||||
@computed get canTransact () {
|
||||
return !this.isBusy && this.addressReceiveValid && this.addressVerifiedValid;
|
||||
}
|
||||
|
||||
@computed get addressReceiveValid () {
|
||||
return apiutil.isAddressValid(this.addressReceive);
|
||||
}
|
||||
|
||||
@computed get addressVerifiedValid () {
|
||||
return apiutil.isAddressValid(this.addressVerified);
|
||||
}
|
||||
|
||||
@action setAddressReceive = (address) => {
|
||||
this.addressReceive = address;
|
||||
}
|
||||
|
||||
@action setAddressVerified = (address) => {
|
||||
this.addressVerified = address;
|
||||
}
|
||||
|
||||
@action setBusy = (isBusy) => {
|
||||
this.isBusy = isBusy;
|
||||
}
|
||||
|
||||
@action setCompleted = (isCompleted) => {
|
||||
transaction(() => {
|
||||
this.setBusy(false);
|
||||
this.isCompleted = isCompleted;
|
||||
});
|
||||
}
|
||||
|
||||
@action setDestination = (isDestination) => {
|
||||
this.isDestination = isDestination;
|
||||
}
|
||||
|
||||
@action setError = (error) => {
|
||||
if (error.indexOf('not certified') !== -1) {
|
||||
this.error = `${error}. Please ensure that this account is sms certified on the mainnet.`;
|
||||
} else {
|
||||
this.error = error;
|
||||
}
|
||||
}
|
||||
|
||||
@action setResponse = (response) => {
|
||||
this.responseText = response.result;
|
||||
this.responseTxHash = response.tx;
|
||||
}
|
||||
|
||||
makeItRain = () => {
|
||||
this.setBusy(true);
|
||||
|
||||
const options = {
|
||||
method: 'GET',
|
||||
mode: 'cors'
|
||||
};
|
||||
const url = `${ENDPOINT}${this.addressVerified}`;
|
||||
|
||||
return fetch(url, options)
|
||||
.then((response) => {
|
||||
if (!response.ok) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return response.json();
|
||||
})
|
||||
.catch(() => {
|
||||
return null;
|
||||
})
|
||||
.then((response) => {
|
||||
transaction(() => {
|
||||
if (!response || response.error) {
|
||||
this.setError(
|
||||
response
|
||||
? response.error
|
||||
: 'Unable to complete request to the faucet, the server may be unavailable. Please try again later.'
|
||||
);
|
||||
} else {
|
||||
this.setResponse(response);
|
||||
}
|
||||
|
||||
this.setCompleted(true);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,7 @@ export default class FirstRun extends Component {
|
||||
defaultMessage='As part of a new installation, the next few steps will guide you through the process of setting up you Parity instance and your associated accounts. Our aim is to make it as simple as possible and to get you up and running in record-time, so please bear with us. Once completed you will have -'
|
||||
/>
|
||||
</p>
|
||||
<p>
|
||||
<div>
|
||||
<ul>
|
||||
<li>
|
||||
<FormattedMessage
|
||||
@@ -70,7 +70,7 @@ export default class FirstRun extends Component {
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
</p>
|
||||
</div>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id='firstRun.welcome.next'
|
||||
|
||||
@@ -77,7 +77,8 @@
|
||||
}
|
||||
|
||||
.form {
|
||||
box-sizing: border-box;
|
||||
margin-top: 0;
|
||||
padding: 0.75rem 1.5rem 1.5rem 1.5rem;
|
||||
padding: 0.75rem 1.5rem 1.5rem;
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
@@ -401,6 +401,7 @@ class WalletSettings extends Component {
|
||||
const cancelBtn = (
|
||||
<Button
|
||||
icon={ <CancelIcon /> }
|
||||
key='cancelBtn'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='walletSettings.buttons.cancel'
|
||||
@@ -414,6 +415,7 @@ class WalletSettings extends Component {
|
||||
const closeBtn = (
|
||||
<Button
|
||||
icon={ <CancelIcon /> }
|
||||
key='closeBtn'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='walletSettings.buttons.close'
|
||||
@@ -427,6 +429,7 @@ class WalletSettings extends Component {
|
||||
const sendingBtn = (
|
||||
<Button
|
||||
icon={ <DoneIcon /> }
|
||||
key='sendingBtn'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='walletSettings.buttons.sending'
|
||||
@@ -440,6 +443,7 @@ class WalletSettings extends Component {
|
||||
const nextBtn = (
|
||||
<Button
|
||||
icon={ <NextIcon /> }
|
||||
key='nextBtn'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='walletSettings.buttons.next'
|
||||
@@ -454,6 +458,7 @@ class WalletSettings extends Component {
|
||||
const sendBtn = (
|
||||
<Button
|
||||
icon={ <NextIcon /> }
|
||||
key='sendBtn'
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='walletSettings.buttons.send'
|
||||
|
||||
@@ -24,6 +24,7 @@ export DeleteAccount from './DeleteAccount';
|
||||
export DeployContract from './DeployContract';
|
||||
export EditMeta from './EditMeta';
|
||||
export ExecuteContract from './ExecuteContract';
|
||||
export Faucet from './Faucet';
|
||||
export FirstRun from './FirstRun';
|
||||
export LoadContract from './LoadContract';
|
||||
export PasswordManager from './PasswordManager';
|
||||
|
||||
@@ -49,7 +49,7 @@ export default class Personal {
|
||||
.filter((address) => {
|
||||
const account = accountsInfo[address];
|
||||
|
||||
return !account.uuid && account.meta.deleted;
|
||||
return !account.uuid && account.meta && account.meta.deleted;
|
||||
})
|
||||
.map((address) => this._api.parity.removeAddress(address))
|
||||
);
|
||||
|
||||
@@ -26,7 +26,6 @@ import WalletsUtils from '~/util/wallets';
|
||||
import { wallet as WalletAbi } from '~/contracts/abi';
|
||||
|
||||
export function personalAccountsInfo (accountsInfo) {
|
||||
const addresses = [];
|
||||
const accounts = {};
|
||||
const contacts = {};
|
||||
const contracts = {};
|
||||
@@ -35,10 +34,9 @@ export function personalAccountsInfo (accountsInfo) {
|
||||
|
||||
Object.keys(accountsInfo || {})
|
||||
.map((address) => Object.assign({}, accountsInfo[address], { address }))
|
||||
.filter((account) => account.uuid || !account.meta.deleted)
|
||||
.filter((account) => account.meta && (account.uuid || !account.meta.deleted))
|
||||
.forEach((account) => {
|
||||
if (account.uuid) {
|
||||
addresses.push(account.address);
|
||||
accounts[account.address] = account;
|
||||
} else if (account.meta.wallet) {
|
||||
account.wallet = true;
|
||||
@@ -87,18 +85,45 @@ export function personalAccountsInfo (accountsInfo) {
|
||||
return [];
|
||||
})
|
||||
.then((_wallets) => {
|
||||
_wallets.forEach((wallet) => {
|
||||
const owners = wallet.owners.map((o) => o.address);
|
||||
// We want to separate owned wallets and other wallets
|
||||
// However, wallets can be owned by wallets, that can
|
||||
// be owned by an account...
|
||||
let otherWallets = [].concat(_wallets);
|
||||
let prevLength;
|
||||
let nextLength;
|
||||
|
||||
// Owners ∩ Addresses not null : Wallet is owned
|
||||
// by one of the accounts
|
||||
if (intersection(owners, addresses).length > 0) {
|
||||
accounts[wallet.address] = wallet;
|
||||
} else {
|
||||
contacts[wallet.address] = wallet;
|
||||
}
|
||||
// If no more other wallets, or if the size decreased, continue...
|
||||
do {
|
||||
prevLength = otherWallets.length;
|
||||
|
||||
otherWallets = otherWallets
|
||||
.map((wallet) => {
|
||||
const addresses = Object.keys(accounts);
|
||||
const owners = wallet.owners.map((o) => o.address);
|
||||
|
||||
// Owners ∩ Addresses not null : Wallet is owned
|
||||
// by one of the accounts
|
||||
if (intersection(owners, addresses).length > 0) {
|
||||
accounts[wallet.address] = wallet;
|
||||
return false;
|
||||
}
|
||||
|
||||
return wallet;
|
||||
})
|
||||
.filter((wallet) => wallet);
|
||||
|
||||
nextLength = otherWallets.length;
|
||||
} while (nextLength < prevLength);
|
||||
|
||||
// And other wallets to contacts...
|
||||
otherWallets.forEach((wallet) => {
|
||||
contacts[wallet.address] = wallet;
|
||||
});
|
||||
|
||||
// Cache the _real_ accounts for
|
||||
// WalletsUtils (used for sending transactions)
|
||||
WalletsUtils.cacheAccounts(accounts);
|
||||
|
||||
dispatch(_personalAccountsInfo({
|
||||
accountsInfo,
|
||||
accounts,
|
||||
|
||||
@@ -431,22 +431,7 @@ function parseLogs (logs) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { wallet } = getState();
|
||||
const { contract } = wallet;
|
||||
const walletInstance = contract.instance;
|
||||
|
||||
const signatures = {
|
||||
OwnerChanged: toHex(walletInstance.OwnerChanged.signature),
|
||||
OwnerAdded: toHex(walletInstance.OwnerAdded.signature),
|
||||
OwnerRemoved: toHex(walletInstance.OwnerRemoved.signature),
|
||||
RequirementChanged: toHex(walletInstance.RequirementChanged.signature),
|
||||
Confirmation: toHex(walletInstance.Confirmation.signature),
|
||||
Revoke: toHex(walletInstance.Revoke.signature),
|
||||
Deposit: toHex(walletInstance.Deposit.signature),
|
||||
SingleTransact: toHex(walletInstance.SingleTransact.signature),
|
||||
MultiTransact: toHex(walletInstance.MultiTransact.signature),
|
||||
ConfirmationNeeded: toHex(walletInstance.ConfirmationNeeded.signature)
|
||||
};
|
||||
const WalletSignatures = WalletsUtils.getWalletSignatures();
|
||||
|
||||
const updates = {};
|
||||
|
||||
@@ -459,25 +444,25 @@ function parseLogs (logs) {
|
||||
};
|
||||
|
||||
switch (eventSignature) {
|
||||
case signatures.OwnerChanged:
|
||||
case signatures.OwnerAdded:
|
||||
case signatures.OwnerRemoved:
|
||||
case WalletSignatures.OwnerChanged:
|
||||
case WalletSignatures.OwnerAdded:
|
||||
case WalletSignatures.OwnerRemoved:
|
||||
updates[address] = {
|
||||
...prev,
|
||||
[ UPDATE_OWNERS ]: true
|
||||
};
|
||||
return;
|
||||
|
||||
case signatures.RequirementChanged:
|
||||
case WalletSignatures.RequirementChanged:
|
||||
updates[address] = {
|
||||
...prev,
|
||||
[ UPDATE_REQUIRE ]: true
|
||||
};
|
||||
return;
|
||||
|
||||
case signatures.ConfirmationNeeded:
|
||||
case signatures.Confirmation:
|
||||
case signatures.Revoke:
|
||||
case WalletSignatures.ConfirmationNeeded:
|
||||
case WalletSignatures.Confirmation:
|
||||
case WalletSignatures.Revoke:
|
||||
const operation = bytesToHex(log.params.operation.value);
|
||||
|
||||
updates[address] = {
|
||||
@@ -489,9 +474,11 @@ function parseLogs (logs) {
|
||||
|
||||
return;
|
||||
|
||||
case signatures.Deposit:
|
||||
case signatures.SingleTransact:
|
||||
case signatures.MultiTransact:
|
||||
case WalletSignatures.Deposit:
|
||||
case WalletSignatures.SingleTransact:
|
||||
case WalletSignatures.MultiTransact:
|
||||
case WalletSignatures.Old.SingleTransact:
|
||||
case WalletSignatures.Old.MultiTransact:
|
||||
updates[address] = {
|
||||
...prev,
|
||||
[ UPDATE_TRANSACTIONS ]: true
|
||||
|
||||
@@ -92,6 +92,26 @@ export default class SecureApi extends Api {
|
||||
return this._transport.token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the current API with the given values
|
||||
* (`signerPort`, `dappsInterface`, `dappsPort`, ...)
|
||||
*/
|
||||
configure (configuration) {
|
||||
const { dappsInterface, dappsPort, signerPort } = configuration;
|
||||
|
||||
if (dappsInterface) {
|
||||
this._dappsInterface = dappsInterface;
|
||||
}
|
||||
|
||||
if (dappsPort) {
|
||||
this._dappsPort = dappsPort;
|
||||
}
|
||||
|
||||
if (signerPort) {
|
||||
this._signerPort = signerPort;
|
||||
}
|
||||
}
|
||||
|
||||
connect () {
|
||||
if (this._isConnecting) {
|
||||
return;
|
||||
|
||||
@@ -110,12 +110,6 @@
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-size: 0.9em;
|
||||
|
||||
.address {
|
||||
&:hover {
|
||||
cursor: text;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.accountName {
|
||||
|
||||
@@ -28,15 +28,15 @@ import styles from './accountCard.css';
|
||||
export default class AccountCard extends Component {
|
||||
static propTypes = {
|
||||
account: PropTypes.object.isRequired,
|
||||
allowAddressClick: PropTypes.bool,
|
||||
balance: PropTypes.object,
|
||||
className: PropTypes.string,
|
||||
disableAddressClick: PropTypes.bool,
|
||||
onClick: PropTypes.func,
|
||||
onFocus: PropTypes.func
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
allowAddressClick: false
|
||||
disableAddressClick: false
|
||||
};
|
||||
|
||||
state = {
|
||||
@@ -87,7 +87,6 @@ export default class AccountCard extends Component {
|
||||
balance={ balance }
|
||||
className={ styles.balance }
|
||||
showOnlyEth
|
||||
showZeroValues
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -138,14 +137,14 @@ export default class AccountCard extends Component {
|
||||
}
|
||||
|
||||
handleAddressClick = (event) => {
|
||||
const { allowAddressClick } = this.props;
|
||||
const { disableAddressClick } = this.props;
|
||||
|
||||
// Don't stop the event if address click is allowed
|
||||
if (allowAddressClick) {
|
||||
return this.onClick(event);
|
||||
// Stop the event if address click is disallowed
|
||||
if (disableAddressClick) {
|
||||
return this.preventEvent(event);
|
||||
}
|
||||
|
||||
return this.preventEvent(event);
|
||||
return this.onClick(event);
|
||||
}
|
||||
|
||||
handleKeyDown = (event) => {
|
||||
|
||||
@@ -74,9 +74,8 @@ describe('ui/AccountCard', () => {
|
||||
expect(balance.length).to.equal(1);
|
||||
});
|
||||
|
||||
it('sets showOnlyEth & showZeroValues', () => {
|
||||
it('sets showOnlyEth', () => {
|
||||
expect(balance.props().showOnlyEth).to.be.true;
|
||||
expect(balance.props().showZeroValues).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -20,11 +20,16 @@
|
||||
flex-wrap: wrap;
|
||||
margin: 0.75em 0 0 0;
|
||||
vertical-align: top;
|
||||
|
||||
&:not(.full) {
|
||||
height: 2.5em;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.balance,
|
||||
.empty {
|
||||
margin: 0.75em 0.5em 0 0;
|
||||
margin: 0.75em 0.5em 4px 0;
|
||||
}
|
||||
|
||||
.empty {
|
||||
@@ -37,28 +42,35 @@
|
||||
}
|
||||
|
||||
.balance {
|
||||
background: rgba(255, 255, 255, 0.07);
|
||||
align-items: center;
|
||||
border-radius: 16px;
|
||||
display: flex;
|
||||
max-height: 24px;
|
||||
max-width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.balance img {
|
||||
height: 32px;
|
||||
margin: -4px 1em 0 0;
|
||||
width: 32px;
|
||||
}
|
||||
&.full {
|
||||
background: rgba(255, 255, 255, 0.07);
|
||||
|
||||
.balanceValue {
|
||||
margin: 0 0.5em 0 0;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
img {
|
||||
margin-right: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.balanceTag {
|
||||
font-size: 0.85em;
|
||||
padding-right: 0.75rem;
|
||||
img {
|
||||
height: 32px;
|
||||
margin-top: -4px;
|
||||
width: 32px;
|
||||
}
|
||||
|
||||
.tag {
|
||||
padding-right: 0.75rem;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
.value {
|
||||
margin: 0 0.5em 0 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user