Merge branch 'master' into tracing

This commit is contained in:
debris 2016-04-11 11:35:07 +02:00
commit 610251fdf7
75 changed files with 1920 additions and 831 deletions

170
Cargo.lock generated
View File

@ -11,6 +11,7 @@ dependencies = [
"ethcore-devtools 1.1.0",
"ethcore-rpc 1.1.0",
"ethcore-util 1.1.0",
"ethcore-webapp 1.1.0",
"ethminer 1.1.0",
"ethsync 1.1.0",
"fdlimit 0.1.0",
@ -107,6 +108,14 @@ dependencies = [
"unicode-normalization 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "conduit-mime-types"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"rustc-serialize 0.3.18 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "cookie"
version = "0.1.21"
@ -185,6 +194,15 @@ dependencies = [
"regex 0.1.61 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "error"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"traitobject 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"typeable 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "eth-secp256k1"
version = "0.5.4"
@ -246,7 +264,7 @@ dependencies = [
"ethminer 1.1.0",
"ethsync 1.1.0",
"jsonrpc-core 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"jsonrpc-http-server 3.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"jsonrpc-http-server 5.0.0 (git+https://github.com/debris/jsonrpc-http-server.git)",
"log 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-serialize 0.3.18 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -291,6 +309,23 @@ dependencies = [
"vergen 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "ethcore-webapp"
version = "1.1.0"
dependencies = [
"clippy 0.0.61 (registry+https://github.com/rust-lang/crates.io-index)",
"ethcore-rpc 1.1.0",
"ethcore-util 1.1.0",
"hyper 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"iron 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"jsonrpc-core 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"jsonrpc-http-server 4.0.0 (git+https://github.com/tomusdrw/jsonrpc-http-server.git?branch=old-hyper)",
"log 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"parity-status 0.1.5 (git+https://github.com/tomusdrw/parity-status.git)",
"parity-wallet 0.1.0 (git+https://github.com/tomusdrw/parity-wallet.git)",
"parity-webapp 0.1.0 (git+https://github.com/tomusdrw/parity-webapp.git)",
]
[[package]]
name = "ethjson"
version = "0.1.0"
@ -415,6 +450,27 @@ dependencies = [
"url 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "hyper"
version = "0.9.0-mio"
source = "git+https://github.com/hyperium/hyper?branch=mio#d55a70dc56dac1f0f03bc4c3a83db0314d48e69a"
dependencies = [
"cookie 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"httparse 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"mime 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"mio 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rotor 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-serialize 0.3.18 (registry+https://github.com/rust-lang/crates.io-index)",
"time 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)",
"traitobject 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"typeable 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"unicase 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"url 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)",
"vecio 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "igd"
version = "0.4.2"
@ -427,6 +483,23 @@ dependencies = [
"xmltree 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "iron"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"conduit-mime-types 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
"error 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"hyper 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"modifier 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"num_cpus 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"plugin 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
"typemap 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"url 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "itertools"
version = "0.4.11"
@ -453,14 +526,24 @@ dependencies = [
[[package]]
name = "jsonrpc-http-server"
version = "3.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
version = "4.0.0"
source = "git+https://github.com/tomusdrw/jsonrpc-http-server.git?branch=old-hyper#46bd4e7cf8352e0efc940cf76d3dff99f1a3da15"
dependencies = [
"hyper 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"jsonrpc-core 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"unicase 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "jsonrpc-http-server"
version = "5.0.0"
source = "git+https://github.com/debris/jsonrpc-http-server.git#76fa443982b40665721fe6b1ece42fc0a53be996"
dependencies = [
"hyper 0.9.0-mio (git+https://github.com/hyperium/hyper?branch=mio)",
"jsonrpc-core 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"unicase 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "kernel32-sys"
version = "0.2.1"
@ -573,6 +656,11 @@ dependencies = [
"ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "modifier"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "net2"
version = "0.2.23"
@ -637,6 +725,35 @@ name = "odds"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "parity-status"
version = "0.1.5"
source = "git+https://github.com/tomusdrw/parity-status.git#6a075228e9248055a37c55dec41461856f5a9f19"
dependencies = [
"parity-webapp 0.1.0 (git+https://github.com/tomusdrw/parity-webapp.git)",
]
[[package]]
name = "parity-wallet"
version = "0.1.0"
source = "git+https://github.com/tomusdrw/parity-wallet.git#9b0253f5cb88b31417450ca8be708cab2e437dfc"
dependencies = [
"parity-webapp 0.1.0 (git+https://github.com/tomusdrw/parity-webapp.git)",
]
[[package]]
name = "parity-webapp"
version = "0.1.0"
source = "git+https://github.com/tomusdrw/parity-webapp.git#a24297256bae0ae0712c6478cd1ad681828b3800"
[[package]]
name = "plugin"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"typemap 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "primal"
version = "0.2.3"
@ -696,6 +813,11 @@ dependencies = [
"syntex_syntax 0.30.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "quick-error"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "quine-mc_cluskey"
version = "0.2.2"
@ -745,6 +867,18 @@ dependencies = [
"librocksdb-sys 0.2.3 (git+https://github.com/arkpar/rust-rocksdb.git)",
]
[[package]]
name = "rotor"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"log 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
"mio 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
"quick-error 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"time 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)",
"void 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rpassword"
version = "0.1.3"
@ -938,6 +1072,14 @@ name = "typeable"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "typemap"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"unsafe-any 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "unicase"
version = "1.4.0"
@ -964,6 +1106,14 @@ name = "unicode-xid"
version = "0.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "unsafe-any"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"traitobject 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "url"
version = "0.2.38"
@ -1000,6 +1150,15 @@ dependencies = [
"rustc-serialize 0.3.18 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "vecio"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
"ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "vergen"
version = "0.1.0"
@ -1009,6 +1168,11 @@ dependencies = [
"time 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "void"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi"
version = "0.2.6"

View File

@ -28,15 +28,19 @@ ethsync = { path = "sync" }
ethminer = { path = "miner" }
ethcore-devtools = { path = "devtools" }
ethcore-rpc = { path = "rpc", optional = true }
ethcore-webapp = { path = "webapp", optional = true }
[dependencies.hyper]
version = "0.8"
default-features = false
[features]
default = ["rpc"]
default = ["rpc", "webapp"]
rpc = ["ethcore-rpc"]
dev = ["clippy", "ethcore/dev", "ethcore-util/dev", "ethsync/dev", "ethcore-rpc/dev", "ethminer/dev"]
webapp = ["ethcore-webapp"]
dev = ["clippy", "ethcore/dev", "ethcore-util/dev", "ethsync/dev", "ethcore-rpc/dev", "ethminer/dev",
"ethcore-webapp/dev"]
travis-beta = ["ethcore/json-tests"]
travis-nightly = ["ethcore/json-tests", "dev"]

2
cov.sh
View File

@ -23,6 +23,7 @@ cargo test \
-p ethcore-rpc \
-p parity \
-p ethminer \
-p ethcore-webapp \
--no-run || exit $?
rm -rf target/coverage
mkdir -p target/coverage
@ -33,5 +34,6 @@ kcov --exclude-pattern $EXCLUDE --include-pattern src --verify target/coverage t
kcov --exclude-pattern $EXCLUDE --include-pattern src --verify target/coverage target/debug/deps/ethcore_util-*
kcov --exclude-pattern $EXCLUDE --include-pattern src --verify target/coverage target/debug/deps/ethsync-*
kcov --exclude-pattern $EXCLUDE --include-pattern src --verify target/coverage target/debug/deps/ethcore_rpc-*
kcov --exclude-pattern $EXCLUDE --include-pattern src --verify target/coverage target/debug/deps/ethcore_webapp-*
kcov --exclude-pattern $EXCLUDE --include-pattern src --verify target/coverage target/debug/deps/ethminer-*
xdg-open target/coverage/index.html

1
doc.sh
View File

@ -7,5 +7,6 @@ cargo doc --no-deps --verbose \
-p ethcore \
-p ethsync \
-p ethcore-rpc \
-p ethcore-webapp \
-p parity \
-p ethminer

View File

@ -1,24 +1,33 @@
{
"name": "Frontier/Homestead",
"engineName": "Ethash",
"engine": {
"Ethash": {
"params": {
"accountStartNonce": "0x00",
"frontierCompatibilityModeLimit": "0x118c30",
"maximumExtraDataSize": "0x20",
"tieBreakingGas": false,
"minGasLimit": "0x1388",
"gasLimitBoundDivisor": "0x0400",
"minimumDifficulty": "0x020000",
"difficultyBoundDivisor": "0x0800",
"durationLimit": "0x0d",
"blockReward": "0x4563918244F40000",
"registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b",
"registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b"
}
}
},
"params": {
"accountStartNonce": "0x00",
"frontierCompatibilityModeLimit": "0x118c30",
"maximumExtraDataSize": "0x20",
"minGasLimit": "0x1388",
"networkID" : "0x1"
},
"genesis": {
"seal": {
"ethereum": {
"nonce": "0x0000000000000042",
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
}
},
"difficulty": "0x400000000",
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"author": "0x0000000000000000000000000000000000000000",
"timestamp": "0x00",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",

View File

@ -1,24 +1,33 @@
{
"name": "Frontier (Test)",
"engineName": "Ethash",
"engine": {
"Ethash": {
"params": {
"accountStartNonce": "0x00",
"frontierCompatibilityModeLimit": "0x118c30",
"maximumExtraDataSize": "0x20",
"tieBreakingGas": false,
"minGasLimit": "0x1388",
"gasLimitBoundDivisor": "0x0400",
"minimumDifficulty": "0x020000",
"difficultyBoundDivisor": "0x0800",
"durationLimit": "0x0d",
"blockReward": "0x4563918244F40000",
"registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b",
"registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b"
}
}
},
"params": {
"accountStartNonce": "0x00",
"frontierCompatibilityModeLimit": "0x118c30",
"maximumExtraDataSize": "0x20",
"minGasLimit": "0x1388",
"networkID" : "0x1"
},
"genesis": {
"seal": {
"ethereum": {
"nonce": "0x0000000000000042",
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
}
},
"difficulty": "0x400000000",
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"author": "0x0000000000000000000000000000000000000000",
"timestamp": "0x00",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",

View File

@ -1,24 +1,33 @@
{
"name": "Frontier (Test)",
"engineName": "Ethash",
"engine": {
"Ethash": {
"params": {
"accountStartNonce": "0x00",
"frontierCompatibilityModeLimit": "0xffffffffffffffff",
"maximumExtraDataSize": "0x20",
"tieBreakingGas": false,
"minGasLimit": "0x1388",
"gasLimitBoundDivisor": "0x0400",
"minimumDifficulty": "0x020000",
"difficultyBoundDivisor": "0x0800",
"durationLimit": "0x0d",
"blockReward": "0x4563918244F40000",
"registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b",
"registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b"
}
}
},
"params": {
"accountStartNonce": "0x00",
"frontierCompatibilityModeLimit": "0xffffffffffffffff",
"maximumExtraDataSize": "0x20",
"minGasLimit": "0x1388",
"networkID" : "0x1"
},
"genesis": {
"seal": {
"ethereum": {
"nonce": "0x0000000000000042",
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
}
},
"difficulty": "0x400000000",
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"author": "0x0000000000000000000000000000000000000000",
"timestamp": "0x00",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",

View File

@ -1,24 +1,33 @@
{
"name": "Homestead (Test)",
"engineName": "Ethash",
"engine": {
"Ethash": {
"params": {
"accountStartNonce": "0x00",
"frontierCompatibilityModeLimit": 0,
"maximumExtraDataSize": "0x20",
"minGasLimit": "0x1388",
"tieBreakingGas": false,
"gasLimitBoundDivisor": "0x0400",
"minimumDifficulty": "0x020000",
"difficultyBoundDivisor": "0x0800",
"durationLimit": "0x0d",
"blockReward": "0x4563918244F40000",
"registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b",
"registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b"
}
}
},
"params": {
"accountStartNonce": "0x00",
"frontierCompatibilityModeLimit": 0,
"maximumExtraDataSize": "0x20",
"minGasLimit": "0x1388",
"networkID" : "0x1"
},
"genesis": {
"seal": {
"ethereum": {
"nonce": "0x0000000000000042",
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
}
},
"difficulty": "0x400000000",
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"author": "0x0000000000000000000000000000000000000000",
"timestamp": "0x00",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",

View File

@ -1,24 +1,33 @@
{
"name": "Morden",
"engineName": "Ethash",
"engine": {
"Ethash": {
"params": {
"accountStartNonce": "0x0100000",
"frontierCompatibilityModeLimit": "0x789b0",
"maximumExtraDataSize": "0x20",
"tieBreakingGas": false,
"minGasLimit": "0x1388",
"gasLimitBoundDivisor": "0x0400",
"minimumDifficulty": "0x020000",
"difficultyBoundDivisor": "0x0800",
"durationLimit": "0x0d",
"blockReward": "0x4563918244F40000",
"registrar": "",
"registrar": ""
}
}
},
"params": {
"accountStartNonce": "0x0100000",
"frontierCompatibilityModeLimit": "0x789b0",
"maximumExtraDataSize": "0x20",
"minGasLimit": "0x1388",
"networkID" : "0x2"
},
"genesis": {
"seal": {
"ethereum": {
"nonce": "0x00006d6f7264656e",
"mixHash": "0x00000000000000000000000000000000000000647572616c65787365646c6578"
}
},
"difficulty": "0x20000",
"mixHash": "0x00000000000000000000000000000000000000647572616c65787365646c6578",
"author": "0x0000000000000000000000000000000000000000",
"timestamp": "0x00",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",

View File

@ -1,24 +1,33 @@
{
"name": "Olympic",
"engineName": "Ethash",
"engine": {
"Ethash": {
"params": {
"accountStartNonce": "0x00",
"frontierCompatibilityModeLimit": "0xffffffffffffffff",
"maximumExtraDataSize": "0x0400",
"tieBreakingGas": false,
"minGasLimit": "125000",
"gasLimitBoundDivisor": "0x0400",
"minimumDifficulty": "0x020000",
"difficultyBoundDivisor": "0x0800",
"durationLimit": "0x08",
"blockReward": "0x14D1120D7B160000",
"registrar": "5e70c0bbcd5636e0f9f9316e9f8633feb64d4050",
"registrar": "5e70c0bbcd5636e0f9f9316e9f8633feb64d4050"
}
}
},
"params": {
"accountStartNonce": "0x00",
"frontierCompatibilityModeLimit": "0xffffffffffffffff",
"maximumExtraDataSize": "0x0400",
"minGasLimit": "125000",
"networkID" : "0x0"
},
"genesis": {
"seal": {
"ethereum": {
"nonce": "0x000000000000002a",
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
}
},
"difficulty": "0x20000",
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"author": "0x0000000000000000000000000000000000000000",
"timestamp": "0x00",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",

View File

@ -1,24 +1,23 @@
{
"name": "Morden",
"engineName": "NullEngine",
"engine": {
"Null": null
},
"params": {
"accountStartNonce": "0x0100000",
"frontierCompatibilityModeLimit": "0x0",
"maximumExtraDataSize": "0x20",
"tieBreakingGas": false,
"minGasLimit": "0x1388",
"gasLimitBoundDivisor": "0x0400",
"minimumDifficulty": "0x020000",
"difficultyBoundDivisor": "0x0800",
"durationLimit": "0x0d",
"blockReward": "0x4563918244F40000",
"registrar": "",
"networkID" : "0x2"
},
"genesis": {
"seal": {
"ethereum": {
"nonce": "0x00006d6f7264656e",
"mixHash": "0x00000000000000000000000000000000000000647572616c65787365646c6578"
}
},
"difficulty": "0x20000",
"mixHash": "0x00000000000000000000000000000000000000647572616c65787365646c6578",
"author": "0x0000000000000000000000000000000000000000",
"timestamp": "0x00",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",

View File

@ -1,24 +1,23 @@
{
"name": "Morden",
"engineName": "NullEngine",
"engine": {
"Null": null
},
"params": {
"accountStartNonce": "0x0100000",
"frontierCompatibilityModeLimit": "0x789b0",
"maximumExtraDataSize": "0x20",
"tieBreakingGas": false,
"minGasLimit": "0x1388",
"gasLimitBoundDivisor": "0x0400",
"minimumDifficulty": "0x020000",
"difficultyBoundDivisor": "0x0800",
"durationLimit": "0x0d",
"blockReward": "0x4563918244F40000",
"registrar": "",
"networkID" : "0x2"
},
"genesis": {
"seal": {
"ethereum": {
"nonce": "0x00006d6f7264656e",
"mixHash": "0x00000000000000000000000000000000000000647572616c65787365646c6578"
}
},
"difficulty": "0x20000",
"mixHash": "0x00000000000000000000000000000000000000647572616c65787365646c6578",
"author": "0x0000000000000000000000000000000000000000",
"timestamp": "0x00",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",

View File

@ -497,16 +497,16 @@ mod tests {
use tests::helpers::*;
use super::*;
use common::*;
use engine::*;
#[test]
fn open_block() {
use spec::*;
let engine = Spec::new_test().to_engine().unwrap();
let genesis_header = engine.spec().genesis_header();
let spec = Spec::new_test();
let engine = &spec.engine;
let genesis_header = spec.genesis_header();
let mut db_result = get_temp_journal_db();
let mut db = db_result.take();
engine.spec().ensure_db_good(db.as_hashdb_mut());
spec.ensure_db_good(db.as_hashdb_mut());
let last_hashes = vec![genesis_header.hash()];
let b = OpenBlock::new(engine.deref(), false, db, &genesis_header, last_hashes, Address::zero(), x!(3141562), vec![]);
let b = b.close_and_lock();
@ -516,19 +516,20 @@ mod tests {
#[test]
fn enact_block() {
use spec::*;
let engine = Spec::new_test().to_engine().unwrap();
let genesis_header = engine.spec().genesis_header();
let spec = Spec::new_test();
let engine = &spec.engine;
let genesis_header = spec.genesis_header();
let mut db_result = get_temp_journal_db();
let mut db = db_result.take();
engine.spec().ensure_db_good(db.as_hashdb_mut());
spec.ensure_db_good(db.as_hashdb_mut());
let b = OpenBlock::new(engine.deref(), false, db, &genesis_header, vec![genesis_header.hash()], Address::zero(), x!(3141562), vec![]).close_and_lock().seal(engine.deref(), vec![]).unwrap();
let orig_bytes = b.rlp_bytes();
let orig_db = b.drain();
let mut db_result = get_temp_journal_db();
let mut db = db_result.take();
engine.spec().ensure_db_good(db.as_hashdb_mut());
spec.ensure_db_good(db.as_hashdb_mut());
let e = enact_and_seal(&orig_bytes, engine.deref(), false, db, &genesis_header, vec![genesis_header.hash()]).unwrap();
assert_eq!(e.rlp_bytes(), orig_bytes);
@ -541,12 +542,13 @@ mod tests {
#[test]
fn enact_block_with_uncle() {
use spec::*;
let engine = Spec::new_test().to_engine().unwrap();
let genesis_header = engine.spec().genesis_header();
let spec = Spec::new_test();
let engine = &spec.engine;
let genesis_header = spec.genesis_header();
let mut db_result = get_temp_journal_db();
let mut db = db_result.take();
engine.spec().ensure_db_good(db.as_hashdb_mut());
spec.ensure_db_good(db.as_hashdb_mut());
let mut open_block = OpenBlock::new(engine.deref(), false, db, &genesis_header, vec![genesis_header.hash()], Address::zero(), x!(3141562), vec![]);
let mut uncle1_header = Header::new();
uncle1_header.extra_data = b"uncle1".to_vec();
@ -561,7 +563,7 @@ mod tests {
let mut db_result = get_temp_journal_db();
let mut db = db_result.take();
engine.spec().ensure_db_good(db.as_hashdb_mut());
spec.ensure_db_good(db.as_hashdb_mut());
let e = enact_and_seal(&orig_bytes, engine.deref(), false, db, &genesis_header, vec![genesis_header.hash()]).unwrap();
let bytes = e.rlp_bytes();

View File

@ -116,6 +116,7 @@ struct VerifyingBlock {
}
struct QueueSignal {
deleting: Arc<AtomicBool>,
signalled: AtomicBool,
message_channel: IoChannel<NetSyncMessage>,
}
@ -123,10 +124,16 @@ struct QueueSignal {
impl QueueSignal {
#[cfg_attr(feature="dev", allow(bool_comparison))]
fn set(&self) {
// Do not signal when we are about to close
if self.deleting.load(AtomicOrdering::Relaxed) {
return;
}
if self.signalled.compare_and_swap(false, true, AtomicOrdering::Relaxed) == false {
self.message_channel.send(UserMessage(SyncMessage::BlockVerified)).expect("Error sending BlockVerified message");
}
}
fn reset(&self) {
self.signalled.store(false, AtomicOrdering::Relaxed);
}
@ -150,8 +157,12 @@ impl BlockQueue {
bad: Mutex::new(HashSet::new()),
});
let more_to_verify = Arc::new(Condvar::new());
let ready_signal = Arc::new(QueueSignal { signalled: AtomicBool::new(false), message_channel: message_channel });
let deleting = Arc::new(AtomicBool::new(false));
let ready_signal = Arc::new(QueueSignal {
deleting: deleting.clone(),
signalled: AtomicBool::new(false),
message_channel: message_channel
});
let empty = Arc::new(Condvar::new());
let panic_handler = PanicHandler::new_in_arc();
@ -431,12 +442,14 @@ impl MayPanic for BlockQueue {
impl Drop for BlockQueue {
fn drop(&mut self) {
trace!(target: "shutdown", "[BlockQueue] Closing...");
self.clear();
self.deleting.store(true, AtomicOrdering::Release);
self.more_to_verify.notify_all();
for t in self.verifiers.drain(..) {
t.join().unwrap();
}
trace!(target: "shutdown", "[BlockQueue] Closed.");
}
}
@ -451,7 +464,7 @@ mod tests {
fn get_test_queue() -> BlockQueue {
let spec = get_test_spec();
let engine = spec.to_engine().unwrap();
let engine = spec.engine;
BlockQueue::new(BlockQueueConfig::default(), Arc::new(engine), IoChannel::disconnected())
}
@ -459,7 +472,7 @@ mod tests {
fn can_be_created() {
// TODO better test
let spec = Spec::new_test();
let engine = spec.to_engine().unwrap();
let engine = spec.engine;
let _ = BlockQueue::new(BlockQueueConfig::default(), Arc::new(engine), IoChannel::disconnected());
}
@ -520,7 +533,7 @@ mod tests {
#[test]
fn test_mem_limit() {
let spec = get_test_spec();
let engine = spec.to_engine().unwrap();
let engine = spec.engine;
let mut config = BlockQueueConfig::default();
config.max_mem_use = super::MIN_MEM_LIMIT; // empty queue uses about 15000
let queue = BlockQueue::new(config, Arc::new(engine), IoChannel::disconnected());

View File

@ -18,6 +18,7 @@ use util::*;
use crypto::sha2::Sha256;
use crypto::ripemd160::Ripemd160;
use crypto::digest::Digest;
use ethjson;
/// Definition of a contract whose implementation is built-in.
pub struct Builtin {
@ -46,13 +47,12 @@ impl Builtin {
}
/// Create a new object from a builtin-function name with a linear cost associated with input size.
pub fn from_named_linear(name: &str, base_cost: usize, word_cost: usize) -> Option<Builtin> {
new_builtin_exec(name).map(|b| {
pub fn from_named_linear(name: &str, base_cost: usize, word_cost: usize) -> Builtin {
let cost = Box::new(move|s: usize| -> U256 {
U256::from(base_cost) + U256::from(word_cost) * U256::from((s + 31) / 32)
});
Self::new(cost, b)
})
Self::new(cost, new_builtin_exec(name))
}
/// Simple forwarder for cost.
@ -60,25 +60,16 @@ impl Builtin {
/// Simple forwarder for execute.
pub fn execute(&self, input: &[u8], output: &mut[u8]) { (*self.execute)(input, output); }
}
/// Create a builtin from JSON.
///
/// JSON must be of the form `{ "name": "identity", "pricing": {"base": 10, "word": 20} }`.
pub fn from_json(json: &Json) -> Option<Builtin> {
// NICE: figure out a more convenient means of handing errors here.
if let Json::String(ref name) = json["name"] {
if let Json::Object(ref o) = json["pricing"] {
if let Json::Object(ref o) = o["linear"] {
if let Json::U64(ref word) = o["word"] {
if let Json::U64(ref base) = o["base"] {
return Self::from_named_linear(&name[..], *base as usize, *word as usize);
impl From<ethjson::spec::Builtin> for Builtin {
fn from(b: ethjson::spec::Builtin) -> Self {
match b.pricing {
ethjson::spec::Pricing::Linear(linear) => {
Self::from_named_linear(b.name.as_ref(), linear.base, linear.word)
}
}
}
}
}
None
}
}
/// Copy a bunch of bytes to a destination; if the `src` is too small to fill `dest`,
@ -92,14 +83,14 @@ pub fn copy_to(src: &[u8], dest: &mut[u8]) {
/// Create a new builtin executor according to `name`.
/// TODO: turn in to a factory with dynamic registration.
pub fn new_builtin_exec(name: &str) -> Option<Box<Fn(&[u8], &mut [u8])>> {
pub fn new_builtin_exec(name: &str) -> Box<Fn(&[u8], &mut [u8])> {
match name {
"identity" => Some(Box::new(move|input: &[u8], output: &mut[u8]| {
"identity" => Box::new(move|input: &[u8], output: &mut[u8]| {
for i in 0..min(input.len(), output.len()) {
output[i] = input[i];
}
})),
"ecrecover" => Some(Box::new(move|input: &[u8], output: &mut[u8]| {
}),
"ecrecover" => Box::new(move|input: &[u8], output: &mut[u8]| {
#[repr(packed)]
#[derive(Debug)]
struct InType {
@ -122,8 +113,8 @@ pub fn new_builtin_exec(name: &str) -> Option<Box<Fn(&[u8], &mut [u8])>> {
}
}
}
})),
"sha256" => Some(Box::new(move|input: &[u8], output: &mut[u8]| {
}),
"sha256" => Box::new(move|input: &[u8], output: &mut[u8]| {
let mut sha = Sha256::new();
sha.input(input);
if output.len() >= 32 {
@ -133,21 +124,23 @@ pub fn new_builtin_exec(name: &str) -> Option<Box<Fn(&[u8], &mut [u8])>> {
sha.result(ret.as_slice_mut());
copy_to(&ret, output);
}
})),
"ripemd160" => Some(Box::new(move|input: &[u8], output: &mut[u8]| {
}),
"ripemd160" => Box::new(move|input: &[u8], output: &mut[u8]| {
let mut sha = Ripemd160::new();
sha.input(input);
let mut ret = H256::new();
sha.result(&mut ret.as_slice_mut()[12..32]);
copy_to(&ret, output);
})),
_ => None
}),
_ => {
panic!("invalid builtin name {}", name);
}
}
}
#[test]
fn identity() {
let f = new_builtin_exec("identity").unwrap();
let f = new_builtin_exec("identity");
let i = [0u8, 1, 2, 3];
let mut o2 = [255u8; 2];
@ -167,7 +160,7 @@ fn identity() {
#[test]
fn sha256() {
use rustc_serialize::hex::FromHex;
let f = new_builtin_exec("sha256").unwrap();
let f = new_builtin_exec("sha256");
let i = [0u8; 0];
let mut o = [255u8; 32];
@ -186,7 +179,7 @@ fn sha256() {
#[test]
fn ripemd160() {
use rustc_serialize::hex::FromHex;
let f = new_builtin_exec("ripemd160").unwrap();
let f = new_builtin_exec("ripemd160");
let i = [0u8; 0];
let mut o = [255u8; 32];
@ -213,7 +206,7 @@ fn ecrecover() {
let s = k.sign(&m).unwrap();
println!("Signed: {}", s);*/
let f = new_builtin_exec("ecrecover").unwrap();
let f = new_builtin_exec("ecrecover");
let i = FromHex::from_hex("47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad000000000000000000000000000000000000000000000000000000000000001b650acf9d3f5f0a2c799776a1254355d5f4061762a237396a99a0e0e3fc2bcd6729514a0dacb2e623ac4abd157cb18163ff942280db4d5caad66ddf941ba12e03").unwrap();
let mut o = [255u8; 32];
@ -260,9 +253,15 @@ fn ecrecover() {
assert_eq!(&o[..], &(FromHex::from_hex("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").unwrap())[..]);*/
}
#[test]
#[should_panic]
fn from_unknown_linear() {
let _ = Builtin::from_named_linear("dw", 10, 20);
}
#[test]
fn from_named_linear() {
let b = Builtin::from_named_linear("identity", 10, 20).unwrap();
let b = Builtin::from_named_linear("identity", 10, 20);
assert_eq!((*b.cost)(0), U256::from(10));
assert_eq!((*b.cost)(1), U256::from(30));
assert_eq!((*b.cost)(32), U256::from(30));
@ -276,9 +275,14 @@ fn from_named_linear() {
#[test]
fn from_json() {
let text = r#"{"name": "identity", "pricing": {"linear": {"base": 10, "word": 20}}}"#;
let json = Json::from_str(text).unwrap();
let b = Builtin::from_json(&json).unwrap();
let b = Builtin::from(ethjson::spec::Builtin {
name: "identity".to_owned(),
pricing: ethjson::spec::Pricing::Linear(ethjson::spec::Linear {
base: 10,
word: 20,
})
});
assert_eq!((*b.cost)(0), U256::from(10));
assert_eq!((*b.cost)(1), U256::from(30));
assert_eq!((*b.cost)(32), U256::from(30));

View File

@ -140,14 +140,15 @@ impl<V> Client<V> where V: Verifier {
let mut state_path = path.to_path_buf();
state_path.push("state");
let engine = Arc::new(try!(spec.to_engine()));
let state_path_str = state_path.to_str().unwrap();
let mut state_db = journaldb::new(state_path_str, config.pruning);
if state_db.is_empty() && engine.spec().ensure_db_good(state_db.as_hashdb_mut()) {
state_db.commit(0, &engine.spec().genesis_header().hash(), None).expect("Error commiting genesis state to state DB");
if state_db.is_empty() && spec.ensure_db_good(state_db.as_hashdb_mut()) {
state_db.commit(0, &spec.genesis_header().hash(), None).expect("Error commiting genesis state to state DB");
}
let engine = Arc::new(spec.engine);
let block_queue = BlockQueue::new(config.queue, engine.clone(), message_channel);
let panic_handler = PanicHandler::new_in_arc();
panic_handler.forward_from(&block_queue);

View File

@ -16,7 +16,7 @@
use common::*;
use block::ExecutedBlock;
use spec::Spec;
use spec::CommonParams;
use evm::Schedule;
use evm::Factory;
@ -35,7 +35,7 @@ pub trait Engine : Sync + Send {
fn extra_info(&self, _header: &Header) -> HashMap<String, String> { HashMap::new() }
/// Get the general parameters of the chain.
fn spec(&self) -> &Spec;
fn params(&self) -> &CommonParams;
/// Get current EVM factory
fn vm_factory(&self) -> &Factory;
@ -43,14 +43,18 @@ pub trait Engine : Sync + Send {
/// Get the EVM schedule for the given `env_info`.
fn schedule(&self, env_info: &EnvInfo) -> Schedule;
/// Builtin-contracts we would like to see in the chain.
/// (In principle these are just hints for the engine since that has the last word on them.)
fn builtins(&self) -> &BTreeMap<Address, Builtin>;
/// Some intrinsic operation parameters; by default they take their value from the `spec()`'s `engine_params`.
fn maximum_extra_data_size(&self) -> usize { decode(&self.spec().engine_params.get("maximumExtraDataSize").unwrap()) }
fn maximum_extra_data_size(&self) -> usize { self.params().maximum_extra_data_size }
/// Maximum number of uncles a block is allowed to declare.
fn maximum_uncle_count(&self) -> usize { 2 }
/// The number of generations back that uncles can be.
fn maximum_uncle_age(&self) -> usize { 6 }
/// The nonce with which accounts begin.
fn account_start_nonce(&self) -> U256 { decode(&self.spec().engine_params.get("accountStartNonce").unwrap()) }
fn account_start_nonce(&self) -> U256 { self.params().account_start_nonce }
/// Block transformation functions, before the transactions.
fn on_new_block(&self, _block: &mut ExecutedBlock) {}
@ -94,23 +98,13 @@ pub trait Engine : Sync + Send {
// TODO: builtin contract routing - to do this properly, it will require removing the built-in configuration-reading logic
// from Spec into here and removing the Spec::builtins field.
/// Determine whether a particular address is a builtin contract.
fn is_builtin(&self, a: &Address) -> bool { self.spec().builtins.contains_key(a) }
fn is_builtin(&self, a: &Address) -> bool { self.builtins().contains_key(a) }
/// Determine the code execution cost of the builtin contract with address `a`.
/// Panics if `is_builtin(a)` is not true.
fn cost_of_builtin(&self, a: &Address, input: &[u8]) -> U256 { self.spec().builtins.get(a).unwrap().cost(input.len()) }
fn cost_of_builtin(&self, a: &Address, input: &[u8]) -> U256 { self.builtins().get(a).unwrap().cost(input.len()) }
/// Execution the builtin contract `a` on `input` and return `output`.
/// Panics if `is_builtin(a)` is not true.
fn execute_builtin(&self, a: &Address, input: &[u8], output: &mut [u8]) { self.spec().builtins.get(a).unwrap().execute(input, output); }
fn execute_builtin(&self, a: &Address, input: &[u8], output: &mut [u8]) { self.builtins().get(a).unwrap().execute(input, output); }
// TODO: sealing stuff - though might want to leave this for later.
/// Get a named parameter from the `spec()`'s `engine_params` item and convert to u64, or 0 if that fails.
fn u64_param(&self, name: &str) -> u64 {
self.spec().engine_params.get(name).map_or(0u64, |a| decode(&a))
}
/// Get a named parameter from the `spec()`'s `engine_params` item and convert to U256, or 0 if that fails.
fn u256_param(&self, name: &str) -> U256 {
self.spec().engine_params.get(name).map_or(x!(0), |a| decode(&a))
}
}

View File

@ -55,21 +55,6 @@ impl Default for EnvInfo {
}
}
impl FromJson for EnvInfo {
fn from_json(json: &Json) -> EnvInfo {
let current_number: u64 = xjson!(&json["currentNumber"]);
EnvInfo {
number: current_number,
author: xjson!(&json["currentCoinbase"]),
difficulty: xjson!(&json["currentDifficulty"]),
gas_limit: xjson!(&json["currentGasLimit"]),
timestamp: xjson!(&json["currentTimestamp"]),
last_hashes: (1..cmp::min(current_number + 1, 257)).map(|i| format!("{}", current_number - i).as_bytes().sha3()).collect(),
gas_used: x!(0),
}
}
}
impl From<ethjson::vm::Env> for EnvInfo {
fn from(e: ethjson::vm::Env) -> Self {
let number = e.number.into();
@ -90,24 +75,20 @@ mod tests {
extern crate rustc_serialize;
use super::*;
use rustc_serialize::*;
use util::from_json::FromJson;
use util::hash::*;
use util::numbers::U256;
use std::str::FromStr;
use ethjson;
#[test]
fn it_serializes_form_json() {
let env_info = EnvInfo::from_json(&json::Json::from_str(
r#"
{
"currentCoinbase": "0x000000f00000000f000000000000f00000000f00",
"currentNumber": 1112339,
"currentDifficulty": 50000,
"currentGasLimit" : 40000,
"currentTimestamp" : 1100
}
"#
).unwrap());
let env_info = EnvInfo::from(ethjson::vm::Env {
author: ethjson::hash::Address(Address::from_str("000000f00000000f000000000000f00000000f00").unwrap()),
number: ethjson::uint::Uint(U256::from(1_112_339)),
difficulty: ethjson::uint::Uint(U256::from(50_000)),
gas_limit: ethjson::uint::Uint(U256::from(40_000)),
timestamp: ethjson::uint::Uint(U256::from(1_100))
});
assert_eq!(env_info.number, 1112339);
assert_eq!(env_info.author, Address::from_str("000000f00000000f000000000000f00000000f00").unwrap());

View File

@ -19,42 +19,63 @@ extern crate ethash;
use self::ethash::{quick_get_difficulty, EthashManager, H256 as EH256};
use common::*;
use block::*;
use spec::*;
use spec::CommonParams;
use engine::*;
use evm::Schedule;
use evm::Factory;
use evm::{Schedule, Factory};
use ethjson;
/// Ethash params.
#[derive(Debug, PartialEq)]
pub struct EthashParams {
/// Tie breaking gas.
pub tie_breaking_gas: bool,
/// Gas limit divisor.
pub gas_limit_bound_divisor: U256,
/// Minimum difficulty.
pub minimum_difficulty: U256,
/// Difficulty bound divisor.
pub difficulty_bound_divisor: U256,
/// Block duration.
pub duration_limit: u64,
/// Block reward.
pub block_reward: U256,
/// Namereg contract address.
pub registrar: Address,
}
impl From<ethjson::spec::EthashParams> for EthashParams {
fn from(p: ethjson::spec::EthashParams) -> Self {
EthashParams {
tie_breaking_gas: p.tie_breaking_gas,
gas_limit_bound_divisor: p.gas_limit_bound_divisor.into(),
minimum_difficulty: p.minimum_difficulty.into(),
difficulty_bound_divisor: p.difficulty_bound_divisor.into(),
duration_limit: p.duration_limit.into(),
block_reward: p.block_reward.into(),
registrar: p.registrar.into(),
}
}
}
/// Engine using Ethash proof-of-work consensus algorithm, suitable for Ethereum
/// mainnet chains in the Olympic, Frontier and Homestead eras.
pub struct Ethash {
spec: Spec,
params: CommonParams,
ethash_params: EthashParams,
builtins: BTreeMap<Address, Builtin>,
pow: EthashManager,
factory: Factory,
u64_params: RwLock<HashMap<String, u64>>,
u256_params: RwLock<HashMap<String, U256>>,
}
impl Ethash {
/// Create a new boxed instance of Ethash engine
pub fn new_boxed(spec: Spec) -> Box<Engine> {
Box::new(Ethash {
spec: spec,
pow: EthashManager::new(),
// TODO [todr] should this return any specific factory?
factory: Factory::default(),
u64_params: RwLock::new(HashMap::new()),
u256_params: RwLock::new(HashMap::new())
})
}
#[cfg(test)]
fn new_test(spec: Spec) -> Ethash {
/// Create a new instance of Ethash engine
pub fn new(params: CommonParams, ethash_params: EthashParams, builtins: BTreeMap<Address, Builtin>) -> Self {
Ethash {
spec: spec,
params: params,
ethash_params: ethash_params,
builtins: builtins,
pow: EthashManager::new(),
factory: Factory::default(),
u64_params: RwLock::new(HashMap::new()),
u256_params: RwLock::new(HashMap::new())
}
}
}
@ -65,17 +86,23 @@ impl Engine for Ethash {
// Two fields - mix
fn seal_fields(&self) -> usize { 2 }
fn params(&self) -> &CommonParams { &self.params }
fn builtins(&self) -> &BTreeMap<Address, Builtin> {
&self.builtins
}
/// Additional engine-specific information for the user/developer concerning `header`.
fn extra_info(&self, _header: &Header) -> HashMap<String, String> { HashMap::new() }
fn spec(&self) -> &Spec { &self.spec }
fn vm_factory(&self) -> &Factory {
&self.factory
}
fn schedule(&self, env_info: &EnvInfo) -> Schedule {
trace!(target: "client", "Creating schedule. param={:?}, fCML={}", self.spec().engine_params.get("frontierCompatibilityModeLimit"), self.u64_param("frontierCompatibilityModeLimit"));
if env_info.number < self.u64_param("frontierCompatibilityModeLimit") {
trace!(target: "client", "Creating schedule. fCML={}", self.params.frontier_compatibility_mode_limit);
if env_info.number < self.params.frontier_compatibility_mode_limit {
Schedule::new_frontier()
} else {
Schedule::new_homestead()
@ -86,7 +113,7 @@ impl Engine for Ethash {
header.difficulty = self.calculate_difficuty(header, parent);
header.gas_limit = {
let gas_limit = parent.gas_limit;
let bound_divisor = self.u256_param("gasLimitBoundDivisor");
let bound_divisor = self.ethash_params.gas_limit_bound_divisor;
if gas_limit < gas_floor_target {
min(gas_floor_target, gas_limit + gas_limit / bound_divisor - x!(1))
} else {
@ -100,7 +127,7 @@ impl Engine for Ethash {
/// Apply the block reward on finalisation of the block.
/// This assumes that all uncles are valid uncles (i.e. of at least one generation before the current).
fn on_close_block(&self, block: &mut ExecutedBlock) {
let reward = self.spec().engine_params.get("blockReward").map_or(U256::from(0u64), |a| decode(&a));
let reward = self.ethash_params.block_reward;
let fields = block.fields_mut();
// Bestow block reward
@ -125,7 +152,7 @@ impl Engine for Ethash {
try!(UntrustedRlp::new(&header.seal[1]).as_val::<H64>());
// TODO: consider removing these lines.
let min_difficulty = decode(self.spec().engine_params.get("minimumDifficulty").unwrap());
let min_difficulty = self.ethash_params.minimum_difficulty;
if header.difficulty < min_difficulty {
return Err(From::from(BlockError::DifficultyOutOfBounds(OutOfBounds { min: Some(min_difficulty), max: None, found: header.difficulty })))
}
@ -170,7 +197,7 @@ impl Engine for Ethash {
if header.difficulty != expected_difficulty {
return Err(From::from(BlockError::InvalidDifficulty(Mismatch { expected: expected_difficulty, found: header.difficulty })))
}
let gas_limit_divisor = decode(self.spec().engine_params.get("gasLimitBoundDivisor").unwrap());
let gas_limit_divisor = self.ethash_params.gas_limit_bound_divisor;
let min_gas = parent.gas_limit - parent.gas_limit / gas_limit_divisor;
let max_gas = parent.gas_limit + parent.gas_limit / gas_limit_divisor;
if header.gas_limit <= min_gas || header.gas_limit >= max_gas {
@ -180,7 +207,7 @@ impl Engine for Ethash {
}
fn verify_transaction_basic(&self, t: &SignedTransaction, header: &Header) -> result::Result<(), Error> {
if header.number() >= self.u64_param("frontierCompatibilityModeLimit") {
if header.number() >= self.params.frontier_compatibility_mode_limit {
try!(t.check_low_s());
}
Ok(())
@ -189,16 +216,6 @@ impl Engine for Ethash {
fn verify_transaction(&self, t: &SignedTransaction, _header: &Header) -> Result<(), Error> {
t.sender().map(|_|()) // Perform EC recovery and cache sender
}
fn u64_param(&self, name: &str) -> u64 {
*self.u64_params.write().unwrap().entry(name.to_owned()).or_insert_with(||
self.spec().engine_params.get(name).map_or(0u64, |a| decode(&a)))
}
fn u256_param(&self, name: &str) -> U256 {
*self.u256_params.write().unwrap().entry(name.to_owned()).or_insert_with(||
self.spec().engine_params.get(name).map_or(x!(0), |a| decode(&a)))
}
}
#[cfg_attr(feature="dev", allow(wrong_self_convention))] // to_ethash should take self
@ -209,10 +226,11 @@ impl Ethash {
panic!("Can't calculate genesis block difficulty");
}
let min_difficulty = self.u256_param("minimumDifficulty");
let difficulty_bound_divisor = self.u256_param("difficultyBoundDivisor");
let duration_limit = self.u64_param("durationLimit");
let frontier_limit = self.u64_param("frontierCompatibilityModeLimit");
let min_difficulty = self.ethash_params.minimum_difficulty;
let difficulty_bound_divisor = self.ethash_params.difficulty_bound_divisor;
let duration_limit = self.ethash_params.duration_limit;
let frontier_limit = self.params.frontier_compatibility_mode_limit;
let mut target = if header.number < frontier_limit {
if header.timestamp >= parent.timestamp + duration_limit {
parent.difficulty - (parent.difficulty / difficulty_bound_divisor)
@ -283,16 +301,16 @@ mod tests {
use block::*;
use engine::*;
use tests::helpers::*;
use super::{Ethash};
use super::super::new_morden;
#[test]
fn on_close_block() {
let engine = new_morden().to_engine().unwrap();
let genesis_header = engine.spec().genesis_header();
let spec = new_morden();
let engine = &spec.engine;
let genesis_header = spec.genesis_header();
let mut db_result = get_temp_journal_db();
let mut db = db_result.take();
engine.spec().ensure_db_good(db.as_hashdb_mut());
spec.ensure_db_good(db.as_hashdb_mut());
let last_hashes = vec![genesis_header.hash()];
let b = OpenBlock::new(engine.deref(), false, db, &genesis_header, last_hashes, Address::zero(), x!(3141562), vec![]);
let b = b.close();
@ -301,11 +319,12 @@ mod tests {
#[test]
fn on_close_block_with_uncle() {
let engine = new_morden().to_engine().unwrap();
let genesis_header = engine.spec().genesis_header();
let spec = new_morden();
let engine = &spec.engine;
let genesis_header = spec.genesis_header();
let mut db_result = get_temp_journal_db();
let mut db = db_result.take();
engine.spec().ensure_db_good(db.as_hashdb_mut());
spec.ensure_db_good(db.as_hashdb_mut());
let last_hashes = vec![genesis_header.hash()];
let mut b = OpenBlock::new(engine.deref(), false, db, &genesis_header, last_hashes, Address::zero(), x!(3141562), vec![]);
let mut uncle = Header::new();
@ -320,27 +339,20 @@ mod tests {
#[test]
fn has_valid_metadata() {
let engine = Ethash::new_boxed(new_morden());
let engine = new_morden().engine;
assert!(!engine.name().is_empty());
assert!(engine.version().major >= 1);
}
#[test]
fn can_return_params() {
let engine = Ethash::new_test(new_morden());
assert!(engine.u64_param("durationLimit") > 0);
assert!(engine.u256_param("minimumDifficulty") > U256::zero());
}
#[test]
fn can_return_factory() {
let engine = Ethash::new_test(new_morden());
let engine = new_morden().engine;
engine.vm_factory();
}
#[test]
fn can_return_schedule() {
let engine = Ethash::new_test(new_morden());
let engine = new_morden().engine;
let schedule = engine.schedule(&EnvInfo {
number: 10000000,
author: x!(0),
@ -368,7 +380,8 @@ mod tests {
#[test]
fn can_do_seal_verification_fail() {
let engine = Ethash::new_test(new_morden());
let engine = new_morden().engine;
//let engine = Ethash::new_test(new_morden());
let header: Header = Header::default();
let verify_result = engine.verify_block_basic(&header, None);
@ -382,7 +395,7 @@ mod tests {
#[test]
fn can_do_difficulty_verification_fail() {
let engine = Ethash::new_test(new_morden());
let engine = new_morden().engine;
let mut header: Header = Header::default();
header.set_seal(vec![rlp::encode(&H256::zero()).to_vec(), rlp::encode(&H64::zero()).to_vec()]);
@ -397,7 +410,7 @@ mod tests {
#[test]
fn can_do_proof_of_work_verification_fail() {
let engine = Ethash::new_test(new_morden());
let engine = new_morden().engine;
let mut header: Header = Header::default();
header.set_seal(vec![rlp::encode(&H256::zero()).to_vec(), rlp::encode(&H64::zero()).to_vec()]);
header.set_difficulty(U256::from_str("ffffffffffffffffffffffffffffffffffffffffffffaaaaaaaaaaaaaaaaaaaa").unwrap());
@ -413,7 +426,7 @@ mod tests {
#[test]
fn can_do_seal_unordered_verification_fail() {
let engine = Ethash::new_test(new_morden());
let engine = new_morden().engine;
let header: Header = Header::default();
let verify_result = engine.verify_block_unordered(&header, None);
@ -427,7 +440,7 @@ mod tests {
#[test]
fn can_do_seal256_verification_fail() {
let engine = Ethash::new_test(new_morden());
let engine = new_morden().engine;
let mut header: Header = Header::default();
header.set_seal(vec![rlp::encode(&H256::zero()).to_vec(), rlp::encode(&H64::zero()).to_vec()]);
let verify_result = engine.verify_block_unordered(&header, None);
@ -441,7 +454,7 @@ mod tests {
#[test]
fn can_do_proof_of_work_unordered_verification_fail() {
let engine = Ethash::new_test(new_morden());
let engine = new_morden().engine;
let mut header: Header = Header::default();
header.set_seal(vec![rlp::encode(&H256::from("b251bd2e0283d0658f2cadfdc8ca619b5de94eca5742725e2e757dd13ed7503d")).to_vec(), rlp::encode(&H64::zero()).to_vec()]);
header.set_difficulty(U256::from_str("ffffffffffffffffffffffffffffffffffffffffffffaaaaaaaaaaaaaaaaaaaa").unwrap());
@ -457,7 +470,7 @@ mod tests {
#[test]
fn can_verify_block_family_genesis_fail() {
let engine = Ethash::new_test(new_morden());
let engine = new_morden().engine;
let header: Header = Header::default();
let parent_header: Header = Header::default();
@ -472,7 +485,7 @@ mod tests {
#[test]
fn can_verify_block_family_difficulty_fail() {
let engine = Ethash::new_test(new_morden());
let engine = new_morden().engine;
let mut header: Header = Header::default();
header.set_number(2);
let mut parent_header: Header = Header::default();
@ -489,7 +502,7 @@ mod tests {
#[test]
fn can_verify_block_family_gas_fail() {
let engine = Ethash::new_test(new_morden());
let engine = new_morden().engine;
let mut header: Header = Header::default();
header.set_number(2);
header.set_difficulty(U256::from_str("0000000000000000000000000000000000000000000000000000000000020000").unwrap());

View File

@ -30,22 +30,22 @@ pub use self::denominations::*;
use super::spec::*;
/// Create a new Olympic chain spec.
pub fn new_olympic() -> Spec { Spec::from_json_utf8(include_bytes!("../../res/ethereum/olympic.json")) }
pub fn new_olympic() -> Spec { Spec::load(include_bytes!("../../res/ethereum/olympic.json")) }
/// Create a new Frontier mainnet chain spec.
pub fn new_frontier() -> Spec { Spec::from_json_utf8(include_bytes!("../../res/ethereum/frontier.json")) }
pub fn new_frontier() -> Spec { Spec::load(include_bytes!("../../res/ethereum/frontier.json")) }
/// Create a new Frontier chain spec as though it never changes to Homestead.
pub fn new_frontier_test() -> Spec { Spec::from_json_utf8(include_bytes!("../../res/ethereum/frontier_test.json")) }
pub fn new_frontier_test() -> Spec { Spec::load(include_bytes!("../../res/ethereum/frontier_test.json")) }
/// Create a new Homestead chain spec as though it never changed from Frontier.
pub fn new_homestead_test() -> Spec { Spec::from_json_utf8(include_bytes!("../../res/ethereum/homestead_test.json")) }
pub fn new_homestead_test() -> Spec { Spec::load(include_bytes!("../../res/ethereum/homestead_test.json")) }
/// Create a new Frontier main net chain spec without genesis accounts.
pub fn new_mainnet_like() -> Spec { Spec::from_json_utf8(include_bytes!("../../res/ethereum/frontier_like_test.json")) }
pub fn new_mainnet_like() -> Spec { Spec::load(include_bytes!("../../res/ethereum/frontier_like_test.json")) }
/// Create a new Morden chain spec.
pub fn new_morden() -> Spec { Spec::from_json_utf8(include_bytes!("../../res/ethereum/morden.json")) }
pub fn new_morden() -> Spec { Spec::load(include_bytes!("../../res/ethereum/morden.json")) }
#[cfg(test)]
mod tests {
@ -57,11 +57,12 @@ mod tests {
#[test]
fn ensure_db_good() {
let engine = new_morden().to_engine().unwrap();
let genesis_header = engine.spec().genesis_header();
let spec = new_morden();
let engine = &spec.engine;
let genesis_header = spec.genesis_header();
let mut db_result = get_temp_journal_db();
let mut db = db_result.take();
engine.spec().ensure_db_good(db.as_hashdb_mut());
spec.ensure_db_good(db.as_hashdb_mut());
let s = State::from_existing(db, genesis_header.state_root.clone(), engine.account_start_nonce());
assert_eq!(s.balance(&address_from_hex("0000000000000000000000000000000000000001")), U256::from(1u64));
assert_eq!(s.balance(&address_from_hex("0000000000000000000000000000000000000002")), U256::from(1u64));
@ -79,7 +80,7 @@ mod tests {
let genesis = morden.genesis_block();
assert_eq!(BlockView::new(&genesis).header_view().sha3(), H256::from_str("0cd786a2425d16f152c658316c423e6ce1181e15c3295826d7c9904cba9ce303").unwrap());
let _ = morden.to_engine();
let _ = morden.engine;
}
#[test]
@ -90,6 +91,6 @@ mod tests {
let genesis = frontier.genesis_block();
assert_eq!(BlockView::new(&genesis).header_view().sha3(), H256::from_str("d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3").unwrap());
let _ = frontier.to_engine();
let _ = frontier.engine;
}
}

View File

@ -330,7 +330,7 @@ mod tests {
fn new() -> Self {
TestSetup {
state: get_temp_state(),
engine: get_test_spec().to_engine().unwrap(),
engine: get_test_spec().engine,
sub_state: Substate::new(),
env_info: get_test_env_info()
}

View File

@ -27,33 +27,6 @@ use substate::*;
use tests::helpers::*;
use ethjson;
struct TestEngineFrontier {
vm_factory: Factory,
spec: Spec,
max_depth: usize
}
impl TestEngineFrontier {
fn new(max_depth: usize, vm_type: VMType) -> TestEngineFrontier {
TestEngineFrontier {
vm_factory: Factory::new(vm_type),
spec: ethereum::new_frontier_test(),
max_depth: max_depth
}
}
}
impl Engine for TestEngineFrontier {
fn name(&self) -> &str { "TestEngine" }
fn spec(&self) -> &Spec { &self.spec }
fn vm_factory(&self) -> &Factory { &self.vm_factory }
fn schedule(&self, _env_info: &EnvInfo) -> Schedule {
let mut schedule = Schedule::new_frontier();
schedule.max_depth = self.max_depth;
schedule
}
}
#[derive(Debug, PartialEq)]
struct CallCreate {
data: Bytes,
@ -207,7 +180,7 @@ fn do_json_test_for(vm_type: &VMType, json_data: &[u8]) -> Vec<String> {
let mut state = state_result.reference_mut();
state.populate_from(From::from(vm.pre_state.clone()));
let info = From::from(vm.env);
let engine = TestEngineFrontier::new(1, vm_type.clone());
let engine = TestEngine::new(1, Factory::new(vm_type.clone()));
let params = ActionParams::from(vm.transaction);
let mut substate = Substate::new();

View File

@ -30,9 +30,9 @@ pub fn json_chain_test(json_data: &[u8], era: ChainEra) -> Vec<String> {
let tests = ethjson::state::Test::load(json_data).unwrap();
let mut failed = Vec::new();
let engine = match era {
ChainEra::Frontier => ethereum::new_mainnet_like(),
ChainEra::Homestead => ethereum::new_homestead_test(),
}.to_engine().unwrap();
ChainEra::Frontier => ethereum::new_mainnet_like().engine,
ChainEra::Homestead => ethereum::new_homestead_test().engine
};
for (name, test) in tests.into_iter() {
let mut fail = false;

View File

@ -76,18 +76,6 @@ impl From<ethjson::state::Log> for LogEntry {
}
}
impl FromJson for LogEntry {
/// Convert given JSON object to a LogEntry.
fn from_json(json: &Json) -> LogEntry {
// TODO: check bloom.
LogEntry {
address: xjson!(&json["address"]),
topics: xjson!(&json["topics"]),
data: xjson!(&json["data"]),
}
}
}
/// Log localized in a blockchain.
#[derive(Default, Debug, PartialEq, Clone)]
pub struct LocalizedLogEntry {

View File

@ -14,26 +14,29 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use std::collections::BTreeMap;
use util::hash::Address;
use builtin::Builtin;
use engine::Engine;
use spec::Spec;
use evm::Schedule;
use evm::Factory;
use spec::CommonParams;
use evm::{Schedule, Factory};
use env_info::EnvInfo;
/// An engine which does not provide any consensus mechanism.
pub struct NullEngine {
spec: Spec,
factory: Factory
params: CommonParams,
builtins: BTreeMap<Address, Builtin>,
factory: Factory,
}
impl NullEngine {
/// Returns new instance of NullEngine with default VM Factory
pub fn new_boxed(spec: Spec) -> Box<Engine> {
Box::new(NullEngine{
spec: spec,
// TODO [todr] should this return any specific factory?
pub fn new(params: CommonParams, builtins: BTreeMap<Address, Builtin>) -> Self {
NullEngine{
params: params,
builtins: builtins,
factory: Factory::default()
})
}
}
}
@ -42,12 +45,20 @@ impl Engine for NullEngine {
&self.factory
}
fn name(&self) -> &str { "NullEngine" }
fn name(&self) -> &str {
"NullEngine"
}
fn spec(&self) -> &Spec { &self.spec }
fn params(&self) -> &CommonParams {
&self.params
}
fn builtins(&self) -> &BTreeMap<Address, Builtin> {
&self.builtins
}
fn schedule(&self, env_info: &EnvInfo) -> Schedule {
if env_info.number < self.u64_param("frontierCompatibilityModeLimit") {
if env_info.number < self.params.frontier_compatibility_mode_limit {
Schedule::new_frontier()
} else {
Schedule::new_homestead()

View File

@ -80,12 +80,22 @@ impl From<ethjson::blockchain::Account> for PodAccount {
balance: a.balance.into(),
nonce: a.nonce.into(),
code: a.code.into(),
storage: a.storage.into_iter().fold(BTreeMap::new(), |mut acc, (key, value)| {
storage: a.storage.into_iter().map(|(key, value)| {
let key: U256 = key.into();
let value: U256 = value.into();
acc.insert(H256::from(key), H256::from(value));
acc
})
(H256::from(key), H256::from(value))
}).collect()
}
}
}
impl From<ethjson::spec::Account> for PodAccount {
fn from(a: ethjson::spec::Account) -> Self {
PodAccount {
balance: a.balance.map_or_else(U256::zero, Into::into),
nonce: a.nonce.map_or_else(U256::zero, Into::into),
code: vec![],
storage: BTreeMap::new()
}
}
}

View File

@ -46,33 +46,20 @@ impl PodState {
pub fn drain(self) -> BTreeMap<Address, PodAccount> { self.0 }
}
impl FromJson for PodState {
/// Translate the JSON object into a hash map of account information ready for insertion into State.
fn from_json(json: &Json) -> PodState {
PodState(json.as_object().unwrap().iter().fold(BTreeMap::new(), |mut state, (address, acc)| {
let balance = acc.find("balance").map(&U256::from_json);
let nonce = acc.find("nonce").map(&U256::from_json);
let storage = acc.find("storage").map(&BTreeMap::from_json);
let code = acc.find("code").map(&Bytes::from_json);
if balance.is_some() || nonce.is_some() || storage.is_some() || code.is_some() {
state.insert(address_from_hex(address), PodAccount{
balance: balance.unwrap_or_else(U256::zero),
nonce: nonce.unwrap_or_else(U256::zero),
storage: storage.unwrap_or_else(BTreeMap::new),
code: code.unwrap_or_else(Vec::new)
});
}
state
}))
impl From<ethjson::blockchain::State> for PodState {
fn from(s: ethjson::blockchain::State) -> PodState {
let state = s.into_iter().map(|(addr, acc)| (addr.into(), PodAccount::from(acc))).collect();
PodState(state)
}
}
impl From<ethjson::blockchain::State> for PodState {
fn from(s: ethjson::blockchain::State) -> PodState {
PodState(s.0.into_iter().fold(BTreeMap::new(), |mut acc, (key, value)| {
acc.insert(key.into(), PodAccount::from(value));
acc
}))
impl From<ethjson::spec::State> for PodState {
fn from(s: ethjson::spec::State) -> PodState {
let state: BTreeMap<_,_> = s.into_iter()
.filter(|pair| !pair.1.is_empty())
.map(|(addr, acc)| (addr.into(), PodAccount::from(acc)))
.collect();
PodState(state)
}
}
@ -85,30 +72,3 @@ impl fmt::Display for PodState {
}
}
#[cfg(test)]
mod tests {
extern crate rustc_serialize;
use super::*;
use rustc_serialize::*;
use util::from_json::FromJson;
use util::hash::*;
#[test]
fn it_serializes_form_json() {
let pod_state = PodState::from_json(&json::Json::from_str(
r#"
{
"0000000000000000000000000000000000000000": {
"balance": "1000",
"nonce": "100",
"storage": {},
"code" : []
}
}
"#
).unwrap());
assert!(pod_state.get().get(&ZERO_ADDRESS).is_some());
}
}

View File

@ -60,7 +60,7 @@ impl ClientService {
panic_handler.forward_from(&net_service);
info!("Starting {}", net_service.host_info());
info!("Configured for {} using {} engine", spec.name, spec.engine_name);
info!("Configured for {} using {:?} engine", spec.name, spec.engine.name());
let client = try!(Client::new(config, spec, db_path, net_service.io().channel()));
panic_handler.forward_from(client.deref());
let client_io = Arc::new(ClientIoHandler {

View File

@ -16,26 +16,9 @@
use util::rlp::*;
use util::numbers::{Uint, U256};
use util::hash::{H64, Address, H256};
use util::hash::{Address, H256};
use ethjson;
/// Genesis seal type.
pub enum Seal {
/// Classic ethereum seal.
Ethereum {
/// Seal nonce.
nonce: H64,
/// Seal mix hash.
mix_hash: H256,
},
/// Generic seal.
Generic {
/// Number of seal fields.
fields: usize,
/// Seal rlp.
rlp: Vec<u8>,
},
}
use super::seal::Seal;
/// Genesis components.
pub struct Genesis {
@ -66,16 +49,7 @@ pub struct Genesis {
impl From<ethjson::spec::Genesis> for Genesis {
fn from(g: ethjson::spec::Genesis) -> Self {
Genesis {
seal: match (g.nonce, g.mix_hash) {
(Some(nonce), Some(mix_hash)) => Seal::Ethereum {
nonce: nonce.into(),
mix_hash: mix_hash.into(),
},
_ => Seal::Generic {
fields: g.seal_fields.unwrap(),
rlp: g.seal_rlp.unwrap().into(),
}
},
seal: From::from(g.seal),
difficulty: g.difficulty.into(),
author: g.author.into(),
timestamp: g.timestamp.into(),

View File

@ -17,6 +17,7 @@
//! Blockchain params.
mod genesis;
mod seal;
pub mod spec;
pub use self::spec::*;

81
ethcore/src/spec/seal.rs Normal file
View File

@ -0,0 +1,81 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Spec seal.
use util::rlp::*;
use util::hash::{H64, H256};
use ethjson;
/// Classic ethereum seal.
pub struct Ethereum {
/// Seal nonce.
pub nonce: H64,
/// Seal mix hash.
pub mix_hash: H256,
}
impl Into<Generic> for Ethereum {
fn into(self) -> Generic {
let mut s = RlpStream::new();
s.append(&self.mix_hash);
s.append(&self.nonce);
Generic {
fields: 2,
rlp: s.out()
}
}
}
/// Generic seal.
pub struct Generic {
/// Number of seal fields.
pub fields: usize,
/// Seal rlp.
pub rlp: Vec<u8>,
}
/// Genesis seal type.
pub enum Seal {
/// Classic ethereum seal.
Ethereum(Ethereum),
/// Generic seal.
Generic(Generic),
}
impl From<ethjson::spec::Seal> for Seal {
fn from(s: ethjson::spec::Seal) -> Self {
match s {
ethjson::spec::Seal::Ethereum(eth) => Seal::Ethereum(Ethereum {
nonce: eth.nonce.into(),
mix_hash: eth.mix_hash.into()
}),
ethjson::spec::Seal::Generic(g) => Seal::Generic(Generic {
fields: g.fields,
rlp: g.rlp.into()
})
}
}
}
impl Into<Generic> for Seal {
fn into(self) -> Generic {
match self {
Seal::Generic(generic) => generic,
Seal::Ethereum(eth) => eth.into()
}
}
}

View File

@ -21,55 +21,51 @@ use engine::*;
use pod_state::*;
use null_engine::*;
use account_db::*;
use super::genesis::Genesis;
use super::seal::Generic as GenericSeal;
use ethereum;
use super::genesis::{Seal as GenesisSeal, Genesis};
use ethjson;
/// Convert JSON value to equivalent RLP representation.
// TODO: handle container types.
fn json_to_rlp(json: &Json) -> Bytes {
match *json {
Json::Boolean(o) => encode(&(if o {1u64} else {0})).to_vec(),
Json::I64(o) => encode(&(o as u64)).to_vec(),
Json::U64(o) => encode(&o).to_vec(),
Json::String(ref s) if s.len() >= 2 && &s[0..2] == "0x" && U256::from_str(&s[2..]).is_ok() => {
encode(&U256::from_str(&s[2..]).unwrap()).to_vec()
},
Json::String(ref s) => {
encode(s).to_vec()
},
_ => panic!()
}
/// Parameters common to all engines.
#[derive(Debug, PartialEq, Clone)]
pub struct CommonParams {
/// Account start nonce.
pub account_start_nonce: U256,
/// Frontier compatibility mode limit.
pub frontier_compatibility_mode_limit: u64,
/// Maximum size of extra data.
pub maximum_extra_data_size: usize,
/// Network id.
pub network_id: U256,
/// Minimum gas limit.
pub min_gas_limit: U256,
}
/// Convert JSON to a string->RLP map.
fn json_to_rlp_map(json: &Json) -> HashMap<String, Bytes> {
json.as_object().unwrap().iter().map(|(k, v)| (k, json_to_rlp(v))).fold(HashMap::new(), |mut acc, kv| {
acc.insert(kv.0.clone(), kv.1);
acc
})
impl From<ethjson::spec::Params> for CommonParams {
fn from(p: ethjson::spec::Params) -> Self {
CommonParams {
account_start_nonce: p.account_start_nonce.into(),
frontier_compatibility_mode_limit: p.frontier_compatibility_mode_limit.into(),
maximum_extra_data_size: p.maximum_extra_data_size.into(),
network_id: p.network_id.into(),
min_gas_limit: p.min_gas_limit.into(),
}
}
}
/// Parameters for a block chain; includes both those intrinsic to the design of the
/// chain and those to be interpreted by the active chain engine.
#[derive(Debug)]
pub struct Spec {
/// User friendly spec name
pub name: String,
/// What engine are we using for this?
pub engine_name: String,
pub engine: Box<Engine>,
/// Known nodes on the network in enode format.
pub nodes: Vec<String>,
/// Network ID
pub network_id: U256,
/// Parameters concerning operation of the specific engine we're using.
/// Maps the parameter name to an RLP-encoded value.
pub engine_params: HashMap<String, Bytes>,
/// Builtin-contracts we would like to see in the chain.
/// (In principle these are just hints for the engine since that has the last word on them.)
pub builtins: BTreeMap<Address, Builtin>,
/// Parameters common to all engines.
pub params: CommonParams,
/// The genesis block's parent hash field.
pub parent_hash: H256,
@ -101,15 +97,41 @@ pub struct Spec {
genesis_state: PodState,
}
#[cfg_attr(feature="dev", allow(wrong_self_convention))] // because to_engine(self) should be to_engine(&self)
impl From<ethjson::spec::Spec> for Spec {
fn from(s: ethjson::spec::Spec) -> Self {
let builtins = s.accounts.builtins().into_iter().map(|p| (p.0.into(), From::from(p.1))).collect();
let g = Genesis::from(s.genesis);
let seal: GenericSeal = g.seal.into();
let params = CommonParams::from(s.params);
Spec {
name: s.name.into(),
params: params.clone(),
engine: Spec::engine(s.engine, params, builtins),
nodes: s.nodes.unwrap_or_else(Vec::new),
parent_hash: g.parent_hash,
transactions_root: g.transactions_root,
receipts_root: g.receipts_root,
author: g.author,
difficulty: g.difficulty,
gas_limit: g.gas_limit,
gas_used: g.gas_used,
timestamp: g.timestamp,
extra_data: g.extra_data,
seal_fields: seal.fields,
seal_rlp: seal.rlp,
state_root_memo: RwLock::new(g.state_root),
genesis_state: From::from(s.accounts)
}
}
}
impl Spec {
/// Convert this object into a boxed Engine of the right underlying type.
// TODO avoid this hard-coded nastiness - use dynamic-linked plugin framework instead.
pub fn to_engine(self) -> Result<Box<Engine>, Error> {
match self.engine_name.as_ref() {
"NullEngine" => Ok(NullEngine::new_boxed(self)),
"Ethash" => Ok(ethereum::Ethash::new_boxed(self)),
_ => Err(Error::UnknownEngineName(self.engine_name.clone()))
/// Convert engine spec into a boxed Engine of the right underlying type.
/// TODO avoid this hard-coded nastiness - use dynamic-linked plugin framework instead.
fn engine(engine_spec: ethjson::spec::Engine, params: CommonParams, builtins: BTreeMap<Address, Builtin>) -> Box<Engine> {
match engine_spec {
ethjson::spec::Engine::Null => Box::new(NullEngine::new(params, builtins)),
ethjson::spec::Engine::Ethash(ethash) => Box::new(ethereum::Ethash::new(params, From::from(ethash.params), builtins))
}
}
@ -125,7 +147,7 @@ impl Spec {
pub fn nodes(&self) -> &Vec<String> { &self.nodes }
/// Get the configured Network ID.
pub fn network_id(&self) -> U256 { self.network_id }
pub fn network_id(&self) -> U256 { self.params.network_id }
/// Get the header of the genesis block.
pub fn genesis_header(&self) -> Header {
@ -168,49 +190,9 @@ impl Spec {
ret.out()
}
/// Overwrite the genesis components with the given JSON, assuming standard Ethereum test format.
pub fn overwrite_genesis(&mut self, genesis: &Json) {
let (seal_fields, seal_rlp) = {
if genesis.find("mixHash").is_some() && genesis.find("nonce").is_some() {
let mut s = RlpStream::new();
s.append(&H256::from_json(&genesis["mixHash"]));
s.append(&H64::from_json(&genesis["nonce"]));
(2, s.out())
} else {
// backup algo that will work with sealFields/sealRlp (and without).
(
u64::from_json(&genesis["sealFields"]) as usize,
Bytes::from_json(&genesis["sealRlp"])
)
}
};
self.parent_hash = H256::from_json(&genesis["parentHash"]);
self.transactions_root = genesis.find("transactionsTrie").and_then(|_| Some(H256::from_json(&genesis["transactionsTrie"]))).unwrap_or(SHA3_NULL_RLP.clone());
self.receipts_root = genesis.find("receiptTrie").and_then(|_| Some(H256::from_json(&genesis["receiptTrie"]))).unwrap_or(SHA3_NULL_RLP.clone());
self.author = Address::from_json(&genesis["coinbase"]);
self.difficulty = U256::from_json(&genesis["difficulty"]);
self.gas_limit = U256::from_json(&genesis["gasLimit"]);
self.gas_used = U256::from_json(&genesis["gasUsed"]);
self.timestamp = u64::from_json(&genesis["timestamp"]);
self.extra_data = Bytes::from_json(&genesis["extraData"]);
self.seal_fields = seal_fields;
self.seal_rlp = seal_rlp;
self.state_root_memo = RwLock::new(genesis.find("stateRoot").and_then(|_| Some(H256::from_json(&genesis["stateRoot"]))));
}
/// Overwrite the genesis components.
pub fn overwrite_genesis_params(&mut self, g: Genesis) {
let (seal_fields, seal_rlp) = match g.seal {
GenesisSeal::Generic { fields, rlp } => (fields, rlp),
GenesisSeal::Ethereum { nonce, mix_hash } => {
let mut s = RlpStream::new();
s.append(&mix_hash);
s.append(&nonce);
(2, s.out())
}
};
let seal: GenericSeal = g.seal.into();
self.parent_hash = g.parent_hash;
self.transactions_root = g.transactions_root;
self.receipts_root = g.receipts_root;
@ -220,8 +202,8 @@ impl Spec {
self.gas_used = g.gas_used;
self.timestamp = g.timestamp;
self.extra_data = g.extra_data;
self.seal_fields = seal_fields;
self.seal_rlp = seal_rlp;
self.seal_fields = seal.fields;
self.seal_rlp = seal.rlp;
self.state_root_memo = RwLock::new(g.state_root);
}
@ -235,74 +217,7 @@ impl Spec {
pub fn is_state_root_valid(&self) -> bool {
self.state_root_memo.read().unwrap().clone().map_or(true, |sr| sr == self.genesis_state.root())
}
}
impl FromJson for Spec {
/// Loads a chain-specification from a json data structure
fn from_json(json: &Json) -> Spec {
// once we commit ourselves to some json parsing library (serde?)
// move it to proper data structure
let mut builtins = BTreeMap::new();
let mut state = PodState::new();
if let Some(&Json::Object(ref accounts)) = json.find("accounts") {
for (address, acc) in accounts.iter() {
let addr = Address::from_str(address).unwrap();
if let Some(ref builtin_json) = acc.find("builtin") {
if let Some(builtin) = Builtin::from_json(builtin_json) {
builtins.insert(addr.clone(), builtin);
}
}
}
state = xjson!(&json["accounts"]);
}
let nodes = if let Some(&Json::Array(ref ns)) = json.find("nodes") {
ns.iter().filter_map(|n| if let Json::String(ref s) = *n { Some(s.clone()) } else {None}).collect()
} else { Vec::new() };
let genesis = &json["genesis"];//.as_object().expect("No genesis object in JSON");
let (seal_fields, seal_rlp) = {
if genesis.find("mixHash").is_some() && genesis.find("nonce").is_some() {
let mut s = RlpStream::new();
s.append(&H256::from_str(&genesis["mixHash"].as_string().expect("mixHash not a string.")[2..]).expect("Invalid mixHash string value"));
s.append(&H64::from_str(&genesis["nonce"].as_string().expect("nonce not a string.")[2..]).expect("Invalid nonce string value"));
(2, s.out())
} else {
// backup algo that will work with sealFields/sealRlp (and without).
(
usize::from_str(&genesis["sealFields"].as_string().unwrap_or("0x")[2..]).expect("Invalid sealFields integer data"),
genesis["sealRlp"].as_string().unwrap_or("0x")[2..].from_hex().expect("Invalid sealRlp hex data")
)
}
};
Spec {
name: json.find("name").map_or("unknown", |j| j.as_string().unwrap()).to_owned(),
engine_name: json["engineName"].as_string().unwrap().to_owned(),
engine_params: json_to_rlp_map(&json["params"]),
nodes: nodes,
network_id: U256::from_str(&json["params"]["networkID"].as_string().unwrap()[2..]).unwrap(),
builtins: builtins,
parent_hash: H256::from_str(&genesis["parentHash"].as_string().unwrap()[2..]).unwrap(),
author: Address::from_str(&genesis["author"].as_string().unwrap()[2..]).unwrap(),
difficulty: U256::from_str(&genesis["difficulty"].as_string().unwrap()[2..]).unwrap(),
gas_limit: U256::from_str(&genesis["gasLimit"].as_string().unwrap()[2..]).unwrap(),
gas_used: U256::from(0u8),
timestamp: u64::from_str(&genesis["timestamp"].as_string().unwrap()[2..]).unwrap(),
transactions_root: SHA3_NULL_RLP.clone(),
receipts_root: SHA3_NULL_RLP.clone(),
extra_data: genesis["extraData"].as_string().unwrap()[2..].from_hex().unwrap(),
genesis_state: state,
seal_fields: seal_fields,
seal_rlp: seal_rlp,
state_root_memo: RwLock::new(genesis.find("stateRoot").and_then(|_| genesis["stateRoot"].as_string()).map(|s| H256::from_str(&s[2..]).unwrap())),
}
}
}
impl Spec {
/// Ensure that the given state DB has the trie nodes in for the genesis state.
pub fn ensure_db_good(&self, db: &mut HashDB) -> bool {
if !db.contains(&self.state_root()) {
@ -321,21 +236,20 @@ impl Spec {
} else { false }
}
/// Create a new Spec from a JSON UTF-8 data resource `data`.
pub fn from_json_utf8(data: &[u8]) -> Spec {
Self::from_json_str(::std::str::from_utf8(data).unwrap())
}
/// Create a new Spec from a JSON string.
pub fn from_json_str(s: &str) -> Spec {
Self::from_json(&Json::from_str(s).expect("Json is invalid"))
/// Loads spec from json file.
pub fn load(reader: &[u8]) -> Self {
From::from(ethjson::spec::Spec::load(reader).expect("invalid json file"))
}
/// Create a new Spec which conforms to the Morden chain except that it's a NullEngine consensus.
pub fn new_test() -> Spec { Self::from_json_utf8(include_bytes!("../../res/null_morden.json")) }
pub fn new_test() -> Spec {
Spec::load(include_bytes!("../../res/null_morden.json"))
}
/// Create a new Spec which conforms to the Morden chain except that it's a NullEngine consensus.
pub fn new_homestead_test() -> Spec { Self::from_json_utf8(include_bytes!("../../res/null_homestead_morden.json")) }
pub fn new_homestead_test() -> Spec {
Spec::load(include_bytes!("../../res/null_homestead_morden.json"))
}
}
#[cfg(test)]
@ -353,7 +267,5 @@ mod tests {
assert_eq!(test_spec.state_root(), H256::from_str("f3f4696bbf3b3b07775128eb7a3763279a394e382130f27c21e70233e04946a9").unwrap());
let genesis = test_spec.genesis_block();
assert_eq!(BlockView::new(&genesis).header_view().sha3(), H256::from_str("0cd786a2425d16f152c658316c423e6ce1181e15c3295826d7c9904cba9ce303").unwrap());
let _ = test_spec.to_engine();
}
}

View File

@ -558,7 +558,7 @@ fn should_not_trace_call_transaction_to_builtin() {
let mut info = EnvInfo::default();
info.gas_limit = x!(1_000_000);
let engine = Spec::new_test().to_engine().unwrap();
let engine = Spec::new_test().engine;
let t = Transaction {
nonce: x!(0),
@ -583,7 +583,7 @@ fn should_not_trace_subcall_transaction_to_builtin() {
let mut info = EnvInfo::default();
info.gas_limit = x!(1_000_000);
let engine = Spec::new_test().to_engine().unwrap();
let engine = Spec::new_test().engine;
let t = Transaction {
nonce: x!(0),
@ -624,7 +624,7 @@ fn should_not_trace_callcode() {
let mut info = EnvInfo::default();
info.gas_limit = x!(1_000_000);
let engine = Spec::new_test().to_engine().unwrap();
let engine = Spec::new_test().engine;
let t = Transaction {
nonce: x!(0),
@ -667,7 +667,7 @@ fn should_not_trace_delegatecall() {
let mut info = EnvInfo::default();
info.gas_limit = x!(1_000_000);
info.number = 0x789b0;
let engine = Spec::new_test().to_engine().unwrap();
let engine = Spec::new_test().engine;
println!("schedule.have_delegate_call: {:?}", engine.schedule(&info).have_delegate_call);

View File

@ -40,8 +40,7 @@ fn returns_state_root_basic() {
let client_result = generate_dummy_client(6);
let client = client_result.reference();
let test_spec = get_test_spec();
let test_engine = test_spec.to_engine().unwrap();
let state_root = test_engine.spec().genesis_header().state_root;
let state_root = test_spec.genesis_header().state_root;
assert!(client.state_data(&state_root).is_some());
}

View File

@ -52,7 +52,7 @@ impl<T> GuardedTempResult<T> {
pub struct TestEngine {
factory: Factory,
spec: Spec,
engine: Box<Engine>,
max_depth: usize
}
@ -60,18 +60,29 @@ impl TestEngine {
pub fn new(max_depth: usize, factory: Factory) -> TestEngine {
TestEngine {
factory: factory,
spec: ethereum::new_frontier_test(),
engine: ethereum::new_frontier_test().engine,
max_depth: max_depth
}
}
}
impl Engine for TestEngine {
fn name(&self) -> &str { "TestEngine" }
fn spec(&self) -> &Spec { &self.spec }
fn name(&self) -> &str {
"TestEngine"
}
fn params(&self) -> &CommonParams {
self.engine.params()
}
fn builtins(&self) -> &BTreeMap<Address, Builtin> {
self.engine.builtins()
}
fn vm_factory(&self) -> &Factory {
&self.factory
}
fn schedule(&self, _env_info: &EnvInfo) -> Schedule {
let mut schedule = Schedule::new_frontier();
schedule.max_depth = self.max_depth;
@ -136,17 +147,17 @@ pub fn generate_dummy_client(block_number: u32) -> GuardedTempResult<Arc<Client>
let client = Client::new(ClientConfig::default(), get_test_spec(), dir.as_path(), IoChannel::disconnected()).unwrap();
let test_spec = get_test_spec();
let test_engine = test_spec.to_engine().unwrap();
let state_root = test_engine.spec().genesis_header().state_root;
let mut rolling_hash = test_engine.spec().genesis_header().hash();
let test_engine = &test_spec.engine;
let state_root = test_spec.genesis_header().state_root;
let mut rolling_hash = test_spec.genesis_header().hash();
let mut rolling_block_number = 1;
let mut rolling_timestamp = 40;
for _ in 0..block_number {
let mut header = Header::new();
header.gas_limit = decode(test_engine.spec().engine_params.get("minGasLimit").unwrap());
header.difficulty = decode(test_engine.spec().engine_params.get("minimumDifficulty").unwrap());
header.gas_limit = test_engine.params().min_gas_limit;
header.difficulty = U256::from(0x20000);
header.timestamp = rolling_timestamp;
header.number = rolling_block_number;
header.parent_hash = rolling_hash;
@ -171,8 +182,9 @@ pub fn generate_dummy_client(block_number: u32) -> GuardedTempResult<Arc<Client>
pub fn push_blocks_to_client(client: &Arc<Client>, timestamp_salt: u64, starting_number: usize, block_number: usize) {
let test_spec = get_test_spec();
let test_engine = test_spec.to_engine().unwrap();
let state_root = test_engine.spec().genesis_header().state_root;
let test_engine = &test_spec.engine;
//let test_engine = test_spec.to_engine().unwrap();
let state_root = test_spec.genesis_header().state_root;
let mut rolling_hash = client.chain_info().best_block_hash;
let mut rolling_block_number = starting_number as u64;
let mut rolling_timestamp = timestamp_salt + starting_number as u64 * 10;
@ -180,8 +192,8 @@ pub fn push_blocks_to_client(client: &Arc<Client>, timestamp_salt: u64, starting
for _ in 0..block_number {
let mut header = Header::new();
header.gas_limit = decode(test_engine.spec().engine_params.get("minGasLimit").unwrap());
header.difficulty = decode(test_engine.spec().engine_params.get("minimumDifficulty").unwrap());
header.gas_limit = test_engine.params().min_gas_limit;
header.difficulty = U256::from(0x20000);
header.timestamp = rolling_timestamp;
header.number = rolling_block_number;
header.parent_hash = rolling_hash;
@ -279,24 +291,23 @@ pub fn get_temp_state_in(path: &Path) -> State {
pub fn get_good_dummy_block_seq(count: usize) -> Vec<Bytes> {
let test_spec = get_test_spec();
let test_engine = test_spec.to_engine().unwrap();
get_good_dummy_block_fork_seq(1, count, &test_engine.spec().genesis_header().hash())
get_good_dummy_block_fork_seq(1, count, &test_spec.genesis_header().hash())
}
pub fn get_good_dummy_block_fork_seq(start_number: usize, count: usize, parent_hash: &H256) -> Vec<Bytes> {
let test_spec = get_test_spec();
let test_engine = test_spec.to_engine().unwrap();
let test_engine = &test_spec.engine;
let mut rolling_timestamp = start_number as u64 * 10;
let mut parent = *parent_hash;
let mut r = Vec::new();
for i in start_number .. start_number + count + 1 {
let mut block_header = Header::new();
block_header.gas_limit = decode(test_engine.spec().engine_params.get("minGasLimit").unwrap());
block_header.gas_limit = test_engine.params().min_gas_limit;
block_header.difficulty = U256::from(i).mul(U256([0, 1, 0, 0]));
block_header.timestamp = rolling_timestamp;
block_header.number = i as u64;
block_header.parent_hash = parent;
block_header.state_root = test_engine.spec().genesis_header().state_root;
block_header.state_root = test_spec.genesis_header().state_root;
parent = block_header.hash();
rolling_timestamp = rolling_timestamp + 10;
@ -310,13 +321,13 @@ pub fn get_good_dummy_block_fork_seq(start_number: usize, count: usize, parent_h
pub fn get_good_dummy_block() -> Bytes {
let mut block_header = Header::new();
let test_spec = get_test_spec();
let test_engine = test_spec.to_engine().unwrap();
block_header.gas_limit = decode(test_engine.spec().engine_params.get("minGasLimit").unwrap());
block_header.difficulty = decode(test_engine.spec().engine_params.get("minimumDifficulty").unwrap());
let test_engine = &test_spec.engine;
block_header.gas_limit = test_engine.params().min_gas_limit;
block_header.difficulty = U256::from(0x20000);
block_header.timestamp = 40;
block_header.number = 1;
block_header.parent_hash = test_engine.spec().genesis_header().hash();
block_header.state_root = test_engine.spec().genesis_header().state_root;
block_header.parent_hash = test_spec.genesis_header().hash();
block_header.state_root = test_spec.genesis_header().state_root;
create_test_block(&block_header)
}
@ -324,12 +335,12 @@ pub fn get_good_dummy_block() -> Bytes {
pub fn get_bad_state_dummy_block() -> Bytes {
let mut block_header = Header::new();
let test_spec = get_test_spec();
let test_engine = test_spec.to_engine().unwrap();
block_header.gas_limit = decode(test_engine.spec().engine_params.get("minGasLimit").unwrap());
block_header.difficulty = decode(test_engine.spec().engine_params.get("minimumDifficulty").unwrap());
let test_engine = &test_spec.engine;
block_header.gas_limit = test_engine.params().min_gas_limit;
block_header.difficulty = U256::from(0x20000);
block_header.timestamp = 40;
block_header.number = 1;
block_header.parent_hash = test_engine.spec().genesis_header().hash();
block_header.parent_hash = test_spec.genesis_header().hash();
block_header.state_root = x!(0xbad);
create_test_block(&block_header)

View File

@ -183,7 +183,7 @@ fn verify_header(header: &Header, engine: &Engine) -> Result<(), Error> {
if header.gas_used > header.gas_limit {
return Err(From::from(BlockError::TooMuchGasUsed(OutOfBounds { max: Some(header.gas_limit), min: None, found: header.gas_used })));
}
let min_gas_limit = decode(engine.spec().engine_params.get("minGasLimit").unwrap());
let min_gas_limit = engine.params().min_gas_limit;
if header.gas_limit < min_gas_limit {
return Err(From::from(BlockError::InvalidGasLimit(OutOfBounds { min: Some(min_gas_limit), max: None, found: header.gas_limit })));
}
@ -336,12 +336,10 @@ mod tests {
// Test against morden
let mut good = Header::new();
let spec = Spec::new_test();
let engine = spec.to_engine().unwrap();
let engine = &spec.engine;
let min_gas_limit = decode(engine.spec().engine_params.get("minGasLimit").unwrap());
let min_difficulty = decode(engine.spec().engine_params.get("minimumDifficulty").unwrap());
let min_gas_limit = engine.params().min_gas_limit;
good.gas_limit = min_gas_limit;
good.difficulty = min_difficulty;
good.timestamp = 40;
good.number = 10;

1
fmt.sh
View File

@ -9,6 +9,7 @@ $RUSTFMT ./json/src/lib.rs
$RUSTFMT ./miner/src/lib.rs
$RUSTFMT ./parity/main.rs
$RUSTFMT ./rpc/src/lib.rs
$RUSTFMT ./webapp/src/lib.rs
$RUSTFMT ./sync/src/lib.rs
$RUSTFMT ./util/src/lib.rs

View File

@ -7,6 +7,6 @@ echo "set -e" >> $FILE
echo "cargo build --release --features dev" >> $FILE
# Build tests
echo "cargo test --no-run --features dev \\" >> $FILE
echo " -p ethash -p ethcore-util -p ethcore -p ethsync -p ethcore-rpc -p parity -p ethminer" >> $FILE
echo " -p ethash -p ethcore-util -p ethcore -p ethsync -p ethcore-rpc -p parity -p ethminer -p ethcore-webapp" >> $FILE
echo "" >> $FILE
chmod +x $FILE

View File

@ -21,7 +21,7 @@ use hash::H256;
use blockchain::state::State;
use blockchain::header::Header;
use blockchain::block::Block;
use spec::Genesis;
use spec::{Genesis, Seal, Ethereum};
/// Blockchain deserialization.
#[derive(Debug, PartialEq, Deserialize)]
@ -54,10 +54,10 @@ impl BlockChain {
/// Returns spec compatible genesis struct.
pub fn genesis(&self) -> Genesis {
Genesis {
nonce: Some(self.genesis_block.nonce.clone()),
mix_hash: Some(self.genesis_block.mix_hash.clone()),
seal_fields: None,
seal_rlp: None,
seal: Seal::Ethereum(Ethereum {
nonce: self.genesis_block.nonce.clone(),
mix_hash: self.genesis_block.mix_hash.clone(),
}),
difficulty: self.genesis_block.difficulty,
author: self.genesis_block.author.clone(),
timestamp: self.genesis_block.timestamp,

View File

@ -22,7 +22,7 @@ use blockchain::account::Account;
/// Blockchain test state deserializer.
#[derive(Debug, PartialEq, Deserialize, Clone)]
pub struct State(pub BTreeMap<Address, Account>);
pub struct State(BTreeMap<Address, Account>);
impl IntoIterator for State {
type Item = <BTreeMap<Address, Account> as IntoIterator>::Item;

View File

@ -22,9 +22,19 @@ use spec::builtin::Builtin;
/// Spec account.
#[derive(Debug, PartialEq, Deserialize)]
pub struct Account {
builtin: Option<Builtin>,
balance: Option<Uint>,
nonce: Option<Uint>,
/// Builtin contract.
pub builtin: Option<Builtin>,
/// Balance.
pub balance: Option<Uint>,
/// Nonce.
pub nonce: Option<Uint>,
}
impl Account {
/// Returns true if account does not have nonce and balance.
pub fn is_empty(&self) -> bool {
self.balance.is_none() && self.nonce.is_none()
}
}
#[cfg(test)]

View File

@ -17,14 +17,16 @@
//! Spec builtin deserialization.
/// Linear pricing.
#[derive(Debug, PartialEq, Deserialize)]
#[derive(Debug, PartialEq, Deserialize, Clone)]
pub struct Linear {
base: u64,
word: u64,
/// Base price.
pub base: usize,
/// Price for word.
pub word: usize,
}
/// Pricing variants.
#[derive(Debug, PartialEq, Deserialize)]
#[derive(Debug, PartialEq, Deserialize, Clone)]
pub enum Pricing {
/// Linear pricing.
#[serde(rename="linear")]
@ -32,10 +34,12 @@ pub enum Pricing {
}
/// Spec builtin.
#[derive(Debug, PartialEq, Deserialize)]
#[derive(Debug, PartialEq, Deserialize, Clone)]
pub struct Builtin {
name: String,
pricing: Pricing,
/// Builtin name.
pub name: String,
/// Builtin pricing.
pub pricing: Pricing,
}
#[cfg(test)]

63
json/src/spec/engine.rs Normal file
View File

@ -0,0 +1,63 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Engine deserialization.
use serde::{Deserializer, Error};
use serde::de::Visitor;
use spec::Ethash;
/// Engine deserialization.
#[derive(Debug, PartialEq, Deserialize)]
pub enum Engine {
/// Null engine.
Null,
/// Ethash engine.
Ethash(Ethash),
}
#[cfg(test)]
mod tests {
use serde_json;
use spec::Engine;
#[test]
fn engine_deserialization() {
let s = r#"{
"Null": null
}"#;
let deserialized: Engine = serde_json::from_str(s).unwrap();
assert_eq!(Engine::Null, deserialized);
let s = r#"{
"Ethash": {
"params": {
"tieBreakingGas": false,
"gasLimitBoundDivisor": "0x0400",
"minimumDifficulty": "0x020000",
"difficultyBoundDivisor": "0x0800",
"durationLimit": "0x0d",
"blockReward": "0x4563918244F40000",
"registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b"
}
}
}"#;
let _deserialized: Engine = serde_json::from_str(s).unwrap();
}
}

75
json/src/spec/ethash.rs Normal file
View File

@ -0,0 +1,75 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Ethash params deserialization.
use uint::Uint;
use hash::Address;
/// Ethash params deserialization.
#[derive(Debug, PartialEq, Deserialize)]
pub struct EthashParams {
/// Tie breaking gas.
#[serde(rename="tieBreakingGas")]
pub tie_breaking_gas: bool,
/// Gas limit divisor.
#[serde(rename="gasLimitBoundDivisor")]
pub gas_limit_bound_divisor: Uint,
/// Minimum difficulty.
#[serde(rename="minimumDifficulty")]
pub minimum_difficulty: Uint,
/// Difficulty bound divisor.
#[serde(rename="difficultyBoundDivisor")]
pub difficulty_bound_divisor: Uint,
/// Block duration.
#[serde(rename="durationLimit")]
pub duration_limit: Uint,
/// Block reward.
#[serde(rename="blockReward")]
pub block_reward: Uint,
/// Namereg contract address.
pub registrar: Address,
}
/// Ethash engine deserialization.
#[derive(Debug, PartialEq, Deserialize)]
pub struct Ethash {
/// Ethash params.
pub params: EthashParams,
}
#[cfg(test)]
mod tests {
use serde_json;
use spec::ethash::Ethash;
#[test]
fn ethash_deserialization() {
let s = r#"{
"params": {
"tieBreakingGas": false,
"gasLimitBoundDivisor": "0x0400",
"minimumDifficulty": "0x020000",
"difficultyBoundDivisor": "0x0800",
"durationLimit": "0x0d",
"blockReward": "0x4563918244F40000",
"registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b"
}
}"#;
let _deserialized: Ethash = serde_json::from_str(s).unwrap();
}
}

View File

@ -17,27 +17,15 @@
//! Spec genesis deserialization.
use uint::Uint;
use hash::{H64, Address, H256};
use hash::{Address, H256};
use bytes::Bytes;
use spec::Seal;
/// Spec genesis.
#[derive(Debug, PartialEq, Deserialize)]
pub struct Genesis {
// old seal
/// Seal nonce.
pub nonce: Option<H64>,
#[serde(rename="mixHash")]
/// Seal mix hash.
pub mix_hash: Option<H256>,
// new seal // TODO: consider moving it to a separate seal structure
#[serde(rename="sealFields")]
/// Number of seal fields.
pub seal_fields: Option<usize>,
#[serde(rename="sealRlp")]
/// Seal rlp.
pub seal_rlp: Option<Bytes>,
/// Seal.
pub seal: Seal,
/// Difficulty.
pub difficulty: Uint,
/// Block author.
@ -77,7 +65,12 @@ mod tests {
let s = r#"{
"nonce": "0x0000000000000042",
"difficulty": "0x400000000",
"seal": {
"ethereum": {
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"nonce": "0x00006d6f7264656e"
}
},
"author": "0x0000000000000000000000000000000000000000",
"timestamp": "0x00",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",

View File

@ -21,9 +21,17 @@ pub mod builtin;
pub mod genesis;
pub mod params;
pub mod spec;
pub mod seal;
pub mod engine;
pub mod state;
pub mod ethash;
pub use self::account::Account;
pub use self::builtin::Builtin;
pub use self::builtin::{Builtin, Pricing, Linear};
pub use self::genesis::Genesis;
pub use self::params::Params;
pub use self::spec::Spec;
pub use self::seal::{Seal, Ethereum, Generic};
pub use self::engine::Engine;
pub use self::state::State;
pub use self::ethash::{Ethash, EthashParams};

View File

@ -17,34 +17,25 @@
//! Spec params deserialization.
use uint::Uint;
use hash::Address;
/// Spec params.
#[derive(Debug, PartialEq, Deserialize)]
pub struct Params {
/// Account start nonce.
#[serde(rename="accountStartNonce")]
account_start_nonce: Uint,
pub account_start_nonce: Uint,
/// Homestead transition block number.
#[serde(rename="frontierCompatibilityModeLimit")]
frontier_compatibility_mode_limit: Uint,
pub frontier_compatibility_mode_limit: Uint,
/// Maximum size of extra data.
#[serde(rename="maximumExtraDataSize")]
maximum_extra_data_size: Uint,
#[serde(rename="tieBreakingGas")]
tie_breaking_gas: bool,
#[serde(rename="minGasLimit")]
min_gas_limit: Uint,
#[serde(rename="gasLimitBoundDivisor")]
gas_limit_bound_divisor: Uint,
#[serde(rename="minimumDifficulty")]
minimum_difficulty: Uint,
#[serde(rename="difficultyBoundDivisor")]
difficulty_bound_divisor: Uint,
#[serde(rename="durationLimit")]
duration_limit: Uint,
#[serde(rename="blockReward")]
block_reward: Uint,
registrar: Address,
pub maximum_extra_data_size: Uint,
/// Network id.
#[serde(rename="networkID")]
network_id: Uint,
pub network_id: Uint,
/// Minimum gas limit.
#[serde(rename="minGasLimit")]
pub min_gas_limit: Uint,
}
#[cfg(test)]
@ -55,19 +46,13 @@ mod tests {
#[test]
fn params_deserialization() {
let s = r#"{
"accountStartNonce": "0x00",
"frontierCompatibilityModeLimit": "0x118c30",
"maximumExtraDataSize": "0x20",
"tieBreakingGas": false,
"networkID" : "0x1",
"minGasLimit": "0x1388",
"gasLimitBoundDivisor": "0x0400",
"minimumDifficulty": "0x020000",
"difficultyBoundDivisor": "0x0800",
"durationLimit": "0x0d",
"blockReward": "0x4563918244F40000",
"registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b",
"networkID" : "0x1"
"accountStartNonce": "0x00"
}"#;
let _deserialized: Params = serde_json::from_str(s).unwrap();
// TODO: validate all fields
}

73
json/src/spec/seal.rs Normal file
View File

@ -0,0 +1,73 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Spec seal deserialization.
use hash::{H64, H256};
use bytes::Bytes;
/// Ethereum seal.
#[derive(Debug, PartialEq, Deserialize)]
pub struct Ethereum {
/// Seal nonce.
pub nonce: H64,
/// Seal mix hash.
#[serde(rename="mixHash")]
pub mix_hash: H256,
}
/// Generic seal.
#[derive(Debug, PartialEq, Deserialize)]
pub struct Generic {
/// Number of fields.
pub fields: usize,
/// Their rlp.
pub rlp: Bytes,
}
/// Seal variants.
#[derive(Debug, PartialEq, Deserialize)]
pub enum Seal {
/// Ethereum seal.
#[serde(rename="ethereum")]
Ethereum(Ethereum),
/// Generic seal.
#[serde(rename="generic")]
Generic(Generic),
}
#[cfg(test)]
mod tests {
use serde_json;
use spec::Seal;
#[test]
fn builtin_deserialization() {
let s = r#"[{
"ethereum": {
"nonce": "0x0000000000000042",
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
}
},{
"generic": {
"fields": 1,
"rlp": "0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa"
}
}]"#;
let _deserialized: Vec<Seal> = serde_json::from_str(s).unwrap();
// TODO: validate all fields
}
}

View File

@ -16,21 +16,33 @@
//! Spec deserialization.
use std::collections::BTreeMap;
use hash::Address;
use spec::account::Account;
use spec::params::Params;
use spec::genesis::Genesis;
use std::io::Read;
use serde_json;
use serde_json::Error;
use spec::{Params, Genesis, Engine, State};
/// Spec deserialization.
#[derive(Debug, PartialEq, Deserialize)]
pub struct Spec {
name: String,
#[serde(rename="engineName")]
engine_name: String, // TODO: consider making it an enum
params: Params,
genesis: Genesis,
accounts: BTreeMap<Address, Account>,
/// Spec name.
pub name: String,
/// Engine.
pub engine: Engine,
/// Spec params.
pub params: Params,
/// Genesis header.
pub genesis: Genesis,
/// Genesis state.
pub accounts: State,
/// Boot nodes.
pub nodes: Option<Vec<String>>,
}
impl Spec {
/// Loads test from json.
pub fn load<R>(reader: R) -> Result<Self, Error> where R: Read {
serde_json::from_reader(reader)
}
}
#[cfg(test)]
@ -42,25 +54,34 @@ mod tests {
fn spec_deserialization() {
let s = r#"{
"name": "Morden",
"engineName": "Ethash",
"engine": {
"Ethash": {
"params": {
"accountStartNonce": "0x0100000",
"frontierCompatibilityModeLimit": "0x789b0",
"maximumExtraDataSize": "0x20",
"tieBreakingGas": false,
"minGasLimit": "0x1388",
"gasLimitBoundDivisor": "0x0400",
"minimumDifficulty": "0x020000",
"difficultyBoundDivisor": "0x0800",
"durationLimit": "0x0d",
"blockReward": "0x4563918244F40000",
"registrar": "",
"registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b"
}
}
},
"params": {
"accountStartNonce": "0x0100000",
"frontierCompatibilityModeLimit": "0x789b0",
"maximumExtraDataSize": "0x20",
"minGasLimit": "0x1388",
"networkID" : "0x2"
},
"genesis": {
"nonce": "0x00006d6f7264656e",
"seal": {
"ethereum": {
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"nonce": "0x00006d6f7264656e"
}
},
"difficulty": "0x20000",
"mixHash": "0x00000000000000000000000000000000000000647572616c65787365646c6578",
"author": "0x0000000000000000000000000000000000000000",
"timestamp": "0x00",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",

44
json/src/spec/state.rs Normal file
View File

@ -0,0 +1,44 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Blockchain test state deserializer.
use std::collections::BTreeMap;
use hash::Address;
use spec::{Account, Builtin};
/// Blockchain test state deserializer.
#[derive(Debug, PartialEq, Deserialize)]
pub struct State(BTreeMap<Address, Account>);
impl State {
/// Returns all builtins.
pub fn builtins(&self) -> BTreeMap<Address, Builtin> {
self.0
.iter()
.filter_map(|ref pair| pair.1.builtin.clone().map(|b| (pair.0.clone(), b.clone())))
.collect()
}
}
impl IntoIterator for State {
type Item = <BTreeMap<Address, Account> as IntoIterator>::Item;
type IntoIter = <BTreeMap<Address, Account> as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}

View File

@ -23,7 +23,7 @@ use util::numbers::{U256, Uint as U};
/// Lenient uint json deserialization for test json files.
#[derive(Default, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
pub struct Uint(U256);
pub struct Uint(pub U256);
impl Into<U256> for Uint {
fn into(self) -> U256 {
@ -37,9 +37,15 @@ impl Into<u64> for Uint {
}
}
impl Into<usize> for Uint {
fn into(self) -> usize {
// TODO: clean it after util conversions refactored.
u64::from(self.0) as usize
}
}
impl Into<u8> for Uint {
fn into(self) -> u8 {
<Uint as Into<u64>>::into(self) as u8
u64::from(self.0) as u8
}
}
@ -55,6 +61,10 @@ struct UintVisitor;
impl Visitor for UintVisitor {
type Value = Uint;
fn visit_u64<E>(&mut self, value: u64) -> Result<Self::Value, E> where E: Error {
Ok(Uint(U256::from(value)))
}
fn visit_str<E>(&mut self, value: &str) -> Result<Self::Value, E> where E: Error {
let value = match value.len() {
0 => U256::from(0),
@ -83,12 +93,13 @@ mod test {
#[test]
fn uint_deserialization() {
let s = r#"["0xa", "10", "", "0x"]"#;
let s = r#"["0xa", "10", "", "0x", 0]"#;
let deserialized: Vec<Uint> = serde_json::from_str(s).unwrap();
assert_eq!(deserialized, vec![
Uint(U256::from(10)),
Uint(U256::from(10)),
Uint(U256::from(0)),
Uint(U256::from(0)),
Uint(U256::from(0))
]);
}

View File

@ -105,9 +105,12 @@ pub trait MinerService : Send + Sync {
/// Get the sealing work package and if `Some`, apply some transform.
fn map_sealing_work<F, T>(&self, chain: &BlockChainClient, f: F) -> Option<T> where F: FnOnce(&ClosedBlock) -> T;
/// Query pending transactions for hash
/// Query pending transactions for hash.
fn transaction(&self, hash: &H256) -> Option<SignedTransaction>;
/// Get a list of all pending transactions.
fn pending_transactions(&self) -> Vec<SignedTransaction>;
/// Returns highest transaction nonce for given address.
fn last_nonce(&self, address: &Address) -> Option<U256>;

View File

@ -228,6 +228,11 @@ impl MinerService for Miner {
queue.find(hash)
}
fn pending_transactions(&self) -> Vec<SignedTransaction> {
let queue = self.transaction_queue.lock().unwrap();
queue.top_transactions()
}
fn last_nonce(&self, address: &Address) -> Option<U256> {
self.transaction_queue.lock().unwrap().last_nonce(address)
}

View File

@ -42,6 +42,8 @@ extern crate rpassword;
#[cfg(feature = "rpc")]
extern crate ethcore_rpc as rpc;
#[cfg(feature = "webapp")]
extern crate ethcore_webapp as webapp;
use std::io::{BufRead, BufReader};
use std::fs::File;
@ -63,6 +65,10 @@ use ethminer::{Miner, MinerService};
use docopt::Docopt;
use daemonize::Daemonize;
use number_prefix::{binary_prefix, Standalone, Prefixed};
#[cfg(feature = "rpc")]
use rpc::Server as RpcServer;
#[cfg(feature = "webapp")]
use webapp::Listening as WebappServer;
mod price_info;
@ -98,7 +104,8 @@ Protocol Options:
--identity NAME Specify your node's name.
Account Options:
--unlock ACCOUNT Unlock ACCOUNT for the duration of the execution.
--unlock ACCOUNTS Unlock ACCOUNTS for the duration of the execution.
ACCOUNTS is a comma-delimited list of addresses.
--password FILE Provide a file containing a password for unlocking
an account.
@ -118,7 +125,7 @@ Networking Options:
string or input to SHA3 operation.
API and Console Options:
-j --jsonrpc Enable the JSON-RPC API sever.
-j --jsonrpc Enable the JSON-RPC API server.
--jsonrpc-interface IP Specify the hostname portion of the JSONRPC API
server, IP should be an interface's IP address, or
all (all interfaces) or local [default: local].
@ -130,6 +137,19 @@ API and Console Options:
interface. APIS is a comma-delimited list of API
name. Possible name are web3, eth and net.
[default: web3,eth,net,personal].
-w --webapp Enable the web applications server (e.g.
status page).
--webapp-port PORT Specify the port portion of the WebApps server
[default: 8080].
--webapp-interface IP Specify the hostname portion of the WebApps
server, IP should be an interface's IP address, or
all (all interfaces) or local [default: local].
--webapp-user USERNAME Specify username for WebApps server. It will be
used in HTTP Basic Authentication Scheme.
If --webapp-pass is not specified you will be
asked for password on startup.
--webapp-pass PASSWORD Specify password for WebApps server. Use only in
conjunction with --webapp-user.
Sealing/Mining Options:
--usd-per-tx USD Amount of USD to be paid for a basic transaction
@ -194,7 +214,7 @@ struct Args {
flag_chain: String,
flag_db_path: String,
flag_identity: String,
flag_unlock: Vec<String>,
flag_unlock: Option<String>,
flag_password: Vec<String>,
flag_cache: Option<usize>,
flag_keys_path: String,
@ -214,6 +234,11 @@ struct Args {
flag_jsonrpc_port: u16,
flag_jsonrpc_cors: String,
flag_jsonrpc_apis: String,
flag_webapp: bool,
flag_webapp_port: u16,
flag_webapp_interface: String,
flag_webapp_user: Option<String>,
flag_webapp_pass: Option<String>,
flag_author: String,
flag_usd_per_tx: String,
flag_usd_per_eth: String,
@ -270,10 +295,10 @@ fn setup_rpc_server(
sync: Arc<EthSync>,
secret_store: Arc<AccountService>,
miner: Arc<Miner>,
url: &str,
url: &SocketAddr,
cors_domain: &str,
apis: Vec<&str>
) -> Option<Arc<PanicHandler>> {
apis: Vec<&str>,
) -> RpcServer {
use rpc::v1::*;
let server = rpc::RpcServer::new();
@ -291,9 +316,50 @@ fn setup_rpc_server(
}
}
}
Some(server.start_http(url, cors_domain, ::num_cpus::get()))
let start_result = server.start_http(url, cors_domain);
match start_result {
Err(rpc::RpcServerError::IoError(err)) => die_with_io_error(err),
Err(e) => die!("{:?}", e),
Ok(server) => server,
}
}
#[cfg(feature = "webapp")]
fn setup_webapp_server(
client: Arc<Client>,
sync: Arc<EthSync>,
secret_store: Arc<AccountService>,
miner: Arc<Miner>,
url: &str,
auth: Option<(String, String)>,
) -> WebappServer {
use rpc::v1::*;
let server = webapp::WebappServer::new();
server.add_delegate(Web3Client::new().to_delegate());
server.add_delegate(NetClient::new(&sync).to_delegate());
server.add_delegate(EthClient::new(&client, &sync, &secret_store, &miner).to_delegate());
server.add_delegate(EthFilterClient::new(&client, &miner).to_delegate());
server.add_delegate(PersonalClient::new(&secret_store).to_delegate());
let start_result = match auth {
None => {
server.start_unsecure_http(url, ::num_cpus::get())
},
Some((username, password)) => {
server.start_basic_auth_http(url, ::num_cpus::get(), &username, &password)
},
};
match start_result {
Err(webapp::WebappServerError::IoError(err)) => die_with_io_error(err),
Err(e) => die!("{:?}", e),
Ok(handle) => handle,
}
}
#[cfg(not(feature = "rpc"))]
struct RpcServer;
#[cfg(not(feature = "rpc"))]
fn setup_rpc_server(
_client: Arc<Client>,
@ -302,9 +368,24 @@ fn setup_rpc_server(
_miner: Arc<Miner>,
_url: &str,
_cors_domain: &str,
_apis: Vec<&str>
) -> Option<Arc<PanicHandler>> {
None
_apis: Vec<&str>,
) -> ! {
die!("Your Parity version has been compiled without JSON-RPC support.")
}
#[cfg(not(feature = "webapp"))]
struct WebappServer;
#[cfg(not(feature = "webapp"))]
fn setup_webapp_server(
_client: Arc<Client>,
_sync: Arc<EthSync>,
_secret_store: Arc<AccountService>,
_miner: Arc<Miner>,
_url: &str,
_auth: Option<(String, String)>,
) -> ! {
die!("Your Parity version has been compiled without WebApps support.")
}
fn print_version() {
@ -396,7 +477,7 @@ impl Configuration {
"frontier" | "homestead" | "mainnet" => ethereum::new_frontier(),
"morden" | "testnet" => ethereum::new_morden(),
"olympic" => ethereum::new_olympic(),
f => Spec::from_json_utf8(contents(f).unwrap_or_else(|_| {
f => Spec::load(contents(f).unwrap_or_else(|_| {
die!("{}: Couldn't read chain specification file. Sure it exists?", f)
}).as_ref()),
}
@ -537,9 +618,9 @@ impl Configuration {
.collect::<Vec<_>>()
.into_iter()
}).collect::<Vec<_>>();
let account_service = AccountService::new_in(Path::new(&self.keys_path()));
for d in &self.args.flag_unlock {
if let Some(ref unlocks) = self.args.flag_unlock {
for d in unlocks.split(',') {
let a = Address::from_str(clean_0x(&d)).unwrap_or_else(|_| {
die!("{}: Invalid address for --unlock. Must be 40 hex characters, without the 0x at the beginning.", d)
});
@ -547,6 +628,7 @@ impl Configuration {
die!("No password given to unlock account {}. Pass the password using `--password`.", a);
}
}
}
account_service
}
@ -567,7 +649,10 @@ impl Configuration {
let account_service = Arc::new(self.account_service());
// Build client
let mut service = ClientService::start(self.client_config(), spec, net_settings, &Path::new(&self.path())).unwrap();
let mut service = ClientService::start(
self.client_config(), spec, net_settings, &Path::new(&self.path())
).unwrap_or_else(|e| die_with_error(e));
panic_handler.forward_from(&service);
let client = service.client();
@ -582,7 +667,8 @@ impl Configuration {
let sync = EthSync::register(service.network(), sync_config, client.clone(), miner.clone());
// Setup rpc
if self.args.flag_jsonrpc || self.args.flag_rpc {
let rpc_server = if self.args.flag_jsonrpc || self.args.flag_rpc {
let apis = self.args.flag_rpcapi.as_ref().unwrap_or(&self.args.flag_jsonrpc_apis);
let url = format!("{}:{}",
match self.args.flag_rpcaddr.as_ref().unwrap_or(&self.args.flag_jsonrpc_interface).as_str() {
"all" => "0.0.0.0",
@ -591,23 +677,53 @@ impl Configuration {
},
self.args.flag_rpcport.unwrap_or(self.args.flag_jsonrpc_port)
);
SocketAddr::from_str(&url).unwrap_or_else(|_| die!("{}: Invalid JSONRPC listen host/port given.", url));
let cors = self.args.flag_rpccorsdomain.as_ref().unwrap_or(&self.args.flag_jsonrpc_cors);
// TODO: use this as the API list.
let apis = self.args.flag_rpcapi.as_ref().unwrap_or(&self.args.flag_jsonrpc_apis);
let server_handler = setup_rpc_server(
let addr = SocketAddr::from_str(&url).unwrap_or_else(|_| die!("{}: Invalid JSONRPC listen host/port given.", url));
let cors_domain = self.args.flag_rpccorsdomain.as_ref().unwrap_or(&self.args.flag_jsonrpc_cors);
Some(setup_rpc_server(
service.client(),
sync.clone(),
account_service.clone(),
miner.clone(),
&addr,
&cors_domain,
apis.split(',').collect()
))
} else {
None
};
let webapp_server = if self.args.flag_webapp {
let url = format!("{}:{}",
match self.args.flag_webapp_interface.as_str() {
"all" => "0.0.0.0",
"local" => "127.0.0.1",
x => x,
},
self.args.flag_webapp_port
);
let auth = self.args.flag_webapp_user.as_ref().map(|username| {
let password = self.args.flag_webapp_pass.as_ref().map_or_else(|| {
use rpassword::read_password;
println!("Type password for WebApps server (user: {}): ", username);
let pass = read_password().unwrap();
println!("OK, got it. Starting server...");
pass
}, |pass| pass.to_owned());
(username.to_owned(), password)
});
Some(setup_webapp_server(
service.client(),
sync.clone(),
account_service.clone(),
miner.clone(),
&url,
cors,
apis.split(',').collect()
);
if let Some(handler) = server_handler {
panic_handler.forward_from(handler.deref());
}
}
auth,
))
} else {
None
};
// Register IO handler
let io_handler = Arc::new(ClientIoHandler {
@ -619,11 +735,11 @@ impl Configuration {
service.io().register_handler(io_handler).expect("Error registering IO handler");
// Handle exit
wait_for_exit(panic_handler);
wait_for_exit(panic_handler, rpc_server, webapp_server);
}
}
fn wait_for_exit(panic_handler: Arc<PanicHandler>) {
fn wait_for_exit(panic_handler: Arc<PanicHandler>, _rpc_server: Option<RpcServer>, _webapp_server: Option<WebappServer>) {
let exit = Arc::new(Condvar::new());
// Handle possible exits
@ -637,6 +753,30 @@ fn wait_for_exit(panic_handler: Arc<PanicHandler>) {
// Wait for signal
let mutex = Mutex::new(());
let _ = exit.wait(mutex.lock().unwrap()).unwrap();
info!("Finishing work, please wait...");
}
fn die_with_error(e: ethcore::error::Error) -> ! {
use ethcore::error::Error;
match e {
Error::Util(UtilError::StdIo(e)) => die_with_io_error(e),
_ => die!("{:?}", e),
}
}
fn die_with_io_error(e: std::io::Error) -> ! {
match e.kind() {
std::io::ErrorKind::PermissionDenied => {
die!("No permissions to bind to specified port.")
},
std::io::ErrorKind::AddrInUse => {
die!("Specified address is already in use. Please make sure that nothing is listening on the same port or try using a different one.")
},
std::io::ErrorKind::AddrNotAvailable => {
die!("Could not use specified interface or given address is invalid.")
},
_ => die!("{:?}", e),
}
}
fn main() {

View File

@ -13,7 +13,7 @@ log = "0.3"
serde = "0.7.0"
serde_json = "0.7.0"
jsonrpc-core = "2.0"
jsonrpc-http-server = "3.0"
jsonrpc-http-server = { git = "https://github.com/debris/jsonrpc-http-server.git" }
ethcore-util = { path = "../util" }
ethcore = { path = "../ethcore" }
ethash = { path = "../ethash" }

View File

@ -33,10 +33,10 @@ extern crate ethminer;
extern crate transient_hashmap;
use std::sync::Arc;
use std::thread;
use util::panics::PanicHandler;
use std::net::SocketAddr;
use self::jsonrpc_core::{IoHandler, IoDelegate};
pub use jsonrpc_http_server::{Server, RpcServerError};
pub mod v1;
/// Http server.
@ -45,7 +45,7 @@ pub struct RpcServer {
}
impl RpcServer {
/// Construct new http server object with given number of threads.
/// Construct new http server object.
pub fn new() -> RpcServer {
RpcServer {
handler: Arc::new(IoHandler::new()),
@ -57,18 +57,9 @@ impl RpcServer {
self.handler.add_delegate(delegate);
}
/// Start server asynchronously in new thread and returns panic handler.
pub fn start_http(&self, addr: &str, cors_domain: &str, threads: usize) -> Arc<PanicHandler> {
let addr = addr.to_owned();
/// Start server asynchronously and returns result with `Server` handle on success or an error.
pub fn start_http(&self, addr: &SocketAddr, cors_domain: &str) -> Result<Server, RpcServerError> {
let cors_domain = cors_domain.to_owned();
let panic_handler = PanicHandler::new_in_arc();
let ph = panic_handler.clone();
let server = jsonrpc_http_server::Server::new(self.handler.clone());
thread::Builder::new().name("jsonrpc_http".to_string()).spawn(move || {
ph.catch_panic(move || {
server.start(addr.as_ref(), jsonrpc_http_server::AccessControlAllowOrigin::Value(cors_domain), threads);
}).unwrap()
}).expect("Error while creating jsonrpc http thread");
panic_handler
Server::start(addr, self.handler.clone(), jsonrpc_http_server::AccessControlAllowOrigin::Value(cors_domain))
}
}

View File

@ -61,7 +61,6 @@ impl<F, T> PollManager<F, T> where T: Timer {
}
// Implementation is always using `poll_mut`
#[cfg(test)]
/// Get a reference to stored poll filter
pub fn poll(&mut self, id: &PollId) -> Option<&F> {
self.polls.prune();

View File

@ -186,7 +186,7 @@ impl<C, S, A, M, EM> EthClient<C, S, A, M, EM>
}.fake_sign(from))
}
fn dispatch_transaction(&self, signed_transaction: SignedTransaction, raw_transaction: Vec<u8>) -> Result<Value, Error> {
fn dispatch_transaction(&self, signed_transaction: SignedTransaction) -> Result<Value, Error> {
let hash = signed_transaction.hash();
let import = {
@ -203,7 +203,6 @@ impl<C, S, A, M, EM> EthClient<C, S, A, M, EM>
match import.into_iter().collect::<Result<Vec<_>, _>>() {
Ok(_) => {
take_weak!(self.sync).new_transaction(raw_transaction);
to_value(&hash)
}
Err(e) => {
@ -466,7 +465,7 @@ impl<C, S, A, M, EM> Eth for EthClient<C, S, A, M, EM>
fn submit_work(&self, params: Params) -> Result<Value, Error> {
from_params::<(H64, H256, H256)>(params).and_then(|(nonce, pow_hash, mix_hash)| {
// trace!("Decoded: nonce={}, pow_hash={}, mix_hash={}", nonce, pow_hash, mix_hash);
trace!(target: "miner", "submit_work: Decoded: nonce={}, pow_hash={}, mix_hash={}", nonce, pow_hash, mix_hash);
let miner = take_weak!(self.miner);
let client = take_weak!(self.client);
let seal = vec![encode(&mix_hash).to_vec(), encode(&nonce).to_vec()];
@ -504,8 +503,7 @@ impl<C, S, A, M, EM> Eth for EthClient<C, S, A, M, EM>
data: request.data.map_or_else(Vec::new, |d| d.to_vec()),
}.sign(&secret)
};
let raw_transaction = encode(&signed_transaction).to_vec();
self.dispatch_transaction(signed_transaction, raw_transaction)
self.dispatch_transaction(signed_transaction)
},
Err(_) => { to_value(&H256::zero()) }
}
@ -517,7 +515,7 @@ impl<C, S, A, M, EM> Eth for EthClient<C, S, A, M, EM>
.and_then(|(raw_transaction, )| {
let raw_transaction = raw_transaction.to_vec();
match UntrustedRlp::new(&raw_transaction).as_val() {
Ok(signed_transaction) => self.dispatch_transaction(signed_transaction, raw_transaction),
Ok(signed_transaction) => self.dispatch_transaction(signed_transaction),
Err(_) => to_value(&H256::zero()),
}
})
@ -638,10 +636,11 @@ impl<C, M> EthFilter for EthFilterClient<C, M>
to_value(&diff)
},
PollFilter::Logs(ref mut block_number, ref mut filter) => {
PollFilter::Logs(ref mut block_number, ref filter) => {
let mut filter = filter.clone();
filter.from_block = BlockId::Number(*block_number);
filter.to_block = BlockId::Latest;
let logs = client.logs(filter.clone())
let logs = client.logs(filter)
.into_iter()
.map(From::from)
.collect::<Vec<Log>>();
@ -656,6 +655,24 @@ impl<C, M> EthFilter for EthFilterClient<C, M>
})
}
fn filter_logs(&self, params: Params) -> Result<Value, Error> {
from_params::<(Index,)>(params)
.and_then(|(index,)| {
let mut polls = self.polls.lock().unwrap();
match polls.poll(&index.value()) {
Some(&PollFilter::Logs(ref _block_number, ref filter)) => {
let logs = take_weak!(self.client).logs(filter.clone())
.into_iter()
.map(From::from)
.collect::<Vec<Log>>();
to_value(&logs)
},
// just empty array
_ => Ok(Value::Array(vec![] as Vec<Value>)),
}
})
}
fn uninstall_filter(&self, params: Params) -> Result<Value, Error> {
from_params::<(Index,)>(params)
.and_then(|(index,)| {

View File

@ -98,6 +98,10 @@ impl MinerService for TestMinerService {
self.pending_transactions.lock().unwrap().get(hash).cloned()
}
fn pending_transactions(&self) -> Vec<SignedTransaction> {
self.pending_transactions.lock().unwrap().values().cloned().collect()
}
fn last_nonce(&self, address: &Address) -> Option<U256> {
self.last_nonces.read().unwrap().get(address).cloned()
}

View File

@ -16,7 +16,7 @@
//! Test implementation of SyncProvider.
use util::{U256, Bytes};
use util::{U256};
use ethsync::{SyncProvider, SyncStatus, SyncState};
use std::sync::{RwLock};
@ -59,8 +59,5 @@ impl SyncProvider for TestSyncProvider {
fn status(&self) -> SyncStatus {
self.status.read().unwrap().clone()
}
fn new_transaction(&self, _raw_transaction: Bytes) {
}
}

View File

@ -190,7 +190,7 @@ pub trait EthFilter: Sized + Send + Sync + 'static {
/// Returns filter changes since last poll.
fn filter_changes(&self, _: Params) -> Result<Value, Error> { rpc_unimplemented!() }
/// Returns filter logs.
/// Returns all logs matching given filter (in a range 'from' - 'to').
fn filter_logs(&self, _: Params) -> Result<Value, Error> { rpc_unimplemented!() }
/// Uninstalls filter.

View File

@ -217,10 +217,6 @@ pub struct ChainSync {
network_id: U256,
/// Miner
miner: Arc<Miner>,
/// Transactions to propagate
// TODO: reconsider where this is in the codebase - seems a little dodgy to have here.
transactions_to_send: Vec<Bytes>,
}
type RlpResponseResult = Result<Option<(PacketId, RlpStream)>, PacketDecodeError>;
@ -247,7 +243,6 @@ impl ChainSync {
max_download_ahead_blocks: max(MAX_HEADERS_TO_REQUEST, config.max_download_ahead_blocks),
network_id: config.network_id,
miner: miner,
transactions_to_send: vec![],
}
}
@ -950,11 +945,6 @@ impl ChainSync {
}
}
/// Place a new transaction on the wire.
pub fn new_transaction(&mut self, raw_transaction: Bytes) {
self.transactions_to_send.push(raw_transaction);
}
/// Called when peer sends us new transactions
fn on_peer_transactions(&mut self, io: &mut SyncIo, peer_id: PeerId, r: &UntrustedRlp) -> Result<(), PacketDecodeError> {
// accepting transactions once only fully synced
@ -1296,11 +1286,16 @@ impl ChainSync {
return 0;
}
let mut packet = RlpStream::new_list(self.transactions_to_send.len());
for tx in &self.transactions_to_send {
packet.append_raw(tx, 1);
let mut transactions = self.miner.pending_transactions();
if transactions.is_empty() {
return 0;
}
let mut packet = RlpStream::new_list(transactions.len());
let tx_count = transactions.len();
for tx in transactions.drain(..) {
packet.append(&tx);
}
self.transactions_to_send.clear();
let rlp = packet.out();
let lucky_peers = {
@ -1319,13 +1314,12 @@ impl ChainSync {
for peer_id in lucky_peers {
self.send_packet(io, peer_id, TRANSACTIONS_PACKET, rlp.clone());
}
trace!(target: "sync", "Sent {} transactions to {} peers.", tx_count, sent);
sent
}
fn propagate_latest_blocks(&mut self, io: &mut SyncIo) {
if !self.transactions_to_send.is_empty() {
self.propagate_new_transactions(io);
}
let chain_info = io.chain().chain_info();
if (((chain_info.best_block_number as i64) - (self.last_sent_block_number as i64)).abs() as BlockNumber) < MAX_PEER_LAG_PROPAGATION {
let blocks = self.propagate_blocks(&chain_info, io);

View File

@ -66,7 +66,7 @@ use std::ops::*;
use std::sync::*;
use util::network::{NetworkProtocolHandler, NetworkService, NetworkContext, PeerId};
use util::TimerToken;
use util::{U256, Bytes, ONE_U256};
use util::{U256, ONE_U256};
use ethcore::client::Client;
use ethcore::service::SyncMessage;
use ethminer::Miner;
@ -101,9 +101,6 @@ impl Default for SyncConfig {
pub trait SyncProvider: Send + Sync {
/// Get sync status
fn status(&self) -> SyncStatus;
/// Note that a user has submitted a new transaction.
fn new_transaction(&self, raw_transaction: Bytes);
}
/// Ethereum network protocol handler
@ -143,11 +140,6 @@ impl SyncProvider for EthSync {
fn status(&self) -> SyncStatus {
self.sync.read().unwrap().status()
}
/// Note that a user has submitted a new transaction.
fn new_transaction(&self, raw_transaction: Bytes) {
self.sync.write().unwrap().new_transaction(raw_transaction);
}
}
impl NetworkProtocolHandler<SyncMessage> for EthSync {

View File

@ -7,6 +7,7 @@ cargo test --features ethcore/json-tests $1 \
-p ethcore \
-p ethsync \
-p ethcore-rpc \
-p ethcore-webapp \
-p parity \
-p ethminer \
-p bigint

View File

@ -376,8 +376,10 @@ impl<Message> IoService<Message> where Message: Send + Sync + Clone + 'static {
impl<Message> Drop for IoService<Message> where Message: Send + Sync + Clone {
fn drop(&mut self) {
trace!(target: "shutdown", "[IoService] Closing...");
self.host_channel.send(IoMessage::Shutdown).unwrap();
self.thread.take().unwrap().join().ok();
trace!(target: "shutdown", "[IoService] Closed.");
}
}

View File

@ -120,10 +120,12 @@ impl Worker {
impl Drop for Worker {
fn drop(&mut self) {
trace!(target: "shutdown", "[IoWorker] Closing...");
let _ = self.wait_mutex.lock();
self.deleting.store(true, AtomicOrdering::Release);
self.wait.notify_all();
let thread = mem::replace(&mut self.thread, None).unwrap();
thread.join().ok();
trace!(target: "shutdown", "[IoWorker] Closed");
}
}

View File

@ -121,22 +121,7 @@ impl AccountProvider for AccountService {
}
}
impl Default for AccountService {
fn default() -> Self {
AccountService::new()
}
}
impl AccountService {
/// New account service with the keys store in default location
pub fn new() -> Self {
let secret_store = RwLock::new(SecretStore::new());
secret_store.write().unwrap().try_import_existing();
AccountService {
secret_store: secret_store
}
}
/// New account service with the keys store in specific location
pub fn new_in(path: &Path) -> Self {
let secret_store = RwLock::new(SecretStore::new_in(path));
@ -165,25 +150,10 @@ impl AccountService {
}
}
impl Default for SecretStore {
fn default() -> Self {
SecretStore::new()
}
}
impl SecretStore {
/// new instance of Secret Store in default home directory
pub fn new() -> Self {
let mut path = ::std::env::home_dir().expect("Failed to get home dir");
path.push(".parity");
path.push("keys");
::std::fs::create_dir_all(&path).expect("Should panic since it is critical to be able to access home dir");
Self::new_in(&path)
}
/// new instance of Secret Store in specific directory
pub fn new_in(path: &Path) -> Self {
::std::fs::create_dir_all(&path).expect("Cannot access requested key directory - critical");
SecretStore {
directory: KeyDirectory::new(path),
unlocks: RwLock::new(HashMap::new()),

26
webapp/Cargo.toml Normal file
View File

@ -0,0 +1,26 @@
[package]
description = "Parity WebApplications crate"
name = "ethcore-webapp"
version = "1.1.0"
license = "GPL-3.0"
authors = ["Ethcore <admin@ethcore.io"]
[lib]
[dependencies]
log = "0.3"
jsonrpc-core = "2.0"
jsonrpc-http-server = { git = "https://github.com/tomusdrw/jsonrpc-http-server.git", branch="old-hyper" }
hyper = { version = "0.8", default-features = false }
iron = { version = "0.3" }
ethcore-rpc = { path = "../rpc" }
ethcore-util = { path = "../util" }
parity-webapp = { git = "https://github.com/tomusdrw/parity-webapp.git" }
# List of apps
parity-status = { git = "https://github.com/tomusdrw/parity-status.git", version = "0.1.5" }
parity-wallet = { git = "https://github.com/tomusdrw/parity-wallet.git", optional = true }
clippy = { version = "0.0.61", optional = true}
[features]
default = ["parity-wallet"]
dev = ["clippy", "ethcore-rpc/dev", "ethcore-util/dev"]

41
webapp/src/apps.rs Normal file
View File

@ -0,0 +1,41 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use std::collections::HashMap;
use page::{Page, PageHandler};
extern crate parity_status;
extern crate parity_wallet;
pub type Pages = HashMap<String, Box<Page>>;
pub fn main_page() -> Box<Page> {
Box::new(PageHandler { app: parity_status::App::default() })
}
pub fn all_pages() -> Pages {
let mut pages = Pages::new();
wallet_page(&mut pages);
pages
}
#[cfg(feature = "parity-wallet")]
fn wallet_page(pages: &mut Pages) {
pages.insert("wallet".to_owned(), Box::new(PageHandler { app: parity_wallet::App::default() }));
}
#[cfg(not(feature = "parity-wallet"))]
fn wallet_page(_pages: &mut Pages) {}

112
webapp/src/lib.rs Normal file
View File

@ -0,0 +1,112 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Ethcore Webapplications for Parity
#![warn(missing_docs)]
#![cfg_attr(feature="nightly", plugin(clippy))]
#[macro_use]
extern crate log;
extern crate hyper;
extern crate iron;
extern crate jsonrpc_core;
extern crate jsonrpc_http_server;
extern crate ethcore_rpc as rpc;
extern crate parity_webapp;
use std::sync::Arc;
use self::jsonrpc_core::{IoHandler, IoDelegate};
use jsonrpc_http_server::ServerHandler;
mod apps;
mod page;
mod router;
use router::auth::{Authorization, NoAuth, HttpBasicAuth};
/// Http server.
pub struct WebappServer {
handler: Arc<IoHandler>,
}
impl WebappServer {
/// Construct new http server object
pub fn new() -> Self {
WebappServer {
handler: Arc::new(IoHandler::new()),
}
}
/// Add io delegate.
pub fn add_delegate<D>(&self, delegate: IoDelegate<D>) where D: Send + Sync + 'static {
self.handler.add_delegate(delegate);
}
/// Asynchronously start server with no authentication,
/// return result with `Listening` handle on success or an error.
pub fn start_unsecure_http(&self, addr: &str, threads: usize) -> Result<Listening, WebappServerError> {
self.start_http(addr, threads, NoAuth)
}
/// Asynchronously start server with `HTTP Basic Authentication`,
/// return result with `Listening` handle on success or an error.
pub fn start_basic_auth_http(&self, addr: &str, threads: usize, username: &str, password: &str) -> Result<Listening, WebappServerError> {
self.start_http(addr, threads, HttpBasicAuth::single_user(username, password))
}
fn start_http<A: Authorization + 'static>(&self, addr: &str, threads: usize, authorization: A) -> Result<Listening, WebappServerError> {
let addr = addr.to_owned();
let handler = self.handler.clone();
let cors_domain = jsonrpc_http_server::AccessControlAllowOrigin::Null;
let rpc = ServerHandler::new(handler, cors_domain);
let router = router::Router::new(rpc, apps::main_page(), apps::all_pages(), authorization);
try!(hyper::Server::http(addr.as_ref() as &str))
.handle_threads(router, threads)
.map(|l| Listening { listening: l })
.map_err(WebappServerError::from)
}
}
/// Listening handle
pub struct Listening {
listening: hyper::server::Listening
}
impl Drop for Listening {
fn drop(&mut self) {
self.listening.close().unwrap();
}
}
/// Webapp Server startup error
#[derive(Debug)]
pub enum WebappServerError {
/// Wrapped `std::io::Error`
IoError(std::io::Error),
/// Other `hyper` error
Other(hyper::error::Error),
}
impl From<hyper::error::Error> for WebappServerError {
fn from(err: hyper::error::Error) -> Self {
match err {
hyper::error::Error::Io(e) => WebappServerError::IoError(e),
e => WebappServerError::Other(e),
}
}
}

67
webapp/src/page/mod.rs Normal file
View File

@ -0,0 +1,67 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use std::io::Write;
use hyper::uri::RequestUri;
use hyper::server;
use hyper::header;
use hyper::status::StatusCode;
use parity_webapp::WebApp;
pub trait Page : Send + Sync {
fn serve_file(&self, mut path: &str, mut res: server::Response);
}
pub struct PageHandler<T : WebApp> {
pub app: T,
}
impl<T: WebApp> Page for PageHandler<T> {
fn serve_file(&self, mut path: &str, mut res: server::Response) {
// Support index file
if path == "" {
path = "index.html"
}
let file = self.app.file(path);
if let Some(f) = file {
*res.status_mut() = StatusCode::Ok;
res.headers_mut().set(header::ContentType(f.content_type.parse().unwrap()));
let _ = match res.start() {
Ok(mut raw_res) => {
for chunk in f.content.chunks(1024 * 20) {
let _ = raw_res.write(chunk);
}
raw_res.end()
},
Err(_) => {
println!("Error while writing response.");
Ok(())
},
};
}
}
}
impl server::Handler for Page {
fn handle(&self, req: server::Request, mut res: server::Response) {
*res.status_mut() = StatusCode::NotFound;
if let RequestUri::AbsolutePath(ref path) = req.uri {
self.serve_file(path, res);
}
}
}

53
webapp/src/router/api.rs Normal file
View File

@ -0,0 +1,53 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Simple REST API
use std::sync::Arc;
use hyper;
use hyper::status::StatusCode;
use hyper::header;
use hyper::uri::RequestUri::AbsolutePath as Path;
use apps::Pages;
pub struct RestApi {
pub pages: Arc<Pages>,
}
impl RestApi {
fn list_pages(&self) -> String {
let mut s = "[".to_owned();
for name in self.pages.keys() {
s.push_str(&format!("\"{}\",", name));
}
s.push_str("\"rpc\"");
s.push_str("]");
s
}
}
impl hyper::server::Handler for RestApi {
fn handle<'b, 'a>(&'a self, req: hyper::server::Request<'a, 'b>, mut res: hyper::server::Response<'a>) {
match req.uri {
Path(ref path) if path == "apps" => {
*res.status_mut() = StatusCode::Ok;
res.headers_mut().set(header::ContentType("application/json".parse().unwrap()));
let _ = res.send(self.list_pages().as_bytes());
},
_ => (),
}
}
}

112
webapp/src/router/auth.rs Normal file
View File

@ -0,0 +1,112 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! HTTP Authorization implementations
use std::collections::HashMap;
use hyper::{header, server};
use hyper::status::StatusCode;
/// Authorization result
pub enum Authorized<'a, 'b> where 'b : 'a {
/// Authorization was successful. Request and Response are returned for further processing.
Yes(server::Request<'a, 'b>, server::Response<'a>),
/// Unsuccessful authorization. Request and Response has been consumed.
No,
}
/// Authorization interface
pub trait Authorization : Send + Sync {
/// Handle authorization process and return `Request` and `Response` when authorization is successful.
fn handle<'b, 'a>(&'a self, req: server::Request<'a, 'b>, res: server::Response<'a>)-> Authorized<'a, 'b>;
}
/// HTTP Basic Authorization handler
pub struct HttpBasicAuth {
users: HashMap<String, String>,
}
/// No-authorization implementation (authorization disabled)
pub struct NoAuth;
impl Authorization for NoAuth {
fn handle<'b, 'a>(&'a self, req: server::Request<'a, 'b>, res: server::Response<'a>)-> Authorized<'a, 'b> {
Authorized::Yes(req, res)
}
}
impl Authorization for HttpBasicAuth {
fn handle<'b, 'a>(&'a self, req: server::Request<'a, 'b>, res: server::Response<'a>)-> Authorized<'a, 'b> {
let auth = self.check_auth(&req);
match auth {
Access::Denied => {
self.respond_with_unauthorized(res);
Authorized::No
},
Access::AuthRequired => {
self.respond_with_auth_required(res);
Authorized::No
},
Access::Granted => {
Authorized::Yes(req, res)
},
}
}
}
enum Access {
Granted,
Denied,
AuthRequired,
}
impl HttpBasicAuth {
/// Creates `HttpBasicAuth` instance with only one user.
pub fn single_user(username: &str, password: &str) -> Self {
let mut users = HashMap::new();
users.insert(username.to_owned(), password.to_owned());
HttpBasicAuth {
users: users
}
}
fn is_authorized(&self, username: &str, password: &str) -> bool {
self.users.get(&username.to_owned()).map_or(false, |pass| pass == password)
}
fn check_auth(&self, req: &server::Request) -> Access {
match req.headers.get::<header::Authorization<header::Basic>>() {
Some(&header::Authorization(
header::Basic { ref username, password: Some(ref password) }
)) if self.is_authorized(username, password) => Access::Granted,
Some(_) => Access::Denied,
None => Access::AuthRequired,
}
}
fn respond_with_unauthorized(&self, mut res: server::Response) {
*res.status_mut() = StatusCode::Unauthorized;
let _ = res.send(b"Unauthorized");
}
fn respond_with_auth_required(&self, mut res: server::Response) {
*res.status_mut() = StatusCode::Unauthorized;
res.headers_mut().set_raw("WWW-Authenticate", vec![b"Basic realm=\"Parity\"".to_vec()]);
}
}

123
webapp/src/router/mod.rs Normal file
View File

@ -0,0 +1,123 @@
// Copyright 2015, 2016 Ethcore (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Router implementation
//! Processes request handling authorization and dispatching it to proper application.
mod api;
pub mod auth;
use std::sync::Arc;
use hyper;
use hyper::{server, uri, header};
use page::Page;
use apps::Pages;
use iron::request::Url;
use jsonrpc_http_server::ServerHandler;
use self::auth::{Authorization, Authorized};
pub struct Router<A: Authorization> {
authorization: A,
rpc: ServerHandler,
api: api::RestApi,
main_page: Box<Page>,
pages: Arc<Pages>,
}
impl<A: Authorization> server::Handler for Router<A> {
fn handle<'b, 'a>(&'a self, req: server::Request<'a, 'b>, res: server::Response<'a>) {
let auth = self.authorization.handle(req, res);
if let Authorized::Yes(req, res) = auth {
let (path, req) = self.extract_request_path(req);
match path {
Some(ref url) if self.pages.contains_key(url) => {
self.pages.get(url).unwrap().handle(req, res);
},
Some(ref url) if url == "api" => {
self.api.handle(req, res);
},
_ if req.method == hyper::method::Method::Post => {
self.rpc.handle(req, res)
},
_ => self.main_page.handle(req, res),
}
}
}
}
impl<A: Authorization> Router<A> {
pub fn new(
rpc: ServerHandler,
main_page: Box<Page>,
pages: Pages,
authorization: A) -> Self {
let pages = Arc::new(pages);
Router {
authorization: authorization,
rpc: rpc,
api: api::RestApi { pages: pages.clone() },
main_page: main_page,
pages: pages,
}
}
fn extract_url(&self, req: &server::Request) -> Option<Url> {
match req.uri {
uri::RequestUri::AbsoluteUri(ref url) => {
match Url::from_generic_url(url.clone()) {
Ok(url) => Some(url),
_ => None,
}
},
uri::RequestUri::AbsolutePath(ref path) => {
// Attempt to prepend the Host header (mandatory in HTTP/1.1)
let url_string = match req.headers.get::<header::Host>() {
Some(ref host) => {
format!("http://{}:{}{}", host.hostname, host.port.unwrap_or(80), path)
},
None => return None,
};
match Url::parse(&url_string) {
Ok(url) => Some(url),
_ => None,
}
},
_ => None,
}
}
fn extract_request_path<'a, 'b>(&self, mut req: server::Request<'a, 'b>) -> (Option<String>, server::Request<'a, 'b>) {
let url = self.extract_url(&req);
match url {
Some(ref url) if url.path.len() > 1 => {
let part = url.path[0].clone();
let url = url.path[1..].join("/");
req.uri = uri::RequestUri::AbsolutePath(url);
(Some(part), req)
},
Some(url) => {
let url = url.path.join("/");
req.uri = uri::RequestUri::AbsolutePath(url);
(None, req)
},
_ => {
(None, req)
},
}
}
}