ethcore: add clique engine (#9981)
* fix broken sync * correct seal fields * ethcore: fix comment * parity: remove duplicate params * clique: fix whitespaces * ethcore: fix goerli chain spec * refactor signer_snapshot into pending/finalized state * move close_block_extra_data after seal is applied * refactor most of the logic into the signer_snapshot * clique: refactor locking logic out of the consensus engine interface * Fix jsonspec and add an unittest * Replace space with tabs * Unbroke sync * Fix broken sync * 1/2 state tracking without votes * 2/2 implement vote tracking * ci: use travis for goerli * ci: setup a clique network * ci: sync a görli node * add clique deploy script * ci: fix paths in clique deploy script * ci: use docker compose * ci: fix travis job names * ci: fix build deps * ci: massively reduce tests * Revert "ci: massively reduce tests" This reverts commit 6369f0b069ed2607a7e9f2e1d85489bacdc43384. * ci: run cargo test directly * ci: separate build and test stages * ci: cache rust installation * ci: simplify ci stages * ci: make clique deploy script executable * ci: shutdown goerli sync after 20min * ci: remove slow sync stage * ci: use timeout to finish jobs * ci: fix build path * ci: use absolute paths to end this confusion * ci: add geth and parity to path * ci: be more verbose * ci: allow for more relaxed caching timeout * ci: update repositories for custom ppa * ci: fix typo in file name * ci: fix docker compose file * ci: add ethkey to docker * ci: make sure deploy script is up to date with upstream * ci: stop docker container after certain time * ci: force superuser to update permissions on docker files * ci: reduce run time of script to ~30 min * ci: remove duplicate caching in travis * remove trace statements * clique: add more validation involving the recent signer list * ethcore: enable constantinople for rinkeby * ethcore: fix whitespaces in rinkeby spec * ethcore: reformat goerli.json * Revert "ci: remove duplicate caching in travis" This reverts commit a562838d3d194d37f9871dcbe00b783637978f89. * tmp commit * another tmp commit * it builds! * add sealing capabilities * add seal_header hook to allow separation of block seal/importing code paths * clique: remove populate_from_parent. * add panic * make turn delay random * initialize OpenBlock properly in 'enact' * misc: remove duplicate lines * misc: fix license headers * misc: convert spaces to tabs * misc: fix tabs * Update Cargo.toml * Update Cargo.toml * Update Cargo.toml * clique: ensure validator restores state before trying to seal * clique: make 'state' return an Error. Make some error messages more clear * Fix compile error after rebase & toolchain upgrade * fix a bunch of import warnings * Refactor code * Fix permissions * Refactoring syncing * Implement full validator checks * Refactor util functions to seperate file * mining 1 * ethcore: add chainspec for kotti * ethcore: rename pre-goerli configs * ethcore: load kotti chain spec * cli: add kotti to params * Implement working local sealing * making sealing & syncing work together * Relax timestamp checking * ethcore: prepare for the real goerli to launch * Implement NOTURN wiggle properly & cleanupnup warnings * Implement vote casting * Update docs & skip signing if no signer * Optimize step-service interval * Record state on local sealed block * Fix script filemode * Cleaning up codebase * restore enact trace logging * Delete clique.sh and move sync.sh * remove travis.yml * Remove dead code * Cleanup compile warning * address review comments * adding more comments and removing unwrap() * ci: remove sync script * Address review comments * fix compile error * adding better debugging for timing * Implement an dedicated thread for sealing timing * fix(add helper for timestamp overflows) (#10330) * fix(add helper timestamp overflows) * fix(simplify code) * fix(make helper private) * snap: official image / test (#10168) * official image / test * fix / test * bit more necromancy * fix paths * add source bin/df /test * add source bin/df /test2 * something w paths /test * something w paths /test * add source-type /test * show paths /test * copy plugin /test * plugin -> nil * install rhash * no questions while installing rhash * publish snap only for release * fix(docker): fix not receives SIGINT (#10059) * fix(docker): fix not receives SIGINT * fix: update with reviews * update with review * update * update * Don't add discovery initiators to the node table (#10305) * Don't add discovery initiators to the node table * Use enums for tracking state of the nodes in discovery * Dont try to ping ourselves * Fix minor nits * Update timeouts when observing an outdated node * Extracted update_bucket_record from update_node * Fixed typo * Fix two final nits from @todr * change docker image based on debian instead of ubuntu due to the chan… (#10336) * change docker image based on debian instead of ubuntu due to the changes of the build container * role back docker build image and docker deploy image to ubuntu:xenial based (#10338) * Bundle protocol and packet_id together in chain sync (#10315) Define a new `enum` where devp2p subprotocol packet ids (currently eth and par) are defined. Additionally provide functionality to query id value and protocol of a given id object. * snap: prefix version and populate candidate channel (#10343) * snap: populate candidate releases with beta snaps to avoid stale channel * snap: prefix version with v* * addressing review comments * engine: fix copyright header * scripts: restore permissions on sign command * ethcore: enforce tabs * ethcore: enforce tabs * ethcore: enforce tabs * addressing comments * addressing comments * addressing more comments * addressing more comments * addressing more comments * addressing more comments * addressing more comments * json-spec: fix clique epoch to non-zero u64 * ci: enable travis for parity goerli * ci: don't separate build and test step * ci: don't run c++ tests on travis * ci: simplify cargo test to squeeze into travis timeout * ci: don't run tests on travis at all * style(fixes) * fix(add tests) * fix(recent_signer bug) * fix(complete all tests) * fix(nits) * fix(simplify asserts) * fix(cliqueState): simplify code * fix(nits) * docs(comments what's need to fixed) * fix(revert unintended changes) * fix(tests) * fix(logs): voting logs * fix(readability + more logs) * fix(sync) * docs(add missing licens header) * fix(log): info! -> trace! * docs(fix nits) + fix(remove assert) * perf(use counter instead of vec) * fix(remove needless block in match) * fix(faulty comment) * grumbles(docs for tests) * fix(nits) * fix(revert_vote): only remove vote when votes == 0 * fix(vote counter): checked arithmetics * fix(simplify tests) * fix(nits) * fix(clique): err types * fix(clique utils): make use of errors * fix(cleanup nits) * fix(clique sealing): don't read state no signer * fix(replace Vec<Signers> with BTreeSet<Signers>) * fix(tests): BTreeSet and more generic helpers * fix(nits) * fix(ethcore_block_seal): remove needless `Box` * fix(faulty log): info -> trace * fix(checked SystemTime): prevent SystemTime panics * style(chain cfg): space after `:` * style(fn enact): fix whitespace * docs(clique): StepService * docs(nit): fix faulty comment * docs(fix typo) * style(fix bad indentation) * fix(bad regex match) * grumble(on_seal_block): make `&mut` to avoid clone * docs(on_seal_block): fix faulty documentation * Delete .travis.yml * docs: remove eth hf references in spec * Update client.rs * fix(nits) * fix(clique step): `RwLock` -> `AtomicBool` * fix(clique): use `Duration::as_millis` * Clean up some Clique documentation Co-authored-by: soc1c <soc1c@users.noreply.github.com> Co-authored-by: HCastano <HCastano@users.noreply.github.com> Co-authored-by: niklasad1 <niklasad1@users.noreply.github.com> Co-authored-by: jwasinger <jwasinger@users.noreply.github.com> Co-authored-by: ChainSafe <ChainSafe@users.noreply.github.com> Co-authored-by: thefallentree <thefallentree@users.noreply.github.com> Co-authored-by: 5chdn <5chdn@users.noreply.github.com>
This commit is contained in:
parent
9cb8606103
commit
aa8487c1d0
@ -39,7 +39,7 @@ keccak-hasher = { path = "../util/keccak-hasher" }
|
|||||||
kvdb = "0.1"
|
kvdb = "0.1"
|
||||||
kvdb-memorydb = "0.1"
|
kvdb-memorydb = "0.1"
|
||||||
kvdb-rocksdb = { version = "0.1.3", optional = true }
|
kvdb-rocksdb = { version = "0.1.3", optional = true }
|
||||||
lazy_static = "1.0"
|
lazy_static = "1.2.0"
|
||||||
len-caching-lock = { path = "../util/len-caching-lock" }
|
len-caching-lock = { path = "../util/len-caching-lock" }
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
lru-cache = "0.1"
|
lru-cache = "0.1"
|
||||||
|
911
ethcore/res/ethereum/goerli.json
Normal file
911
ethcore/res/ethereum/goerli.json
Normal file
@ -0,0 +1,911 @@
|
|||||||
|
{
|
||||||
|
"name": "Görli Testnet",
|
||||||
|
"dataDir": "goerli",
|
||||||
|
"engine": {
|
||||||
|
"clique": {
|
||||||
|
"params": {
|
||||||
|
"period": 15,
|
||||||
|
"epoch": 30000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"params": {
|
||||||
|
"accountStartNonce": "0x0",
|
||||||
|
"chainID": "0x5",
|
||||||
|
"eip140Transition": "0x0",
|
||||||
|
"eip145Transition": "0x0",
|
||||||
|
"eip150Transition": "0x0",
|
||||||
|
"eip155Transition": "0x0",
|
||||||
|
"eip160Transition": "0x0",
|
||||||
|
"eip161abcTransition": "0x0",
|
||||||
|
"eip161dTransition": "0x0",
|
||||||
|
"eip211Transition": "0x0",
|
||||||
|
"eip214Transition": "0x0",
|
||||||
|
"eip658Transition": "0x0",
|
||||||
|
"eip1014Transition": "0x0",
|
||||||
|
"eip1052Transition": "0x0",
|
||||||
|
"eip1283Transition": "0x0",
|
||||||
|
"eip1283DisableTransition": "0x0",
|
||||||
|
"gasLimitBoundDivisor": "0x400",
|
||||||
|
"maxCodeSize": "0x6000",
|
||||||
|
"maxCodeSizeTransition": "0x0",
|
||||||
|
"maximumExtraDataSize": "0xffff",
|
||||||
|
"minGasLimit": "0x1388",
|
||||||
|
"networkID": "0x5"
|
||||||
|
},
|
||||||
|
"genesis": {
|
||||||
|
"author": "0x0000000000000000000000000000000000000000",
|
||||||
|
"difficulty": "0x1",
|
||||||
|
"extraData": "0x22466c6578692069732061207468696e6722202d204166726900000000000000e0a2bd4258d2768837baa26a28fe71dc079f84c70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
"gasLimit": "0xa00000",
|
||||||
|
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
"seal": {
|
||||||
|
"ethereum": {
|
||||||
|
"nonce": "0x0000000000000000",
|
||||||
|
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"timestamp": "0x5c51a607"
|
||||||
|
},
|
||||||
|
"nodes": [
|
||||||
|
"enode://011f758e6552d105183b1761c5e2dea0111bc20fd5f6422bc7f91e0fabbec9a6595caf6239b37feb773dddd3f87240d99d859431891e4a642cf2a0a9e6cbb98a@51.141.78.53:30303",
|
||||||
|
"enode://176b9417f511d05b6b2cf3e34b756cf0a7096b3094572a8f6ef4cdcb9d1f9d00683bf0f83347eebdf3b81c3521c2332086d9592802230bf528eaf606a1d9677b@13.93.54.137:30303"
|
||||||
|
],
|
||||||
|
"accounts": {
|
||||||
|
"0x0000000000000000000000000000000000000000": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000001": {
|
||||||
|
"balance": "0x1",
|
||||||
|
"builtin": {
|
||||||
|
"name": "ecrecover",
|
||||||
|
"pricing": {
|
||||||
|
"linear": {
|
||||||
|
"base": 3000,
|
||||||
|
"word": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000002": {
|
||||||
|
"balance": "0x1",
|
||||||
|
"builtin": {
|
||||||
|
"name": "sha256",
|
||||||
|
"pricing": {
|
||||||
|
"linear": {
|
||||||
|
"base": 60,
|
||||||
|
"word": 12
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000003": {
|
||||||
|
"balance": "0x1",
|
||||||
|
"builtin": {
|
||||||
|
"name": "ripemd160",
|
||||||
|
"pricing": {
|
||||||
|
"linear": {
|
||||||
|
"base": 600,
|
||||||
|
"word": 120
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000004": {
|
||||||
|
"balance": "0x1",
|
||||||
|
"builtin": {
|
||||||
|
"name": "identity",
|
||||||
|
"pricing": {
|
||||||
|
"linear": {
|
||||||
|
"base": 15,
|
||||||
|
"word": 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000005": {
|
||||||
|
"balance": "0x1",
|
||||||
|
"builtin": {
|
||||||
|
"name": "modexp",
|
||||||
|
"activate_at": "0x0",
|
||||||
|
"pricing": {
|
||||||
|
"modexp": {
|
||||||
|
"divisor": 20
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000006": {
|
||||||
|
"balance": "0x1",
|
||||||
|
"builtin": {
|
||||||
|
"name": "alt_bn128_add",
|
||||||
|
"activate_at": "0x0",
|
||||||
|
"pricing": {
|
||||||
|
"linear": {
|
||||||
|
"base": 500,
|
||||||
|
"word": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000007": {
|
||||||
|
"balance": "0x1",
|
||||||
|
"builtin": {
|
||||||
|
"name": "alt_bn128_mul",
|
||||||
|
"activate_at": "0x0",
|
||||||
|
"pricing": {
|
||||||
|
"linear": {
|
||||||
|
"base": 40000,
|
||||||
|
"word": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000008": {
|
||||||
|
"balance": "0x1",
|
||||||
|
"builtin": {
|
||||||
|
"name": "alt_bn128_pairing",
|
||||||
|
"activate_at": "0x0",
|
||||||
|
"pricing": {
|
||||||
|
"alt_bn128_pairing": {
|
||||||
|
"base": 100000,
|
||||||
|
"pair": 80000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000009": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000000a": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000000b": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000000c": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000000d": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000000e": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000000f": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000010": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000011": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000012": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000013": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000014": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000015": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000016": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000017": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000018": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000019": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000001a": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000001b": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000001c": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000001d": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000001e": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000001f": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000020": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000021": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000022": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000023": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000024": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000025": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000026": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000027": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000028": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000029": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000002a": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000002b": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000002c": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000002d": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000002e": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000002f": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000030": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000031": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000032": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000033": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000034": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000035": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000036": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000037": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000038": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000039": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000003a": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000003b": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000003c": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000003d": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000003e": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000003f": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000040": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000041": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000042": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000043": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000044": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000045": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000046": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000047": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000048": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000049": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000004a": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000004b": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000004c": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000004d": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000004e": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000004f": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000050": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000051": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000052": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000053": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000054": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000055": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000056": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000057": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000058": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000059": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000005a": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000005b": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000005c": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000005d": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000005e": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000005f": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000060": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000061": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000062": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000063": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000064": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000065": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000066": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000067": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000068": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000069": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000006a": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000006b": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000006c": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000006d": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000006e": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000006f": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000070": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000071": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000072": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000073": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000074": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000075": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000076": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000077": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000078": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000079": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000007a": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000007b": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000007c": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000007d": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000007e": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000007f": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000080": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000081": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000082": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000083": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000084": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000085": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000086": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000087": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000088": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000089": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000008a": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000008b": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000008c": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000008d": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000008e": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000008f": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000090": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000091": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000092": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000093": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000094": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000095": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000096": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000097": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000098": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000099": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000009a": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000009b": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000009c": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000009d": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000009e": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000009f": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000a0": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000a1": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000a2": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000a3": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000a4": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000a5": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000a6": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000a7": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000a8": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000a9": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000aa": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000ab": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000ac": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000ad": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000ae": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000af": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000b0": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000b1": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000b2": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000b3": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000b4": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000b5": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000b6": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000b7": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000b8": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000b9": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000ba": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000bb": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000bc": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000bd": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000be": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000bf": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000c0": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000c1": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000c2": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000c3": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000c4": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000c5": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000c6": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000c7": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000c8": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000c9": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000ca": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000cb": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000cc": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000cd": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000ce": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000cf": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000d0": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000d1": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000d2": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000d3": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000d4": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000d5": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000d6": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000d7": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000d8": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000d9": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000da": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000db": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000dc": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000dd": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000de": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000df": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000e0": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000e1": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000e2": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000e3": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000e4": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000e5": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000e6": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000e7": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000e8": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000e9": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000ea": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000eb": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000ec": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000ed": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000ee": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000ef": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000f0": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000f1": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000f2": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000f3": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000f4": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000f5": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000f6": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000f7": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000f8": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000f9": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000fa": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000fb": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000fc": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000fd": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000fe": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000ff": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x4c2ae482593505f0163cdefc073e81c63cda4107": {
|
||||||
|
"balance": "0x152d02c7e14af6800000"
|
||||||
|
},
|
||||||
|
"0xa8e8f14732658e4b51e8711931053a8a69baf2b1": {
|
||||||
|
"balance": "0x152d02c7e14af6800000"
|
||||||
|
},
|
||||||
|
"0xd9a5179f091d85051d3c982785efd1455cec8699": {
|
||||||
|
"balance": "0x84595161401484a000000"
|
||||||
|
},
|
||||||
|
"0xe0a2bd4258d2768837baa26a28fe71dc079f84c7": {
|
||||||
|
"balance": "0x4a47e3c12448f4ad000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
855
ethcore/res/ethereum/kotti.json
Normal file
855
ethcore/res/ethereum/kotti.json
Normal file
@ -0,0 +1,855 @@
|
|||||||
|
{
|
||||||
|
"name": "Kotti Testnet",
|
||||||
|
"dataDir": "kotti",
|
||||||
|
"engine": {
|
||||||
|
"clique": {
|
||||||
|
"params": {
|
||||||
|
"period": 15,
|
||||||
|
"epoch": 30000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"params": {
|
||||||
|
"accountStartNonce": "0x0",
|
||||||
|
"chainID": "0x6",
|
||||||
|
"eip150Transition": "0x0",
|
||||||
|
"eip155Transition": "0x0",
|
||||||
|
"eip160Transition": "0x0",
|
||||||
|
"eip161abcTransition": "0x7fffffffffffffff",
|
||||||
|
"eip161dTransition": "0x7fffffffffffffff",
|
||||||
|
"gasLimitBoundDivisor": "0x400",
|
||||||
|
"maximumExtraDataSize": "0xffff",
|
||||||
|
"minGasLimit": "0x1388",
|
||||||
|
"networkID": "0x6"
|
||||||
|
},
|
||||||
|
"genesis": {
|
||||||
|
"author": "0x0000000000000000000000000000000000000000",
|
||||||
|
"difficulty": "0x1",
|
||||||
|
"extraData": "0x000000000000000000000000000000000000000000000000000000000000000025b7955e43adf9c2a01a9475908702cce67f302a6aaf8cba3c9255a2b863415d4db7bae4f4bbca020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
"gasLimit": "0xa00000",
|
||||||
|
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
"seal": {
|
||||||
|
"ethereum": {
|
||||||
|
"nonce": "0x0000000000000000",
|
||||||
|
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"timestamp": "0x5c2d2287"
|
||||||
|
},
|
||||||
|
"nodes": [
|
||||||
|
"enode://06333009fc9ef3c9e174768e495722a7f98fe7afd4660542e983005f85e556028410fd03278944f44cfe5437b1750b5e6bd1738f700fe7da3626d52010d2954c@51.141.15.254:30303",
|
||||||
|
"enode://ae8658da8d255d1992c3ec6e62e11d6e1c5899aa1566504bc1ff96a0c9c8bd44838372be643342553817f5cc7d78f1c83a8093dee13d77b3b0a583c050c81940@18.232.185.151:30303"
|
||||||
|
],
|
||||||
|
"accounts": {
|
||||||
|
"0x0000000000000000000000000000000000000000": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000001": {
|
||||||
|
"balance": "0x1",
|
||||||
|
"builtin": {
|
||||||
|
"name": "ecrecover",
|
||||||
|
"pricing": {
|
||||||
|
"linear": {
|
||||||
|
"base": 3000,
|
||||||
|
"word": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000002": {
|
||||||
|
"balance": "0x1",
|
||||||
|
"builtin": {
|
||||||
|
"name": "sha256",
|
||||||
|
"pricing": {
|
||||||
|
"linear": {
|
||||||
|
"base": 60,
|
||||||
|
"word": 12
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000003": {
|
||||||
|
"balance": "0x1",
|
||||||
|
"builtin": {
|
||||||
|
"name": "ripemd160",
|
||||||
|
"pricing": {
|
||||||
|
"linear": {
|
||||||
|
"base": 600,
|
||||||
|
"word": 120
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000004": {
|
||||||
|
"balance": "0x1",
|
||||||
|
"builtin": {
|
||||||
|
"name": "identity",
|
||||||
|
"pricing": {
|
||||||
|
"linear": {
|
||||||
|
"base": 15,
|
||||||
|
"word": 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000005": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000006": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000007": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000008": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000009": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000000a": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000000b": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000000c": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000000d": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000000e": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000000f": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000010": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000011": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000012": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000013": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000014": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000015": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000016": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000017": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000018": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000019": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000001a": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000001b": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000001c": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000001d": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000001e": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000001f": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000020": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000021": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000022": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000023": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000024": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000025": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000026": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000027": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000028": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000029": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000002a": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000002b": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000002c": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000002d": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000002e": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000002f": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000030": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000031": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000032": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000033": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000034": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000035": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000036": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000037": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000038": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000039": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000003a": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000003b": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000003c": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000003d": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000003e": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000003f": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000040": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000041": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000042": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000043": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000044": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000045": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000046": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000047": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000048": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000049": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000004a": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000004b": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000004c": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000004d": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000004e": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000004f": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000050": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000051": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000052": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000053": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000054": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000055": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000056": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000057": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000058": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000059": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000005a": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000005b": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000005c": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000005d": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000005e": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000005f": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000060": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000061": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000062": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000063": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000064": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000065": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000066": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000067": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000068": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000069": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000006a": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000006b": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000006c": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000006d": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000006e": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000006f": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000070": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000071": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000072": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000073": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000074": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000075": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000076": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000077": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000078": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000079": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000007a": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000007b": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000007c": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000007d": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000007e": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000007f": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000080": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000081": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000082": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000083": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000084": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000085": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000086": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000087": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000088": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000089": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000008a": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000008b": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000008c": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000008d": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000008e": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000008f": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000090": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000091": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000092": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000093": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000094": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000095": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000096": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000097": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000098": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000099": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000009a": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000009b": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000009c": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000009d": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000009e": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000009f": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000a0": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000a1": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000a2": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000a3": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000a4": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000a5": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000a6": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000a7": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000a8": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000a9": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000aa": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000ab": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000ac": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000ad": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000ae": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000af": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000b0": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000b1": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000b2": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000b3": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000b4": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000b5": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000b6": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000b7": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000b8": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000b9": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000ba": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000bb": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000bc": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000bd": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000be": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000bf": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000c0": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000c1": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000c2": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000c3": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000c4": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000c5": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000c6": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000c7": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000c8": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000c9": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000ca": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000cb": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000cc": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000cd": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000ce": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000cf": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000d0": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000d1": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000d2": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000d3": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000d4": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000d5": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000d6": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000d7": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000d8": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000d9": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000da": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000db": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000dc": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000dd": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000de": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000df": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000e0": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000e1": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000e2": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000e3": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000e4": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000e5": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000e6": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000e7": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000e8": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000e9": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000ea": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000eb": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000ec": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000ed": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000ee": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000ef": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000f0": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000f1": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000f2": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000f3": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000f4": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000f5": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000f6": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000f7": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000f8": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000f9": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000fa": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000fb": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000fc": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000fd": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000fe": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000ff": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x25b7955e43adf9c2a01a9475908702cce67f302a": {
|
||||||
|
"balance": "0x84595161401484a000000"
|
||||||
|
},
|
||||||
|
"0x6aaf8cba3c9255a2b863415d4db7bae4f4bbca02": {
|
||||||
|
"balance": "0x4a723dc6b40b8a9a000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
902
ethcore/res/ethereum/rinkeby.json
Normal file
902
ethcore/res/ethereum/rinkeby.json
Normal file
@ -0,0 +1,902 @@
|
|||||||
|
{
|
||||||
|
"name": "Rinkeby",
|
||||||
|
"dataDir": "rinkeby",
|
||||||
|
"engine": {
|
||||||
|
"clique": {
|
||||||
|
"params": {
|
||||||
|
"period": 15,
|
||||||
|
"epoch": 30000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"params": {
|
||||||
|
"accountStartNonce": "0x0",
|
||||||
|
"chainID": "0x4",
|
||||||
|
"eip140Transition": "0xfcc25",
|
||||||
|
"eip145Transition": "0x37db77",
|
||||||
|
"eip150Transition": "0x2",
|
||||||
|
"eip155Transition": "0x3",
|
||||||
|
"eip160Transition": "0x0",
|
||||||
|
"eip161abcTransition": "0x0",
|
||||||
|
"eip161dTransition": "0x0",
|
||||||
|
"eip211Transition": "0xfcc25",
|
||||||
|
"eip214Transition": "0xfcc25",
|
||||||
|
"eip658Transition": "0xfcc25",
|
||||||
|
"eip1014Transition": "0x37db77",
|
||||||
|
"eip1052Transition": "0x37db77",
|
||||||
|
"eip1283Transition": "0x37db77",
|
||||||
|
"gasLimitBoundDivisor": "0x400",
|
||||||
|
"maxCodeSize": "0x6000",
|
||||||
|
"maxCodeSizeTransition": "0x0",
|
||||||
|
"maximumExtraDataSize": "0xffff",
|
||||||
|
"minGasLimit": "0x1388",
|
||||||
|
"networkID": "0x4"
|
||||||
|
},
|
||||||
|
"genesis": {
|
||||||
|
"author": "0x0000000000000000000000000000000000000000",
|
||||||
|
"difficulty": "0x1",
|
||||||
|
"extraData": "0x52657370656374206d7920617574686f7269746168207e452e436172746d616e42eb768f2244c8811c63729a21a3569731535f067ffc57839b00206d1ad20c69a1981b489f772031b279182d99e65703f0076e4812653aab85fca0f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
"gasLimit": "0x47b760",
|
||||||
|
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
"seal": {
|
||||||
|
"ethereum": {
|
||||||
|
"nonce": "0x0000000000000000",
|
||||||
|
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"timestamp": "0x58ee40ba"
|
||||||
|
},
|
||||||
|
"nodes": [
|
||||||
|
"enode://a24ac7c5484ef4ed0c5eb2d36620ba4e4aa13b8c84684e1b4aab0cebea2ae45cb4d375b77eab56516d34bfbd3c1a833fc51296ff084b770b94fb9028c4d25ccf@52.169.42.101:30303",
|
||||||
|
"enode://343149e4feefa15d882d9fe4ac7d88f885bd05ebb735e547f12e12080a9fa07c8014ca6fd7f373123488102fe5e34111f8509cf0b7de3f5b44339c9f25e87cb8@52.3.158.184:30303",
|
||||||
|
"enode://b6b28890b006743680c52e64e0d16db57f28124885595fa03a562be1d2bf0f3a1da297d56b13da25fb992888fd556d4c1a27b1f39d531bde7de1921c90061cc6@159.89.28.211:30303"
|
||||||
|
],
|
||||||
|
"accounts": {
|
||||||
|
"0x0000000000000000000000000000000000000000": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000001": {
|
||||||
|
"balance": "0x1",
|
||||||
|
"builtin": {
|
||||||
|
"name": "ecrecover",
|
||||||
|
"pricing": {
|
||||||
|
"linear": {
|
||||||
|
"base": 3000,
|
||||||
|
"word": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000002": {
|
||||||
|
"balance": "0x1",
|
||||||
|
"builtin": {
|
||||||
|
"name": "sha256",
|
||||||
|
"pricing": {
|
||||||
|
"linear": {
|
||||||
|
"base": 60,
|
||||||
|
"word": 12
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000003": {
|
||||||
|
"balance": "0x1",
|
||||||
|
"builtin": {
|
||||||
|
"name": "ripemd160",
|
||||||
|
"pricing": {
|
||||||
|
"linear": {
|
||||||
|
"base": 600,
|
||||||
|
"word": 120
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000004": {
|
||||||
|
"balance": "0x1",
|
||||||
|
"builtin": {
|
||||||
|
"name": "identity",
|
||||||
|
"pricing": {
|
||||||
|
"linear": {
|
||||||
|
"base": 15,
|
||||||
|
"word": 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000005": {
|
||||||
|
"balance": "0x1",
|
||||||
|
"builtin": {
|
||||||
|
"name": "modexp",
|
||||||
|
"activate_at": "0xfcc25",
|
||||||
|
"pricing": {
|
||||||
|
"modexp": {
|
||||||
|
"divisor": 20
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000006": {
|
||||||
|
"balance": "0x1",
|
||||||
|
"builtin": {
|
||||||
|
"name": "alt_bn128_add",
|
||||||
|
"activate_at": "0xfcc25",
|
||||||
|
"pricing": {
|
||||||
|
"linear": {
|
||||||
|
"base": 500,
|
||||||
|
"word": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000007": {
|
||||||
|
"balance": "0x1",
|
||||||
|
"builtin": {
|
||||||
|
"name": "alt_bn128_mul",
|
||||||
|
"activate_at": "0xfcc25",
|
||||||
|
"pricing": {
|
||||||
|
"linear": {
|
||||||
|
"base": 40000,
|
||||||
|
"word": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000008": {
|
||||||
|
"balance": "0x1",
|
||||||
|
"builtin": {
|
||||||
|
"name": "alt_bn128_pairing",
|
||||||
|
"activate_at": "0xfcc25",
|
||||||
|
"pricing": {
|
||||||
|
"alt_bn128_pairing": {
|
||||||
|
"base": 100000,
|
||||||
|
"pair": 80000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000009": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000000a": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000000b": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000000c": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000000d": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000000e": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000000f": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000010": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000011": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000012": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000013": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000014": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000015": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000016": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000017": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000018": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000019": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000001a": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000001b": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000001c": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000001d": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000001e": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000001f": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000020": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000021": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000022": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000023": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000024": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000025": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000026": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000027": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000028": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000029": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000002a": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000002b": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000002c": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000002d": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000002e": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000002f": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000030": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000031": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000032": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000033": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000034": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000035": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000036": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000037": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000038": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000039": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000003a": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000003b": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000003c": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000003d": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000003e": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000003f": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000040": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000041": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000042": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000043": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000044": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000045": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000046": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000047": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000048": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000049": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000004a": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000004b": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000004c": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000004d": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000004e": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000004f": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000050": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000051": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000052": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000053": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000054": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000055": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000056": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000057": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000058": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000059": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000005a": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000005b": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000005c": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000005d": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000005e": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000005f": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000060": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000061": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000062": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000063": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000064": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000065": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000066": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000067": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000068": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000069": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000006a": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000006b": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000006c": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000006d": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000006e": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000006f": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000070": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000071": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000072": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000073": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000074": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000075": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000076": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000077": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000078": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000079": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000007a": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000007b": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000007c": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000007d": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000007e": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000007f": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000080": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000081": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000082": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000083": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000084": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000085": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000086": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000087": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000088": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000089": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000008a": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000008b": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000008c": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000008d": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000008e": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000008f": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000090": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000091": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000092": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000093": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000094": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000095": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000096": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000097": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000098": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x0000000000000000000000000000000000000099": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000009a": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000009b": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000009c": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000009d": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000009e": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x000000000000000000000000000000000000009f": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000a0": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000a1": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000a2": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000a3": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000a4": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000a5": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000a6": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000a7": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000a8": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000a9": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000aa": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000ab": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000ac": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000ad": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000ae": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000af": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000b0": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000b1": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000b2": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000b3": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000b4": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000b5": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000b6": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000b7": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000b8": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000b9": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000ba": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000bb": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000bc": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000bd": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000be": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000bf": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000c0": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000c1": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000c2": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000c3": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000c4": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000c5": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000c6": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000c7": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000c8": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000c9": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000ca": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000cb": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000cc": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000cd": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000ce": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000cf": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000d0": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000d1": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000d2": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000d3": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000d4": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000d5": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000d6": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000d7": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000d8": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000d9": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000da": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000db": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000dc": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000dd": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000de": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000df": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000e0": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000e1": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000e2": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000e3": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000e4": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000e5": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000e6": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000e7": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000e8": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000e9": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000ea": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000eb": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000ec": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000ed": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000ee": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000ef": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000f0": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000f1": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000f2": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000f3": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000f4": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000f5": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000f6": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000f7": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000f8": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000f9": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000fa": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000fb": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000fc": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000fd": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000fe": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x00000000000000000000000000000000000000ff": {
|
||||||
|
"balance": "0x1"
|
||||||
|
},
|
||||||
|
"0x31b98d14007bdee637298086988a0bbd31184523": {
|
||||||
|
"balance": "0x200000000000000000000000000000000000000000000000000000000000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -284,7 +284,6 @@ impl<'x> OpenBlock<'x> {
|
|||||||
self.block.header.set_difficulty(*header.difficulty());
|
self.block.header.set_difficulty(*header.difficulty());
|
||||||
self.block.header.set_gas_limit(*header.gas_limit());
|
self.block.header.set_gas_limit(*header.gas_limit());
|
||||||
self.block.header.set_timestamp(header.timestamp());
|
self.block.header.set_timestamp(header.timestamp());
|
||||||
self.block.header.set_author(*header.author());
|
|
||||||
self.block.header.set_uncles_hash(*header.uncles_hash());
|
self.block.header.set_uncles_hash(*header.uncles_hash());
|
||||||
self.block.header.set_transactions_root(*header.transactions_root());
|
self.block.header.set_transactions_root(*header.transactions_root());
|
||||||
// TODO: that's horrible. set only for backwards compatibility
|
// TODO: that's horrible. set only for backwards compatibility
|
||||||
@ -405,15 +404,20 @@ impl LockedBlock {
|
|||||||
/// Provide a valid seal in order to turn this into a `SealedBlock`.
|
/// Provide a valid seal in order to turn this into a `SealedBlock`.
|
||||||
///
|
///
|
||||||
/// NOTE: This does not check the validity of `seal` with the engine.
|
/// NOTE: This does not check the validity of `seal` with the engine.
|
||||||
pub fn seal(self, engine: &EthEngine, seal: Vec<Bytes>) -> Result<SealedBlock, BlockError> {
|
pub fn seal(self, engine: &EthEngine, seal: Vec<Bytes>) -> Result<SealedBlock, Error> {
|
||||||
let expected_seal_fields = engine.seal_fields(&self.block.header);
|
let expected_seal_fields = engine.seal_fields(&self.header);
|
||||||
let mut s = self;
|
let mut s = self;
|
||||||
if seal.len() != expected_seal_fields {
|
if seal.len() != expected_seal_fields {
|
||||||
return Err(BlockError::InvalidSealArity(
|
Err(BlockError::InvalidSealArity(Mismatch {
|
||||||
Mismatch { expected: expected_seal_fields, found: seal.len() }));
|
expected: expected_seal_fields,
|
||||||
|
found: seal.len()
|
||||||
|
}))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
s.block.header.set_seal(seal);
|
s.block.header.set_seal(seal);
|
||||||
|
engine.on_seal_block(&mut s.block)?;
|
||||||
s.block.header.compute_hash();
|
s.block.header.compute_hash();
|
||||||
|
|
||||||
Ok(SealedBlock {
|
Ok(SealedBlock {
|
||||||
block: s.block
|
block: s.block
|
||||||
})
|
})
|
||||||
@ -422,6 +426,7 @@ impl LockedBlock {
|
|||||||
/// Provide a valid seal in order to turn this into a `SealedBlock`.
|
/// Provide a valid seal in order to turn this into a `SealedBlock`.
|
||||||
/// This does check the validity of `seal` with the engine.
|
/// This does check the validity of `seal` with the engine.
|
||||||
/// Returns the `ClosedBlock` back again if the seal is no good.
|
/// Returns the `ClosedBlock` back again if the seal is no good.
|
||||||
|
/// TODO(https://github.com/paritytech/parity-ethereum/issues/10407): This is currently only used in POW chain call paths, we should really merge it with seal() above.
|
||||||
pub fn try_seal(
|
pub fn try_seal(
|
||||||
self,
|
self,
|
||||||
engine: &EthEngine,
|
engine: &EthEngine,
|
||||||
@ -463,7 +468,7 @@ impl Drain for SealedBlock {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Enact the block given by block header, transactions and uncles
|
/// Enact the block given by block header, transactions and uncles
|
||||||
fn enact(
|
pub(crate) fn enact(
|
||||||
header: Header,
|
header: Header,
|
||||||
transactions: Vec<SignedTransaction>,
|
transactions: Vec<SignedTransaction>,
|
||||||
uncles: Vec<Header>,
|
uncles: Vec<Header>,
|
||||||
@ -476,13 +481,12 @@ fn enact(
|
|||||||
is_epoch_begin: bool,
|
is_epoch_begin: bool,
|
||||||
ancestry: &mut Iterator<Item=ExtendedHeader>,
|
ancestry: &mut Iterator<Item=ExtendedHeader>,
|
||||||
) -> Result<LockedBlock, Error> {
|
) -> Result<LockedBlock, Error> {
|
||||||
{
|
// For trace log
|
||||||
if ::log::max_level() >= ::log::Level::Trace {
|
let trace_state = if log_enabled!(target: "enact", ::log::Level::Trace) {
|
||||||
let s = State::from_existing(db.boxed_clone(), parent.state_root().clone(), engine.account_start_nonce(parent.number() + 1), factories.clone())?;
|
Some(State::from_existing(db.boxed_clone(), parent.state_root().clone(), engine.account_start_nonce(parent.number() + 1), factories.clone())?)
|
||||||
trace!(target: "enact", "num={}, root={}, author={}, author_balance={}\n",
|
} else {
|
||||||
header.number(), s.root(), header.author(), s.balance(&header.author())?);
|
None
|
||||||
}
|
};
|
||||||
}
|
|
||||||
|
|
||||||
let mut b = OpenBlock::new(
|
let mut b = OpenBlock::new(
|
||||||
engine,
|
engine,
|
||||||
@ -491,13 +495,23 @@ fn enact(
|
|||||||
db,
|
db,
|
||||||
parent,
|
parent,
|
||||||
last_hashes,
|
last_hashes,
|
||||||
Address::new(),
|
// Engine such as Clique will calculate author from extra_data.
|
||||||
|
// this is only important for executing contracts as the 'executive_author'.
|
||||||
|
engine.executive_author(&header)?,
|
||||||
(3141562.into(), 31415620.into()),
|
(3141562.into(), 31415620.into()),
|
||||||
vec![],
|
vec![],
|
||||||
is_epoch_begin,
|
is_epoch_begin,
|
||||||
ancestry,
|
ancestry,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
if let Some(ref s) = trace_state {
|
||||||
|
let env = b.env_info();
|
||||||
|
let root = s.root();
|
||||||
|
let author_balance = s.balance(&env.author)?;
|
||||||
|
trace!(target: "enact", "num={}, root={}, author={}, author_balance={}\n",
|
||||||
|
b.block.header.number(), root, env.author, author_balance);
|
||||||
|
}
|
||||||
|
|
||||||
b.populate_from(&header);
|
b.populate_from(&header);
|
||||||
b.push_transactions(transactions)?;
|
b.push_transactions(transactions)?;
|
||||||
|
|
||||||
@ -563,6 +577,7 @@ mod tests {
|
|||||||
last_hashes: Arc<LastHashes>,
|
last_hashes: Arc<LastHashes>,
|
||||||
factories: Factories,
|
factories: Factories,
|
||||||
) -> Result<LockedBlock, Error> {
|
) -> Result<LockedBlock, Error> {
|
||||||
|
|
||||||
let block = Unverified::from_rlp(block_bytes)?;
|
let block = Unverified::from_rlp(block_bytes)?;
|
||||||
let header = block.header;
|
let header = block.header;
|
||||||
let transactions: Result<Vec<_>, Error> = block
|
let transactions: Result<Vec<_>, Error> = block
|
||||||
|
@ -399,6 +399,7 @@ impl Importer {
|
|||||||
let db = client.state_db.read().boxed_clone_canon(header.parent_hash());
|
let db = client.state_db.read().boxed_clone_canon(header.parent_hash());
|
||||||
|
|
||||||
let is_epoch_begin = chain.epoch_transition(parent.number(), *header.parent_hash()).is_some();
|
let is_epoch_begin = chain.epoch_transition(parent.number(), *header.parent_hash()).is_some();
|
||||||
|
|
||||||
let enact_result = enact_verified(
|
let enact_result = enact_verified(
|
||||||
block,
|
block,
|
||||||
engine,
|
engine,
|
||||||
@ -2515,7 +2516,11 @@ impl SnapshotClient for Client {}
|
|||||||
|
|
||||||
impl Drop for Client {
|
impl Drop for Client {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
self.engine.stop();
|
if let Some(c) = Arc::get_mut(&mut self.engine) {
|
||||||
|
c.stop()
|
||||||
|
} else {
|
||||||
|
warn!(target: "shutdown", "unable to get mut ref for engine for shutdown.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
369
ethcore/src/engines/clique/block_state.rs
Normal file
369
ethcore/src/engines/clique/block_state.rs
Normal file
@ -0,0 +1,369 @@
|
|||||||
|
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity Ethereum.
|
||||||
|
|
||||||
|
// Parity Ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity Ethereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use std::collections::{HashMap, BTreeSet, VecDeque};
|
||||||
|
use std::fmt;
|
||||||
|
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
|
use engines::EngineError;
|
||||||
|
use engines::clique::util::{extract_signers, recover_creator};
|
||||||
|
use engines::clique::{VoteType, DIFF_INTURN, DIFF_NOTURN, NULL_AUTHOR, SIGNING_DELAY_NOTURN_MS};
|
||||||
|
use error::{Error, BlockError};
|
||||||
|
use ethereum_types::{Address, H64};
|
||||||
|
use rand::Rng;
|
||||||
|
use types::BlockNumber;
|
||||||
|
use types::header::Header;
|
||||||
|
use unexpected::Mismatch;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "time_checked_add"))]
|
||||||
|
use time_utils::CheckedSystemTime;
|
||||||
|
|
||||||
|
/// Type that keeps track of the state for a given vote
|
||||||
|
// Votes that go against the proposal aren't counted since it's equivalent to not voting
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
|
||||||
|
pub struct VoteState {
|
||||||
|
kind: VoteType,
|
||||||
|
votes: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Type that represent a vote
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
|
||||||
|
pub struct Vote {
|
||||||
|
block_number: BlockNumber,
|
||||||
|
beneficiary: Address,
|
||||||
|
kind: VoteType,
|
||||||
|
signer: Address,
|
||||||
|
reverted: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Type that represent a pending vote
|
||||||
|
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd)]
|
||||||
|
pub struct PendingVote {
|
||||||
|
signer: Address,
|
||||||
|
beneficiary: Address,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clique state for each block.
|
||||||
|
#[cfg(not(test))]
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
pub struct CliqueBlockState {
|
||||||
|
/// Current votes for a beneficiary
|
||||||
|
votes: HashMap<PendingVote, VoteState>,
|
||||||
|
/// A list of all votes for the given epoch
|
||||||
|
votes_history: Vec<Vote>,
|
||||||
|
/// a list of all valid signer, sorted by ascending order.
|
||||||
|
signers: BTreeSet<Address>,
|
||||||
|
/// a deque of recent signer, new entry should be pushed front, apply() modifies this.
|
||||||
|
recent_signers: VecDeque<Address>,
|
||||||
|
/// inturn signing should wait until this time
|
||||||
|
pub next_timestamp_inturn: Option<SystemTime>,
|
||||||
|
/// noturn signing should wait until this time
|
||||||
|
pub next_timestamp_noturn: Option<SystemTime>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
pub struct CliqueBlockState {
|
||||||
|
/// All recorded votes for a given signer, `Vec<PendingVote>` is a stack of votes
|
||||||
|
pub votes: HashMap<PendingVote, VoteState>,
|
||||||
|
/// A list of all votes for the given epoch
|
||||||
|
pub votes_history: Vec<Vote>,
|
||||||
|
/// a list of all valid signer, sorted by ascending order.
|
||||||
|
pub signers: BTreeSet<Address>,
|
||||||
|
/// a deque of recent signer, new entry should be pushed front, apply() modifies this.
|
||||||
|
pub recent_signers: VecDeque<Address>,
|
||||||
|
/// inturn signing should wait until this time
|
||||||
|
pub next_timestamp_inturn: Option<SystemTime>,
|
||||||
|
/// noturn signing should wait until this time
|
||||||
|
pub next_timestamp_noturn: Option<SystemTime>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for CliqueBlockState {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
let signers: Vec<String> = self.signers.iter()
|
||||||
|
.map(|s|
|
||||||
|
format!("{} {:?}",
|
||||||
|
s,
|
||||||
|
self.votes.iter().map(|(v, s)| format!("[beneficiary {}, votes: {}]", v.beneficiary, s.votes))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let recent_signers: Vec<String> = self.recent_signers.iter().map(|s| format!("{}", s)).collect();
|
||||||
|
let num_votes = self.votes_history.len();
|
||||||
|
let add_votes = self.votes_history.iter().filter(|v| v.kind == VoteType::Add).count();
|
||||||
|
let rm_votes = self.votes_history.iter().filter(|v| v.kind == VoteType::Remove).count();
|
||||||
|
let reverted_votes = self.votes_history.iter().filter(|v| v.reverted).count();
|
||||||
|
|
||||||
|
write!(f,
|
||||||
|
"Votes {{ \n signers: {:?} \n recent_signers: {:?} \n number of votes: {} \n number of add votes {}
|
||||||
|
\r number of remove votes {} \n number of reverted votes: {}}}",
|
||||||
|
signers, recent_signers, num_votes, add_votes, rm_votes, reverted_votes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CliqueBlockState {
|
||||||
|
/// Create new state with given information, this is used creating new state from Checkpoint block.
|
||||||
|
pub fn new(signers: BTreeSet<Address>) -> Self {
|
||||||
|
CliqueBlockState {
|
||||||
|
signers,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// see https://github.com/ethereum/go-ethereum/blob/master/consensus/clique/clique.go#L474
|
||||||
|
fn verify(&self, header: &Header) -> Result<Address, Error> {
|
||||||
|
let creator = recover_creator(header)?.clone();
|
||||||
|
|
||||||
|
// The signer is not authorized
|
||||||
|
if !self.signers.contains(&creator) {
|
||||||
|
trace!(target: "engine", "current state: {}", self);
|
||||||
|
Err(EngineError::NotAuthorized(creator))?
|
||||||
|
}
|
||||||
|
|
||||||
|
// The signer has signed a block too recently
|
||||||
|
if self.recent_signers.contains(&creator) {
|
||||||
|
trace!(target: "engine", "current state: {}", self);
|
||||||
|
Err(EngineError::CliqueTooRecentlySigned(creator))?
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrong difficulty
|
||||||
|
let inturn = self.is_inturn(header.number(), &creator);
|
||||||
|
|
||||||
|
if inturn && *header.difficulty() != DIFF_INTURN {
|
||||||
|
Err(BlockError::InvalidDifficulty(Mismatch {
|
||||||
|
expected: DIFF_INTURN,
|
||||||
|
found: *header.difficulty(),
|
||||||
|
}))?
|
||||||
|
}
|
||||||
|
|
||||||
|
if !inturn && *header.difficulty() != DIFF_NOTURN {
|
||||||
|
Err(BlockError::InvalidDifficulty(Mismatch {
|
||||||
|
expected: DIFF_NOTURN,
|
||||||
|
found: *header.difficulty(),
|
||||||
|
}))?
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(creator)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Verify and apply a new header to current state
|
||||||
|
pub fn apply(&mut self, header: &Header, is_checkpoint: bool) -> Result<Address, Error> {
|
||||||
|
let creator = self.verify(header)?;
|
||||||
|
self.recent_signers.push_front(creator);
|
||||||
|
self.rotate_recent_signers();
|
||||||
|
|
||||||
|
if is_checkpoint {
|
||||||
|
// checkpoint block should not affect previous tallying, so we check that.
|
||||||
|
let signers = extract_signers(header)?;
|
||||||
|
if self.signers != signers {
|
||||||
|
let invalid_signers: Vec<String> = signers.into_iter()
|
||||||
|
.filter(|s| !self.signers.contains(s))
|
||||||
|
.map(|s| format!("{}", s))
|
||||||
|
.collect();
|
||||||
|
Err(EngineError::CliqueFaultyRecoveredSigners(invalid_signers))?
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO(niklasad1): I'm not sure if we should shrink here because it is likely that next epoch
|
||||||
|
// will need some memory and might be better for allocation algorithm to decide whether to shrink or not
|
||||||
|
// (typically doubles or halves the allocted memory when necessary)
|
||||||
|
self.votes.clear();
|
||||||
|
self.votes_history.clear();
|
||||||
|
self.votes.shrink_to_fit();
|
||||||
|
self.votes_history.shrink_to_fit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contains vote
|
||||||
|
if *header.author() != NULL_AUTHOR {
|
||||||
|
let decoded_seal = header.decode_seal::<Vec<_>>()?;
|
||||||
|
if decoded_seal.len() != 2 {
|
||||||
|
Err(BlockError::InvalidSealArity(Mismatch { expected: 2, found: decoded_seal.len() }))?
|
||||||
|
}
|
||||||
|
|
||||||
|
let nonce: H64 = decoded_seal[1].into();
|
||||||
|
self.update_signers_on_vote(VoteType::from_nonce(nonce)?, creator, *header.author(), header.number())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(creator)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_signers_on_vote(
|
||||||
|
&mut self,
|
||||||
|
kind: VoteType,
|
||||||
|
signer: Address,
|
||||||
|
beneficiary: Address,
|
||||||
|
block_number: u64
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
|
||||||
|
trace!(target: "engine", "Attempt vote {:?} {:?}", kind, beneficiary);
|
||||||
|
|
||||||
|
let pending_vote = PendingVote { signer, beneficiary };
|
||||||
|
|
||||||
|
let reverted = if self.is_valid_vote(&beneficiary, kind) {
|
||||||
|
self.add_vote(pending_vote, kind)
|
||||||
|
} else {
|
||||||
|
// This case only happens if a `signer` wants to revert their previous vote
|
||||||
|
// (does nothing if no previous vote was found)
|
||||||
|
self.revert_vote(pending_vote)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add all votes to the history
|
||||||
|
self.votes_history.push(
|
||||||
|
Vote {
|
||||||
|
block_number,
|
||||||
|
beneficiary,
|
||||||
|
kind,
|
||||||
|
signer,
|
||||||
|
reverted,
|
||||||
|
});
|
||||||
|
|
||||||
|
// If no vote was found for the beneficiary return `early` but don't propogate an error
|
||||||
|
let (votes, vote_kind) = match self.get_current_votes_and_kind(beneficiary) {
|
||||||
|
Some((v, k)) => (v, k),
|
||||||
|
None => return Ok(()),
|
||||||
|
};
|
||||||
|
let threshold = self.signers.len() / 2;
|
||||||
|
|
||||||
|
debug!(target: "engine", "{}/{} votes to have consensus", votes, threshold + 1);
|
||||||
|
trace!(target: "engine", "votes: {:?}", votes);
|
||||||
|
|
||||||
|
if votes > threshold {
|
||||||
|
match vote_kind {
|
||||||
|
VoteType::Add => {
|
||||||
|
if self.signers.insert(beneficiary) {
|
||||||
|
debug!(target: "engine", "added new signer: {}", beneficiary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
VoteType::Remove => {
|
||||||
|
if self.signers.remove(&beneficiary) {
|
||||||
|
debug!(target: "engine", "removed signer: {}", beneficiary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.rotate_recent_signers();
|
||||||
|
self.remove_all_votes_from(beneficiary);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculate the next timestamp for `inturn` and `noturn` fails if any of them can't be represented as
|
||||||
|
/// `SystemTime`
|
||||||
|
// TODO(niklasad1): refactor this method to be in constructor of `CliqueBlockState` instead.
|
||||||
|
// This is a quite bad API because we must mutate both variables even when already `inturn` fails
|
||||||
|
// That's why we can't return early and must have the `if-else` in the end
|
||||||
|
pub fn calc_next_timestamp(&mut self, timestamp: u64, period: u64) -> Result<(), Error> {
|
||||||
|
let inturn = UNIX_EPOCH.checked_add(Duration::from_secs(timestamp.saturating_add(period)));
|
||||||
|
|
||||||
|
self.next_timestamp_inturn = inturn;
|
||||||
|
|
||||||
|
let delay = Duration::from_millis(
|
||||||
|
rand::thread_rng().gen_range(0u64, (self.signers.len() as u64 / 2 + 1) * SIGNING_DELAY_NOTURN_MS));
|
||||||
|
self.next_timestamp_noturn = inturn.map(|inturn| {
|
||||||
|
inturn + delay
|
||||||
|
});
|
||||||
|
|
||||||
|
if self.next_timestamp_inturn.is_some() && self.next_timestamp_noturn.is_some() {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(BlockError::TimestampOverflow)?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if the block difficulty should be `inturn`
|
||||||
|
pub fn is_inturn(&self, current_block_number: u64, author: &Address) -> bool {
|
||||||
|
if let Some(pos) = self.signers.iter().position(|x| *author == *x) {
|
||||||
|
return current_block_number % self.signers.len() as u64 == pos as u64;
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns whether the signer is authorized to sign a block
|
||||||
|
pub fn is_authorized(&self, author: &Address) -> bool {
|
||||||
|
self.signers.contains(author) && !self.recent_signers.contains(author)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns whether it makes sense to cast the specified vote in the
|
||||||
|
/// current state (e.g. don't try to add an already authorized signer).
|
||||||
|
pub fn is_valid_vote(&self, address: &Address, vote_type: VoteType) -> bool {
|
||||||
|
let in_signer = self.signers.contains(address);
|
||||||
|
match vote_type {
|
||||||
|
VoteType::Add => !in_signer,
|
||||||
|
VoteType::Remove => in_signer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the list of current signers
|
||||||
|
pub fn signers(&self) -> &BTreeSet<Address> {
|
||||||
|
&self.signers
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note this method will always return `true` but it is intended for a uniform `API`
|
||||||
|
fn add_vote(&mut self, pending_vote: PendingVote, kind: VoteType) -> bool {
|
||||||
|
|
||||||
|
self.votes.entry(pending_vote)
|
||||||
|
.and_modify(|state| {
|
||||||
|
state.votes = state.votes.saturating_add(1);
|
||||||
|
})
|
||||||
|
.or_insert_with(|| VoteState { kind, votes: 1 });
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn revert_vote(&mut self, pending_vote: PendingVote) -> bool {
|
||||||
|
let mut revert = false;
|
||||||
|
let mut remove = false;
|
||||||
|
|
||||||
|
self.votes.entry(pending_vote).and_modify(|state| {
|
||||||
|
if state.votes.saturating_sub(1) == 0 {
|
||||||
|
remove = true;
|
||||||
|
}
|
||||||
|
revert = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
if remove {
|
||||||
|
self.votes.remove(&pending_vote);
|
||||||
|
}
|
||||||
|
|
||||||
|
revert
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_current_votes_and_kind(&self, beneficiary: Address) -> Option<(usize, VoteType)> {
|
||||||
|
let kind = self.votes.iter()
|
||||||
|
.find(|(v, _t)| v.beneficiary == beneficiary)
|
||||||
|
.map(|(_v, t)| t.kind)?;
|
||||||
|
|
||||||
|
let votes = self.votes.keys()
|
||||||
|
.filter(|vote| vote.beneficiary == beneficiary)
|
||||||
|
.count();
|
||||||
|
|
||||||
|
Some((votes, kind))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rotate_recent_signers(&mut self) {
|
||||||
|
if self.recent_signers.len() >= ( self.signers.len() / 2 ) + 1 {
|
||||||
|
self.recent_signers.pop_back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_all_votes_from(&mut self, beneficiary: Address) {
|
||||||
|
self.votes = std::mem::replace(&mut self.votes, HashMap::new())
|
||||||
|
.into_iter()
|
||||||
|
.filter(|(v, _t)| v.signer != beneficiary && v.beneficiary != beneficiary)
|
||||||
|
.collect();
|
||||||
|
}
|
||||||
|
}
|
768
ethcore/src/engines/clique/mod.rs
Normal file
768
ethcore/src/engines/clique/mod.rs
Normal file
@ -0,0 +1,768 @@
|
|||||||
|
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity Ethereum.
|
||||||
|
|
||||||
|
// Parity Ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity Ethereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
//! Implementation of the Clique PoA Engine.
|
||||||
|
//!
|
||||||
|
//! File structure:
|
||||||
|
//! - mod.rs -> Provides the engine API implementation, with additional block state tracking
|
||||||
|
//! - block_state.rs -> Records the Clique state for given block.
|
||||||
|
//! - params.rs -> Contains the parameters for the Clique engine.
|
||||||
|
//! - step_service.rs -> An event loop to trigger sealing.
|
||||||
|
//! - util.rs -> Various standalone utility functions.
|
||||||
|
//! - tests.rs -> Consensus tests as defined in EIP-225.
|
||||||
|
|
||||||
|
/// How syncing works:
|
||||||
|
///
|
||||||
|
/// 1. Client will call:
|
||||||
|
/// - `Clique::verify_block_basic()`
|
||||||
|
/// - `Clique::verify_block_unordered()`
|
||||||
|
/// - `Clique::verify_block_family()`
|
||||||
|
/// 2. Using `Clique::state()` we try and retrieve the parent state. If this isn't found
|
||||||
|
/// we need to back-fill it from the last known checkpoint.
|
||||||
|
/// 3. Once we have a good state, we can record it using `CliqueBlockState::apply()`.
|
||||||
|
|
||||||
|
/// How sealing works:
|
||||||
|
///
|
||||||
|
/// 1. Set a signer using `Engine::set_signer()`. If a miner account was set up through
|
||||||
|
/// a config file or CLI flag `MinerService::set_author()` will eventually set the signer
|
||||||
|
/// 2. We check that the engine seals internally through `Clique::seals_internally()`
|
||||||
|
/// Note: This is always true for Clique
|
||||||
|
/// 3. Calling `Clique::new()` will spawn a `StepService` thread. This thread will call `Engine::step()`
|
||||||
|
/// periodically. Internally, the Clique `step()` function calls `Client::update_sealing()`, which is
|
||||||
|
/// what makes and seals a block.
|
||||||
|
/// 4. `Clique::generate_seal()` will then be called by `miner`. This will return a `Seal` which
|
||||||
|
/// is either a `Seal::None` or `Seal:Regular`. The following shows how a `Seal` variant is chosen:
|
||||||
|
/// a. We return `Seal::None` if no signer is available or the signer is not authorized.
|
||||||
|
/// b. If period == 0 and block has transactions, we return `Seal::Regular`, otherwise return `Seal::None`.
|
||||||
|
/// c. If we're `INTURN`, wait for at least `period` since last block before trying to seal.
|
||||||
|
/// d. If we're not `INTURN`, we wait for a random amount of time using the algorithm specified
|
||||||
|
/// in EIP-225 before trying to seal again.
|
||||||
|
/// 5. Miner will create new block, in process it will call several engine methods to do following:
|
||||||
|
/// a. `Clique::open_block_header_timestamp()` must set timestamp correctly.
|
||||||
|
/// b. `Clique::populate_from_parent()` must set difficulty to correct value.
|
||||||
|
/// Note: `Clique::populate_from_parent()` is used in both the syncing and sealing code paths.
|
||||||
|
/// 6. We call `Clique::on_seal_block()` which will allow us to modify the block header during seal generation.
|
||||||
|
/// 7. Finally, `Clique::verify_local_seal()` is called. After this, the syncing code path will be followed
|
||||||
|
/// in order to import the new block.
|
||||||
|
|
||||||
|
use std::cmp;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
use std::sync::{Arc, Weak};
|
||||||
|
use std::thread;
|
||||||
|
use std::time;
|
||||||
|
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
|
use block::ExecutedBlock;
|
||||||
|
use client::{BlockId, EngineClient};
|
||||||
|
use engines::clique::util::{extract_signers, recover_creator};
|
||||||
|
use engines::{Engine, EngineError, Seal};
|
||||||
|
use error::{BlockError, Error};
|
||||||
|
use ethereum_types::{Address, H64, H160, H256, U256};
|
||||||
|
use ethkey::Signature;
|
||||||
|
use hash::KECCAK_EMPTY_LIST_RLP;
|
||||||
|
use itertools::Itertools;
|
||||||
|
use lru_cache::LruCache;
|
||||||
|
use machine::{Call, EthereumMachine};
|
||||||
|
use parking_lot::RwLock;
|
||||||
|
use rand::Rng;
|
||||||
|
use super::signer::EngineSigner;
|
||||||
|
use unexpected::{Mismatch, OutOfBounds};
|
||||||
|
use types::BlockNumber;
|
||||||
|
use types::header::{ExtendedHeader, Header};
|
||||||
|
|
||||||
|
#[cfg(not(feature = "time_checked_add"))]
|
||||||
|
use time_utils::CheckedSystemTime;
|
||||||
|
|
||||||
|
use self::block_state::CliqueBlockState;
|
||||||
|
use self::params::CliqueParams;
|
||||||
|
use self::step_service::StepService;
|
||||||
|
|
||||||
|
mod params;
|
||||||
|
mod block_state;
|
||||||
|
mod step_service;
|
||||||
|
mod util;
|
||||||
|
|
||||||
|
// TODO(niklasad1): extract tester types into a separate mod to be shared in the code base
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
|
||||||
|
// Protocol constants
|
||||||
|
/// Fixed number of extra-data prefix bytes reserved for signer vanity
|
||||||
|
pub const VANITY_LENGTH: usize = 32;
|
||||||
|
/// Fixed number of extra-data suffix bytes reserved for signer signature
|
||||||
|
pub const SIGNATURE_LENGTH: usize = 65;
|
||||||
|
/// Address length of signer
|
||||||
|
pub const ADDRESS_LENGTH: usize = 20;
|
||||||
|
/// Nonce value for DROP vote
|
||||||
|
pub const NONCE_DROP_VOTE: H64 = H64([0; 8]);
|
||||||
|
/// Nonce value for AUTH vote
|
||||||
|
pub const NONCE_AUTH_VOTE: H64 = H64([0xff; 8]);
|
||||||
|
/// Difficulty for INTURN block
|
||||||
|
pub const DIFF_INTURN: U256 = U256([2, 0, 0, 0]);
|
||||||
|
/// Difficulty for NOTURN block
|
||||||
|
pub const DIFF_NOTURN: U256 = U256([1, 0, 0, 0]);
|
||||||
|
/// Default empty author field value
|
||||||
|
pub const NULL_AUTHOR: Address = H160([0x00; 20]);
|
||||||
|
/// Default empty nonce value
|
||||||
|
pub const NULL_NONCE: H64 = NONCE_DROP_VOTE;
|
||||||
|
/// Default value for mixhash
|
||||||
|
pub const NULL_MIXHASH: H256 = H256([0; 32]);
|
||||||
|
/// Default value for uncles hash
|
||||||
|
pub const NULL_UNCLES_HASH: H256 = KECCAK_EMPTY_LIST_RLP;
|
||||||
|
/// Default noturn block wiggle factor defined in spec.
|
||||||
|
pub const SIGNING_DELAY_NOTURN_MS: u64 = 500;
|
||||||
|
|
||||||
|
/// How many CliqueBlockState to cache in the memory.
|
||||||
|
pub const STATE_CACHE_NUM: usize = 128;
|
||||||
|
|
||||||
|
/// Vote to add or remove the beneficiary
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
|
||||||
|
pub enum VoteType {
|
||||||
|
Add,
|
||||||
|
Remove,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VoteType {
|
||||||
|
/// Try to construct a `Vote` from a nonce
|
||||||
|
pub fn from_nonce(nonce: H64) -> Result<Self, Error> {
|
||||||
|
if nonce == NONCE_AUTH_VOTE {
|
||||||
|
Ok(VoteType::Add)
|
||||||
|
} else if nonce == NONCE_DROP_VOTE {
|
||||||
|
Ok(VoteType::Remove)
|
||||||
|
} else {
|
||||||
|
Err(EngineError::CliqueInvalidNonce(nonce))?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the rlp encoding of the vote
|
||||||
|
pub fn as_rlp(&self) -> Vec<Vec<u8>> {
|
||||||
|
match self {
|
||||||
|
VoteType::Add => vec![rlp::encode(&NULL_MIXHASH), rlp::encode(&NONCE_AUTH_VOTE)],
|
||||||
|
VoteType::Remove => vec![rlp::encode(&NULL_MIXHASH), rlp::encode(&NONCE_DROP_VOTE)],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clique Engine implementation
|
||||||
|
// block_state_by_hash -> block state indexed by header hash.
|
||||||
|
#[cfg(not(test))]
|
||||||
|
pub struct Clique {
|
||||||
|
epoch_length: u64,
|
||||||
|
period: u64,
|
||||||
|
machine: EthereumMachine,
|
||||||
|
client: RwLock<Option<Weak<EngineClient>>>,
|
||||||
|
block_state_by_hash: RwLock<LruCache<H256, CliqueBlockState>>,
|
||||||
|
proposals: RwLock<HashMap<Address, VoteType>>,
|
||||||
|
signer: RwLock<Option<Box<EngineSigner>>>,
|
||||||
|
step_service: Option<Arc<StepService>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
/// Test version of `CliqueEngine` to make all fields public
|
||||||
|
pub struct Clique {
|
||||||
|
pub epoch_length: u64,
|
||||||
|
pub period: u64,
|
||||||
|
pub machine: EthereumMachine,
|
||||||
|
pub client: RwLock<Option<Weak<EngineClient>>>,
|
||||||
|
pub block_state_by_hash: RwLock<LruCache<H256, CliqueBlockState>>,
|
||||||
|
pub proposals: RwLock<HashMap<Address, VoteType>>,
|
||||||
|
pub signer: RwLock<Option<Box<EngineSigner>>>,
|
||||||
|
pub step_service: Option<Arc<StepService>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clique {
|
||||||
|
/// Initialize Clique engine from empty state.
|
||||||
|
pub fn new(our_params: CliqueParams, machine: EthereumMachine) -> Result<Arc<Self>, Error> {
|
||||||
|
let mut engine = Clique {
|
||||||
|
epoch_length: our_params.epoch,
|
||||||
|
period: our_params.period,
|
||||||
|
client: Default::default(),
|
||||||
|
block_state_by_hash: RwLock::new(LruCache::new(STATE_CACHE_NUM)),
|
||||||
|
proposals: Default::default(),
|
||||||
|
signer: Default::default(),
|
||||||
|
machine,
|
||||||
|
step_service: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let res = Arc::new(engine);
|
||||||
|
|
||||||
|
if our_params.period > 0 {
|
||||||
|
engine.step_service = Some(StepService::start(Arc::downgrade(&res) as Weak<Engine<_>>));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
/// Initialize test variant of `CliqueEngine`,
|
||||||
|
/// Note we need to `mock` the miner and it is introduced to test block verification to trigger new blocks
|
||||||
|
/// to mainly test consensus edge cases
|
||||||
|
pub fn with_test(epoch_length: u64, period: u64) -> Self {
|
||||||
|
use spec::Spec;
|
||||||
|
|
||||||
|
Self {
|
||||||
|
epoch_length,
|
||||||
|
period,
|
||||||
|
client: Default::default(),
|
||||||
|
block_state_by_hash: RwLock::new(LruCache::new(STATE_CACHE_NUM)),
|
||||||
|
proposals: Default::default(),
|
||||||
|
signer: Default::default(),
|
||||||
|
machine: Spec::new_test_machine(),
|
||||||
|
step_service: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sign_header(&self, header: &Header) -> Result<(Signature, H256), Error> {
|
||||||
|
|
||||||
|
match self.signer.read().as_ref() {
|
||||||
|
None => {
|
||||||
|
Err(EngineError::RequiresSigner)?
|
||||||
|
}
|
||||||
|
Some(signer) => {
|
||||||
|
let digest = header.hash();
|
||||||
|
match signer.sign(digest) {
|
||||||
|
Ok(sig) => Ok((sig, digest)),
|
||||||
|
Err(e) => Err(EngineError::Custom(e.into()))?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Construct an new state from given checkpoint header.
|
||||||
|
fn new_checkpoint_state(&self, header: &Header) -> Result<CliqueBlockState, Error> {
|
||||||
|
debug_assert_eq!(header.number() % self.epoch_length, 0);
|
||||||
|
|
||||||
|
let mut state = CliqueBlockState::new(
|
||||||
|
extract_signers(header)?);
|
||||||
|
|
||||||
|
// TODO(niklasad1): refactor to perform this check in the `CliqueBlockState` constructor instead
|
||||||
|
state.calc_next_timestamp(header.timestamp(), self.period)?;
|
||||||
|
|
||||||
|
Ok(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn state_no_backfill(&self, hash: &H256) -> Option<CliqueBlockState> {
|
||||||
|
self.block_state_by_hash.write().get_mut(hash).cloned()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get `CliqueBlockState` for given header, backfill from last checkpoint if needed.
|
||||||
|
fn state(&self, header: &Header) -> Result<CliqueBlockState, Error> {
|
||||||
|
let mut block_state_by_hash = self.block_state_by_hash.write();
|
||||||
|
if let Some(state) = block_state_by_hash.get_mut(&header.hash()) {
|
||||||
|
return Ok(state.clone());
|
||||||
|
}
|
||||||
|
// If we are looking for an checkpoint block state, we can directly reconstruct it.
|
||||||
|
if header.number() % self.epoch_length == 0 {
|
||||||
|
let state = self.new_checkpoint_state(header)?;
|
||||||
|
block_state_by_hash.insert(header.hash(), state.clone());
|
||||||
|
return Ok(state);
|
||||||
|
}
|
||||||
|
// BlockState is not found in memory, which means we need to reconstruct state from last checkpoint.
|
||||||
|
match self.client.read().as_ref().and_then(|w| w.upgrade()) {
|
||||||
|
None => {
|
||||||
|
return Err(EngineError::RequiresClient)?;
|
||||||
|
}
|
||||||
|
Some(c) => {
|
||||||
|
let last_checkpoint_number = header.number() - header.number() % self.epoch_length as u64;
|
||||||
|
debug_assert_ne!(last_checkpoint_number, header.number());
|
||||||
|
|
||||||
|
let mut chain: &mut VecDeque<Header> = &mut VecDeque::with_capacity(
|
||||||
|
(header.number() - last_checkpoint_number + 1) as usize);
|
||||||
|
|
||||||
|
// Put ourselves in.
|
||||||
|
chain.push_front(header.clone());
|
||||||
|
|
||||||
|
// populate chain to last checkpoint
|
||||||
|
loop {
|
||||||
|
let (last_parent_hash, last_num) = {
|
||||||
|
let l = chain.front().expect("chain has at least one element; qed");
|
||||||
|
(*l.parent_hash(), l.number())
|
||||||
|
};
|
||||||
|
|
||||||
|
if last_num == last_checkpoint_number + 1 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
match c.block_header(BlockId::Hash(last_parent_hash)) {
|
||||||
|
None => {
|
||||||
|
return Err(BlockError::UnknownParent(last_parent_hash))?;
|
||||||
|
}
|
||||||
|
Some(next) => {
|
||||||
|
chain.push_front(next.decode()?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Catching up state, note that we don't really store block state for intermediary blocks,
|
||||||
|
// for speed.
|
||||||
|
let backfill_start = time::Instant::now();
|
||||||
|
trace!(target: "engine",
|
||||||
|
"Back-filling block state. last_checkpoint_number: {}, target: {}({}).",
|
||||||
|
last_checkpoint_number, header.number(), header.hash());
|
||||||
|
|
||||||
|
// Get the state for last checkpoint.
|
||||||
|
let last_checkpoint_hash = *chain.front()
|
||||||
|
.expect("chain has at least one element; qed")
|
||||||
|
.parent_hash();
|
||||||
|
|
||||||
|
let last_checkpoint_header = match c.block_header(BlockId::Hash(last_checkpoint_hash)) {
|
||||||
|
None => return Err(EngineError::CliqueMissingCheckpoint(last_checkpoint_hash))?,
|
||||||
|
Some(header) => header.decode()?,
|
||||||
|
};
|
||||||
|
|
||||||
|
let last_checkpoint_state = match block_state_by_hash.get_mut(&last_checkpoint_hash) {
|
||||||
|
Some(state) => state.clone(),
|
||||||
|
None => self.new_checkpoint_state(&last_checkpoint_header)?,
|
||||||
|
};
|
||||||
|
|
||||||
|
block_state_by_hash.insert(last_checkpoint_header.hash(), last_checkpoint_state.clone());
|
||||||
|
|
||||||
|
// Backfill!
|
||||||
|
let mut new_state = last_checkpoint_state.clone();
|
||||||
|
for item in chain {
|
||||||
|
new_state.apply(item, false)?;
|
||||||
|
}
|
||||||
|
new_state.calc_next_timestamp(header.timestamp(), self.period)?;
|
||||||
|
block_state_by_hash.insert(header.hash(), new_state.clone());
|
||||||
|
|
||||||
|
let elapsed = backfill_start.elapsed();
|
||||||
|
trace!(target: "engine", "Back-filling succeed, took {} ms.", elapsed.as_millis());
|
||||||
|
Ok(new_state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Engine<EthereumMachine> for Clique {
|
||||||
|
fn name(&self) -> &str { "Clique" }
|
||||||
|
|
||||||
|
fn machine(&self) -> &EthereumMachine { &self.machine }
|
||||||
|
|
||||||
|
// Clique use same fields, nonce + mixHash
|
||||||
|
fn seal_fields(&self, _header: &Header) -> usize { 2 }
|
||||||
|
|
||||||
|
fn maximum_uncle_count(&self, _block: BlockNumber) -> usize { 0 }
|
||||||
|
|
||||||
|
fn on_new_block(
|
||||||
|
&self,
|
||||||
|
_block: &mut ExecutedBlock,
|
||||||
|
_epoch_begin: bool,
|
||||||
|
_ancestry: &mut Iterator<Item=ExtendedHeader>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clique has no block reward.
|
||||||
|
fn on_close_block(&self, _block: &mut ExecutedBlock) -> Result<(), Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_seal_block(&self, block: &mut ExecutedBlock) -> Result<(), Error> {
|
||||||
|
trace!(target: "engine", "on_seal_block");
|
||||||
|
|
||||||
|
let header = &mut block.header;
|
||||||
|
|
||||||
|
let state = self.state_no_backfill(header.parent_hash())
|
||||||
|
.ok_or_else(|| BlockError::UnknownParent(*header.parent_hash()))?;
|
||||||
|
|
||||||
|
let is_checkpoint = header.number() % self.epoch_length == 0;
|
||||||
|
|
||||||
|
header.set_author(NULL_AUTHOR);
|
||||||
|
|
||||||
|
// Cast a random Vote if not checkpoint
|
||||||
|
if !is_checkpoint {
|
||||||
|
// TODO(niklasad1): this will always be false because `proposals` is never written to
|
||||||
|
let votes = self.proposals.read().iter()
|
||||||
|
.filter(|(address, vote_type)| state.is_valid_vote(*address, **vote_type))
|
||||||
|
.map(|(address, vote_type)| (*address, *vote_type))
|
||||||
|
.collect_vec();
|
||||||
|
|
||||||
|
if !votes.is_empty() {
|
||||||
|
// Pick a random vote.
|
||||||
|
let random_vote = rand::thread_rng().gen_range(0 as usize, votes.len());
|
||||||
|
let (beneficiary, vote_type) = votes[random_vote];
|
||||||
|
|
||||||
|
trace!(target: "engine", "Casting vote: beneficiary {}, type {:?} ", beneficiary, vote_type);
|
||||||
|
|
||||||
|
header.set_author(beneficiary);
|
||||||
|
header.set_seal(vote_type.as_rlp());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Work on clique seal.
|
||||||
|
|
||||||
|
let mut seal: Vec<u8> = Vec::with_capacity(VANITY_LENGTH + SIGNATURE_LENGTH);
|
||||||
|
|
||||||
|
// At this point, extra_data should only contain miner vanity.
|
||||||
|
if header.extra_data().len() != VANITY_LENGTH {
|
||||||
|
Err(BlockError::ExtraDataOutOfBounds(OutOfBounds {
|
||||||
|
min: Some(VANITY_LENGTH),
|
||||||
|
max: Some(VANITY_LENGTH),
|
||||||
|
found: header.extra_data().len()
|
||||||
|
}))?;
|
||||||
|
}
|
||||||
|
// vanity
|
||||||
|
{
|
||||||
|
seal.extend_from_slice(&header.extra_data()[0..VANITY_LENGTH]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we are building an checkpoint block, add all signers now.
|
||||||
|
if is_checkpoint {
|
||||||
|
seal.reserve(state.signers().len() * 20);
|
||||||
|
state.signers().iter().foreach(|addr| {
|
||||||
|
seal.extend_from_slice(&addr[..]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
header.set_extra_data(seal.clone());
|
||||||
|
|
||||||
|
// append signature onto extra_data
|
||||||
|
let (sig, _msg) = self.sign_header(&header)?;
|
||||||
|
seal.extend_from_slice(&sig[..]);
|
||||||
|
header.set_extra_data(seal.clone());
|
||||||
|
|
||||||
|
header.compute_hash();
|
||||||
|
|
||||||
|
// locally sealed block don't go through valid_block_family(), so we have to record state here.
|
||||||
|
let mut new_state = state.clone();
|
||||||
|
new_state.apply(&header, is_checkpoint)?;
|
||||||
|
new_state.calc_next_timestamp(header.timestamp(), self.period)?;
|
||||||
|
self.block_state_by_hash.write().insert(header.hash(), new_state);
|
||||||
|
|
||||||
|
trace!(target: "engine", "on_seal_block: finished, final header: {:?}", header);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clique doesn't require external work to seal, so we always return true here.
|
||||||
|
fn seals_internally(&self) -> Option<bool> {
|
||||||
|
Some(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns if we are ready to seal, the real sealing (signing extra_data) is actually done in `on_seal_block()`.
|
||||||
|
fn generate_seal(&self, block: &ExecutedBlock, parent: &Header) -> Seal {
|
||||||
|
trace!(target: "engine", "tried to generate_seal");
|
||||||
|
let null_seal = util::null_seal();
|
||||||
|
|
||||||
|
if block.header.number() == 0 {
|
||||||
|
trace!(target: "engine", "attempted to seal genesis block");
|
||||||
|
return Seal::None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if sealing period is 0, and not an checkpoint block, refuse to seal
|
||||||
|
if self.period == 0 {
|
||||||
|
if block.transactions.is_empty() && block.header.number() % self.epoch_length != 0 {
|
||||||
|
return Seal::None;
|
||||||
|
}
|
||||||
|
return Seal::Regular(null_seal);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check we actually have authority to seal.
|
||||||
|
if let Some(author) = self.signer.read().as_ref().map(|x| x.address()) {
|
||||||
|
|
||||||
|
// ensure the voting state exists
|
||||||
|
match self.state(&parent) {
|
||||||
|
Err(e) => {
|
||||||
|
warn!(target: "engine", "generate_seal: can't get parent state(number: {}, hash: {}): {} ",
|
||||||
|
parent.number(), parent.hash(), e);
|
||||||
|
return Seal::None;
|
||||||
|
}
|
||||||
|
Ok(state) => {
|
||||||
|
// Are we authorized to seal?
|
||||||
|
if !state.is_authorized(&author) {
|
||||||
|
trace!(target: "engine", "generate_seal: Not authorized to sign right now.");
|
||||||
|
// wait for one third of period to try again.
|
||||||
|
thread::sleep(Duration::from_secs(self.period / 3 + 1));
|
||||||
|
return Seal::None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let inturn = state.is_inturn(block.header.number(), &author);
|
||||||
|
|
||||||
|
let now = SystemTime::now();
|
||||||
|
|
||||||
|
let limit = match inturn {
|
||||||
|
true => state.next_timestamp_inturn.unwrap_or(now),
|
||||||
|
false => state.next_timestamp_noturn.unwrap_or(now),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Wait for the right moment.
|
||||||
|
if now < limit {
|
||||||
|
trace!(target: "engine",
|
||||||
|
"generate_seal: sleeping to sign: inturn: {}, now: {:?}, to: {:?}.",
|
||||||
|
inturn, now, limit);
|
||||||
|
match limit.duration_since(SystemTime::now()) {
|
||||||
|
Ok(duration) => {
|
||||||
|
thread::sleep(duration);
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
warn!(target:"engine", "generate_seal: unable to sleep, err: {}", e);
|
||||||
|
return Seal::None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trace!(target: "engine", "generate_seal: seal ready for block {}, txs: {}.",
|
||||||
|
block.header.number(), block.transactions.len());
|
||||||
|
return Seal::Regular(null_seal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Seal::None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verify_local_seal(&self, _header: &Header) -> Result<(), Error> { Ok(()) }
|
||||||
|
|
||||||
|
fn verify_block_basic(&self, header: &Header) -> Result<(), Error> {
|
||||||
|
// Largely same as https://github.com/ethereum/go-ethereum/blob/master/consensus/clique/clique.go#L275
|
||||||
|
|
||||||
|
// Ignore genesis block.
|
||||||
|
if header.number() == 0 {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't waste time checking blocks from the future
|
||||||
|
{
|
||||||
|
let limit = SystemTime::now().checked_add(Duration::from_secs(self.period))
|
||||||
|
.ok_or(BlockError::TimestampOverflow)?;
|
||||||
|
|
||||||
|
// This should succeed under the contraints that the system clock works
|
||||||
|
let limit_as_dur = limit.duration_since(UNIX_EPOCH).map_err(|e| {
|
||||||
|
Box::new(format!("Converting SystemTime to Duration failed: {}", e))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let hdr = Duration::from_secs(header.timestamp());
|
||||||
|
if hdr > limit_as_dur {
|
||||||
|
let found = UNIX_EPOCH.checked_add(hdr).ok_or(BlockError::TimestampOverflow)?;
|
||||||
|
|
||||||
|
Err(BlockError::TemporarilyInvalid(OutOfBounds {
|
||||||
|
min: None,
|
||||||
|
max: Some(limit),
|
||||||
|
found,
|
||||||
|
}))?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let is_checkpoint = header.number() % self.epoch_length == 0;
|
||||||
|
|
||||||
|
if is_checkpoint && *header.author() != NULL_AUTHOR {
|
||||||
|
return Err(EngineError::CliqueWrongAuthorCheckpoint(Mismatch {
|
||||||
|
expected: 0.into(),
|
||||||
|
found: *header.author(),
|
||||||
|
}))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let seal_fields = header.decode_seal::<Vec<_>>()?;
|
||||||
|
if seal_fields.len() != 2 {
|
||||||
|
Err(BlockError::InvalidSealArity(Mismatch {
|
||||||
|
expected: 2,
|
||||||
|
found: seal_fields.len(),
|
||||||
|
}))?
|
||||||
|
}
|
||||||
|
|
||||||
|
let mixhash: H256 = seal_fields[0].into();
|
||||||
|
let nonce: H64 = seal_fields[1].into();
|
||||||
|
|
||||||
|
// Nonce must be 0x00..0 or 0xff..f
|
||||||
|
if nonce != NONCE_DROP_VOTE && nonce != NONCE_AUTH_VOTE {
|
||||||
|
Err(EngineError::CliqueInvalidNonce(nonce))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_checkpoint && nonce != NULL_NONCE {
|
||||||
|
Err(EngineError::CliqueInvalidNonce(nonce))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that the mix digest is zero as Clique don't have fork protection currently
|
||||||
|
if mixhash != NULL_MIXHASH {
|
||||||
|
Err(BlockError::MismatchedH256SealElement(Mismatch {
|
||||||
|
expected: NULL_MIXHASH,
|
||||||
|
found: mixhash,
|
||||||
|
}))?
|
||||||
|
}
|
||||||
|
|
||||||
|
let extra_data_len = header.extra_data().len();
|
||||||
|
|
||||||
|
if extra_data_len < VANITY_LENGTH {
|
||||||
|
Err(EngineError::CliqueMissingVanity)?
|
||||||
|
}
|
||||||
|
|
||||||
|
if extra_data_len < VANITY_LENGTH + SIGNATURE_LENGTH {
|
||||||
|
Err(EngineError::CliqueMissingSignature)?
|
||||||
|
}
|
||||||
|
|
||||||
|
let signers = extra_data_len - (VANITY_LENGTH + SIGNATURE_LENGTH);
|
||||||
|
|
||||||
|
// Checkpoint blocks must at least contain one signer
|
||||||
|
if is_checkpoint && signers == 0 {
|
||||||
|
Err(EngineError::CliqueCheckpointNoSigner)?
|
||||||
|
}
|
||||||
|
|
||||||
|
// Addresses must be be divisable by 20
|
||||||
|
if is_checkpoint && signers % ADDRESS_LENGTH != 0 {
|
||||||
|
Err(EngineError::CliqueCheckpointInvalidSigners(signers))?
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that the block doesn't contain any uncles which are meaningless in PoA
|
||||||
|
if *header.uncles_hash() != NULL_UNCLES_HASH {
|
||||||
|
Err(BlockError::InvalidUnclesHash(Mismatch {
|
||||||
|
expected: NULL_UNCLES_HASH,
|
||||||
|
found: *header.uncles_hash(),
|
||||||
|
}))?
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that the block's difficulty is meaningful (may not be correct at this point)
|
||||||
|
if *header.difficulty() != DIFF_INTURN && *header.difficulty() != DIFF_NOTURN {
|
||||||
|
Err(BlockError::DifficultyOutOfBounds(OutOfBounds {
|
||||||
|
min: Some(DIFF_NOTURN),
|
||||||
|
max: Some(DIFF_INTURN),
|
||||||
|
found: *header.difficulty(),
|
||||||
|
}))?
|
||||||
|
}
|
||||||
|
|
||||||
|
// All basic checks passed, continue to next phase
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verify_block_unordered(&self, _header: &Header) -> Result<(), Error> {
|
||||||
|
// Nothing to check here.
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Verify block family by looking up parent state (backfill if needed), then try to apply current header.
|
||||||
|
/// see https://github.com/ethereum/go-ethereum/blob/master/consensus/clique/clique.go#L338
|
||||||
|
fn verify_block_family(&self, header: &Header, parent: &Header) -> Result<(), Error> {
|
||||||
|
// Ignore genesis block.
|
||||||
|
if header.number() == 0 {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// parent sanity check
|
||||||
|
if parent.hash() != *header.parent_hash() || header.number() != parent.number() + 1 {
|
||||||
|
Err(BlockError::UnknownParent(parent.hash()))?
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that the block's timestamp isn't too close to it's parent
|
||||||
|
let limit = parent.timestamp().saturating_add(self.period);
|
||||||
|
if limit > header.timestamp() {
|
||||||
|
let max = UNIX_EPOCH.checked_add(Duration::from_secs(header.timestamp()));
|
||||||
|
let found = UNIX_EPOCH.checked_add(Duration::from_secs(limit))
|
||||||
|
.ok_or(BlockError::TimestampOverflow)?;
|
||||||
|
|
||||||
|
Err(BlockError::InvalidTimestamp(OutOfBounds {
|
||||||
|
min: None,
|
||||||
|
max,
|
||||||
|
found,
|
||||||
|
}))?
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve the parent state
|
||||||
|
let parent_state = self.state(&parent)?;
|
||||||
|
// Try to apply current state, apply() will further check signer and recent signer.
|
||||||
|
let mut new_state = parent_state.clone();
|
||||||
|
new_state.apply(header, header.number() % self.epoch_length == 0)?;
|
||||||
|
new_state.calc_next_timestamp(header.timestamp(), self.period)?;
|
||||||
|
self.block_state_by_hash.write().insert(header.hash(), new_state);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn genesis_epoch_data(&self, header: &Header, _call: &Call) -> Result<Vec<u8>, String> {
|
||||||
|
let mut state = self.new_checkpoint_state(header).expect("Unable to parse genesis data.");
|
||||||
|
state.calc_next_timestamp(header.timestamp(), self.period).map_err(|e| format!("{}", e))?;
|
||||||
|
self.block_state_by_hash.write().insert(header.hash(), state);
|
||||||
|
|
||||||
|
// no proof.
|
||||||
|
Ok(Vec::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Our task here is to set difficulty
|
||||||
|
fn populate_from_parent(&self, header: &mut Header, parent: &Header) {
|
||||||
|
// TODO(https://github.com/paritytech/parity-ethereum/issues/10410): this is a horrible hack,
|
||||||
|
// it is due to the fact that enact and miner both use OpenBlock::new() which will both call
|
||||||
|
// this function. more refactoring is definitely needed.
|
||||||
|
if header.extra_data().len() < VANITY_LENGTH + SIGNATURE_LENGTH {
|
||||||
|
trace!(target: "engine", "populate_from_parent in sealing");
|
||||||
|
|
||||||
|
// It's unclear how to prevent creating new blocks unless we are authorized, the best way (and geth does this too)
|
||||||
|
// it's just to ignore setting an correct difficulty here, we will check authorization in next step in generate_seal anyway.
|
||||||
|
if let Some(signer) = self.signer.read().as_ref() {
|
||||||
|
let state = match self.state(&parent) {
|
||||||
|
Err(e) => {
|
||||||
|
trace!(target: "engine", "populate_from_parent: Unable to find parent state: {}, ignored.", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Ok(state) => state,
|
||||||
|
};
|
||||||
|
|
||||||
|
if state.is_authorized(&signer.address()) {
|
||||||
|
if state.is_inturn(header.number(), &signer.address()) {
|
||||||
|
header.set_difficulty(DIFF_INTURN);
|
||||||
|
} else {
|
||||||
|
header.set_difficulty(DIFF_NOTURN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
trace!(target: "engine", "populate_from_parent: no signer registered");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_signer(&self, signer: Box<EngineSigner>) {
|
||||||
|
trace!(target: "engine", "set_signer: {}", signer.address());
|
||||||
|
*self.signer.write() = Some(signer);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn register_client(&self, client: Weak<EngineClient>) {
|
||||||
|
*self.client.write() = Some(client.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn step(&self) {
|
||||||
|
if self.signer.read().is_some() {
|
||||||
|
if let Some(ref weak) = *self.client.read() {
|
||||||
|
if let Some(c) = weak.upgrade() {
|
||||||
|
c.update_sealing();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stop(&mut self) {
|
||||||
|
if let Some(mut s) = self.step_service.as_mut() {
|
||||||
|
Arc::get_mut(&mut s).map(|x| x.stop());
|
||||||
|
} else {
|
||||||
|
warn!(target: "engine", "Stopping `CliqueStepService` failed requires mutable access");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clique timestamp is set to parent + period , or current time which ever is higher.
|
||||||
|
fn open_block_header_timestamp(&self, parent_timestamp: u64) -> u64 {
|
||||||
|
let now = time::SystemTime::now().duration_since(time::UNIX_EPOCH).unwrap_or_default();
|
||||||
|
cmp::max(now.as_secs() as u64, parent_timestamp.saturating_add(self.period))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_timestamp_valid(&self, header_timestamp: u64, parent_timestamp: u64) -> bool {
|
||||||
|
header_timestamp >= parent_timestamp.saturating_add(self.period)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fork_choice(&self, new: &ExtendedHeader, current: &ExtendedHeader) -> super::ForkChoice {
|
||||||
|
super::total_difficulty_fork_choice(new, current)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clique uses the author field for voting, the real author is hidden in the `extra_data` field.
|
||||||
|
// So when executing tx's (like in `enact()`) we want to use the executive author
|
||||||
|
fn executive_author(&self, header: &Header) -> Result<Address, Error> {
|
||||||
|
recover_creator(header)
|
||||||
|
}
|
||||||
|
}
|
41
ethcore/src/engines/clique/params.rs
Normal file
41
ethcore/src/engines/clique/params.rs
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity Ethereum.
|
||||||
|
|
||||||
|
// Parity Ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity Ethereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
//! Clique specific parameters.
|
||||||
|
|
||||||
|
use ethjson;
|
||||||
|
|
||||||
|
/// `Clique` params.
|
||||||
|
pub struct CliqueParams {
|
||||||
|
/// Period as defined in EIP
|
||||||
|
pub period: u64,
|
||||||
|
/// Epoch length as defined in EIP
|
||||||
|
pub epoch: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ethjson::spec::CliqueParams> for CliqueParams {
|
||||||
|
fn from(p: ethjson::spec::CliqueParams) -> Self {
|
||||||
|
let period = p.period.map_or_else(|| 30000 as u64, Into::into);
|
||||||
|
let epoch = p.epoch.map_or_else(|| 15 as u64, Into::into);
|
||||||
|
|
||||||
|
assert!(epoch > 0);
|
||||||
|
|
||||||
|
CliqueParams {
|
||||||
|
period,
|
||||||
|
epoch,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
77
ethcore/src/engines/clique/step_service.rs
Normal file
77
ethcore/src/engines/clique/step_service.rs
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity Ethereum.
|
||||||
|
|
||||||
|
// Parity Ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity Ethereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
use std::sync::Weak;
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
use std::time::Duration;
|
||||||
|
use std::thread;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use engines::Engine;
|
||||||
|
use machine::Machine;
|
||||||
|
|
||||||
|
/// Service that is managing the engine
|
||||||
|
pub struct StepService {
|
||||||
|
shutdown: Arc<AtomicBool>,
|
||||||
|
thread: Option<thread::JoinHandle<()>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StepService {
|
||||||
|
/// Start the `StepService`
|
||||||
|
pub fn start<M: Machine + 'static>(engine: Weak<Engine<M>>) -> Arc<Self> {
|
||||||
|
let shutdown = Arc::new(AtomicBool::new(false));
|
||||||
|
let s = shutdown.clone();
|
||||||
|
|
||||||
|
let thread = thread::Builder::new()
|
||||||
|
.name("CliqueStepService".into())
|
||||||
|
.spawn(move || {
|
||||||
|
// startup delay.
|
||||||
|
thread::sleep(Duration::from_secs(5));
|
||||||
|
|
||||||
|
loop {
|
||||||
|
// see if we are in shutdown.
|
||||||
|
if shutdown.load(Ordering::Acquire) {
|
||||||
|
trace!(target: "miner", "CliqueStepService: received shutdown signal!");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
trace!(target: "miner", "CliqueStepService: triggering sealing");
|
||||||
|
|
||||||
|
// Try sealing
|
||||||
|
engine.upgrade().map(|x| x.step());
|
||||||
|
|
||||||
|
// Yield
|
||||||
|
thread::sleep(Duration::from_millis(2000));
|
||||||
|
}
|
||||||
|
trace!(target: "miner", "CliqueStepService: shutdown.");
|
||||||
|
}).expect("CliqueStepService thread failed");
|
||||||
|
|
||||||
|
Arc::new(StepService {
|
||||||
|
shutdown: s,
|
||||||
|
thread: Some(thread),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stop the `StepService`
|
||||||
|
pub fn stop(&mut self) {
|
||||||
|
trace!(target: "miner", "CliqueStepService: shutting down.");
|
||||||
|
self.shutdown.store(true, Ordering::Release);
|
||||||
|
if let Some(t) = self.thread.take() {
|
||||||
|
t.join().expect("CliqueStepService thread panicked!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
804
ethcore/src/engines/clique/tests.rs
Normal file
804
ethcore/src/engines/clique/tests.rs
Normal file
@ -0,0 +1,804 @@
|
|||||||
|
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity Ethereum.
|
||||||
|
|
||||||
|
// Parity Ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity Ethereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
//! Consensus tests for `PoA Clique Engine`, see http://eips.ethereum.org/EIPS/eip-225 for more information
|
||||||
|
|
||||||
|
use block::*;
|
||||||
|
use engines::Engine;
|
||||||
|
use error::{Error, ErrorKind};
|
||||||
|
use ethereum_types::{Address, H256};
|
||||||
|
use ethkey::{Secret, KeyPair};
|
||||||
|
use state_db::StateDB;
|
||||||
|
use super::*;
|
||||||
|
use test_helpers::get_temp_state_db;
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
/// Possible signers
|
||||||
|
pub const SIGNER_TAGS: [char; 6] = ['A', 'B', 'C', 'D', 'E', 'F'];
|
||||||
|
|
||||||
|
/// Clique block types
|
||||||
|
pub enum CliqueBlockType {
|
||||||
|
/// Epoch transition block must contain list of signers
|
||||||
|
Checkpoint,
|
||||||
|
/// Block with no votes
|
||||||
|
Empty,
|
||||||
|
/// Vote
|
||||||
|
Vote(VoteType),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clique tester
|
||||||
|
pub struct CliqueTester {
|
||||||
|
/// Mocked Clique
|
||||||
|
pub clique: Clique,
|
||||||
|
/// Mocked genesis state
|
||||||
|
pub genesis: Header,
|
||||||
|
/// StateDB
|
||||||
|
pub db: StateDB,
|
||||||
|
/// List of signers
|
||||||
|
pub signers: HashMap<char, KeyPair>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CliqueTester {
|
||||||
|
/// Create a `Clique` tester with settings
|
||||||
|
pub fn with(epoch: u64, period: u64, initial_signers: Vec<char>) -> Self {
|
||||||
|
assert_eq!(initial_signers.iter().all(|s| SIGNER_TAGS.contains(s)), true,
|
||||||
|
"Not all the initial signers is in SIGNER_TAGS, possible keys are 'A' ..= 'F'");
|
||||||
|
|
||||||
|
let clique = Clique::with_test(epoch, period);
|
||||||
|
let mut genesis = Header::default();
|
||||||
|
let mut signers = HashMap::new();
|
||||||
|
|
||||||
|
let call = |_a, _b| {
|
||||||
|
unimplemented!("Clique doesn't use Engine::Call");
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut extra_data = vec![0; VANITY_LENGTH];
|
||||||
|
|
||||||
|
for &signer in SIGNER_TAGS.iter() {
|
||||||
|
let secret = Secret::from(H256::from(signer as u64));
|
||||||
|
let keypair = KeyPair::from_secret(secret).unwrap();
|
||||||
|
if initial_signers.contains(&signer) {
|
||||||
|
extra_data.extend(&*keypair.address());
|
||||||
|
}
|
||||||
|
signers.insert(signer, keypair);
|
||||||
|
}
|
||||||
|
|
||||||
|
// append dummy signature
|
||||||
|
extra_data.extend(std::iter::repeat(0).take(SIGNATURE_LENGTH));
|
||||||
|
|
||||||
|
genesis.set_extra_data(extra_data);
|
||||||
|
genesis.set_gas_limit(U256::from(0xa00000));
|
||||||
|
genesis.set_difficulty(U256::from(1));
|
||||||
|
genesis.set_seal(util::null_seal());
|
||||||
|
|
||||||
|
clique.genesis_epoch_data(&genesis, &call).expect("Create genesis failed");
|
||||||
|
Self {clique, genesis, db: get_temp_state_db(), signers}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get difficulty for a given block
|
||||||
|
pub fn get_difficulty(&self, block_num: BlockNumber, header: &Header, signer: &Address) -> U256 {
|
||||||
|
let state = self.clique.state(header).unwrap();
|
||||||
|
if state.is_inturn(block_num, signer) {
|
||||||
|
DIFF_INTURN
|
||||||
|
} else {
|
||||||
|
DIFF_NOTURN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the state of a given block
|
||||||
|
// Note, this will read the cache and `will` not work with more than 128 blocks
|
||||||
|
pub fn get_state_at_block(&self, hash: &H256) -> CliqueBlockState {
|
||||||
|
self.clique.block_state_by_hash.write()
|
||||||
|
.get_mut(hash)
|
||||||
|
.expect("CliqueBlockState not found tested failed")
|
||||||
|
.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get signers after a certain state
|
||||||
|
// This is generally used to fetch the state after a test has been executed and checked against
|
||||||
|
// the intial list of signers provided in the test
|
||||||
|
pub fn clique_signers(&self, hash: &H256) -> impl Iterator<Item = Address> {
|
||||||
|
self.get_state_at_block(hash).signers().clone().into_iter()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fetches all addresses at current `block` and converts them back to `tags (char)` and sorts them
|
||||||
|
/// Addresses are supposed sorted based on address but these tests are using `tags` just for simplicity
|
||||||
|
/// and the order is not important!
|
||||||
|
pub fn into_tags<T: Iterator<Item = Address>>(&self, addr: T) -> Vec<char> {
|
||||||
|
let mut tags: Vec<char> = addr.filter_map(|addr| {
|
||||||
|
for (t, kp) in self.signers.iter() {
|
||||||
|
if addr == kp.address() {
|
||||||
|
return Some(*t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
tags.sort();
|
||||||
|
tags
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new `Clique` block and import
|
||||||
|
pub fn new_block_and_import(
|
||||||
|
&self,
|
||||||
|
block_type: CliqueBlockType,
|
||||||
|
last_header: &Header,
|
||||||
|
beneficary: Option<Address>,
|
||||||
|
signer: char,
|
||||||
|
) -> Result<Header, Error> {
|
||||||
|
|
||||||
|
let mut extra_data = vec![0; VANITY_LENGTH];
|
||||||
|
let mut seal = util::null_seal();
|
||||||
|
let last_hash = last_header.hash();
|
||||||
|
|
||||||
|
match block_type {
|
||||||
|
CliqueBlockType::Checkpoint => {
|
||||||
|
let signers = self.clique.state(&last_header).unwrap().signers().clone();
|
||||||
|
for signer in signers {
|
||||||
|
extra_data.extend(&*signer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CliqueBlockType::Vote(v) => seal = v.as_rlp(),
|
||||||
|
CliqueBlockType::Empty => (),
|
||||||
|
};
|
||||||
|
|
||||||
|
let db = self.db.boxed_clone();
|
||||||
|
|
||||||
|
let mut block = OpenBlock::new(
|
||||||
|
&self.clique,
|
||||||
|
Default::default(),
|
||||||
|
false,
|
||||||
|
db,
|
||||||
|
&last_header.clone(),
|
||||||
|
Arc::new(vec![last_hash]),
|
||||||
|
beneficary.unwrap_or_default(),
|
||||||
|
(3141562.into(), 31415620.into()),
|
||||||
|
extra_data,
|
||||||
|
false,
|
||||||
|
None,
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
|
{
|
||||||
|
let difficulty = self.get_difficulty(block.header.number(), last_header, &self.signers[&signer].address());
|
||||||
|
let b = block.block_mut();
|
||||||
|
b.header.set_timestamp(last_header.timestamp() + self.clique.period);
|
||||||
|
b.header.set_difficulty(difficulty);
|
||||||
|
b.header.set_seal(seal);
|
||||||
|
|
||||||
|
let sign = ethkey::sign(self.signers[&signer].secret(), &b.header.hash()).unwrap();
|
||||||
|
let mut extra_data = b.header.extra_data().clone();
|
||||||
|
extra_data.extend_from_slice(&*sign);
|
||||||
|
b.header.set_extra_data(extra_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
let current_header = &block.header;
|
||||||
|
self.clique.verify_block_basic(current_header)?;
|
||||||
|
self.clique.verify_block_family(current_header, &last_header)?;
|
||||||
|
|
||||||
|
Ok(current_header.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn one_signer_with_no_votes() {
|
||||||
|
let tester = CliqueTester::with(10, 1, vec!['A']);
|
||||||
|
|
||||||
|
let empty_block = tester.new_block_and_import(CliqueBlockType::Empty, &tester.genesis, None, 'A').unwrap();
|
||||||
|
|
||||||
|
let tags = tester.into_tags(tester.clique_signers(&empty_block.hash()));
|
||||||
|
assert_eq!(&tags, &['A']);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn one_signer_two_votes() {
|
||||||
|
let tester = CliqueTester::with(10, 1, vec!['A']);
|
||||||
|
|
||||||
|
// Add a vote for `B` signed by `A`
|
||||||
|
let vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &tester.genesis,
|
||||||
|
Some(tester.signers[&'B'].address()), 'A').unwrap();
|
||||||
|
let tags = tester.into_tags(tester.clique_signers(&vote.hash()));
|
||||||
|
assert_eq!(&tags, &['A', 'B']);
|
||||||
|
|
||||||
|
// Add a empty block signed by `B`
|
||||||
|
let empty = tester.new_block_and_import(CliqueBlockType::Empty, &vote, None, 'B').unwrap();
|
||||||
|
|
||||||
|
// Add vote for `C` signed by A but should not be accepted
|
||||||
|
let vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &empty,
|
||||||
|
Some(tester.signers[&'C'].address()), 'A').unwrap();
|
||||||
|
|
||||||
|
let tags = tester.into_tags(tester.clique_signers(&vote.hash()));
|
||||||
|
assert_eq!(&tags, &['A', 'B']);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn two_signers_six_votes_deny_last() {
|
||||||
|
let tester = CliqueTester::with(10, 1, vec!['A', 'B']);
|
||||||
|
|
||||||
|
let mut prev_header = tester.genesis.clone();
|
||||||
|
|
||||||
|
// Add two votes for `C` signed by `A` and `B`
|
||||||
|
for &signer in SIGNER_TAGS.iter().take(2) {
|
||||||
|
let vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &prev_header,
|
||||||
|
Some(tester.signers[&'C'].address()), signer).unwrap();
|
||||||
|
prev_header = vote.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add two votes for `D` signed by `A` and `B`
|
||||||
|
for &signer in SIGNER_TAGS.iter().take(2) {
|
||||||
|
let vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &prev_header,
|
||||||
|
Some(tester.signers[&'D'].address()), signer).unwrap();
|
||||||
|
prev_header = vote.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a empty block signed by `C`
|
||||||
|
let empty = tester.new_block_and_import(CliqueBlockType::Empty, &prev_header, None, 'C').unwrap();
|
||||||
|
prev_header = empty.clone();
|
||||||
|
|
||||||
|
// Add two votes for `E` signed by `A` and `B`
|
||||||
|
for &signer in SIGNER_TAGS.iter().take(2) {
|
||||||
|
let vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &prev_header,
|
||||||
|
Some(tester.signers[&'E'].address()), signer).unwrap();
|
||||||
|
prev_header = vote.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
let tags = tester.into_tags(tester.clique_signers(&prev_header.hash()));
|
||||||
|
assert_eq!(&tags, &['A', 'B', 'C', 'D']);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn one_signer_dropping_itself() {
|
||||||
|
let tester = CliqueTester::with(10, 1, vec!['A']);
|
||||||
|
let vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &tester.genesis,
|
||||||
|
Some(tester.signers[&'A'].address()), 'A').unwrap();
|
||||||
|
let signers = tester.clique_signers(&vote.hash());
|
||||||
|
assert!(signers.count() == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn two_signers_one_remove_vote_no_consensus() {
|
||||||
|
let tester = CliqueTester::with(10, 1, vec!['A', 'B']);
|
||||||
|
let vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &tester.genesis,
|
||||||
|
Some(tester.signers[&'B'].address()), 'A').unwrap();
|
||||||
|
|
||||||
|
let tags = tester.into_tags(tester.clique_signers(&vote.hash()));
|
||||||
|
assert_eq!(&tags, &['A', 'B']);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn two_signers_consensus_remove_b() {
|
||||||
|
let tester = CliqueTester::with(10, 1, vec!['A', 'B']);
|
||||||
|
let first_vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &tester.genesis,
|
||||||
|
Some(tester.signers[&'B'].address()), 'A').unwrap();
|
||||||
|
let second_vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &first_vote,
|
||||||
|
Some(tester.signers[&'B'].address()), 'B').unwrap();
|
||||||
|
|
||||||
|
let tags = tester.into_tags(tester.clique_signers(&second_vote.hash()));
|
||||||
|
assert_eq!(&tags, &['A']);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn three_signers_consensus_remove_c() {
|
||||||
|
let tester = CliqueTester::with(10, 1, vec!['A', 'B', 'C']);
|
||||||
|
let first_vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &tester.genesis,
|
||||||
|
Some(tester.signers[&'C'].address()), 'A').unwrap();
|
||||||
|
let second_vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &first_vote,
|
||||||
|
Some(tester.signers[&'C'].address()), 'B').unwrap();
|
||||||
|
|
||||||
|
let tags = tester.into_tags(tester.clique_signers(&second_vote.hash()));
|
||||||
|
assert_eq!(&tags, &['A', 'B']);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn four_signers_half_no_consensus() {
|
||||||
|
let tester = CliqueTester::with(10, 1, vec!['A', 'B', 'C', 'D']);
|
||||||
|
let first_vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &tester.genesis,
|
||||||
|
Some(tester.signers[&'C'].address()), 'A').unwrap();
|
||||||
|
|
||||||
|
let second_vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &first_vote,
|
||||||
|
Some(tester.signers[&'C'].address()), 'B').unwrap();
|
||||||
|
|
||||||
|
let tags = tester.into_tags(tester.clique_signers(&second_vote.hash()));
|
||||||
|
assert_eq!(&tags, &['A', 'B', 'C', 'D']);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn four_signers_three_consensus_rm() {
|
||||||
|
let tester = CliqueTester::with(10, 1, vec!['A', 'B', 'C', 'D']);
|
||||||
|
|
||||||
|
let mut prev_header = tester.genesis.clone();
|
||||||
|
|
||||||
|
// Three votes to remove `D` signed by ['A', 'B', 'C']
|
||||||
|
for signer in SIGNER_TAGS.iter().take(3) {
|
||||||
|
let vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &prev_header,
|
||||||
|
Some(tester.signers[&'D'].address()), *signer).unwrap();
|
||||||
|
prev_header = vote.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
let tags = tester.into_tags(tester.clique_signers(&prev_header.hash()));
|
||||||
|
assert_eq!(&tags, &['A', 'B', 'C']);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn vote_add_only_counted_once_per_signer() {
|
||||||
|
let tester = CliqueTester::with(10, 1, vec!['A', 'B']);
|
||||||
|
|
||||||
|
// Add a vote for `C` signed by `A`
|
||||||
|
let vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &tester.genesis,
|
||||||
|
Some(tester.signers[&'C'].address()), 'A').unwrap();
|
||||||
|
// Empty block signed by B`
|
||||||
|
let empty = tester.new_block_and_import(CliqueBlockType::Empty, &vote, None, 'B').unwrap();
|
||||||
|
|
||||||
|
// Add a vote for `C` signed by `A`
|
||||||
|
let vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &empty,
|
||||||
|
Some(tester.signers[&'C'].address()), 'A').unwrap();
|
||||||
|
// Empty block signed by `B`
|
||||||
|
let empty = tester.new_block_and_import(CliqueBlockType::Empty, &vote, None, 'B').unwrap();
|
||||||
|
|
||||||
|
// Add a vote for `C` signed by `A`
|
||||||
|
let vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &empty,
|
||||||
|
Some(tester.signers[&'C'].address()), 'A').unwrap();
|
||||||
|
|
||||||
|
let tags = tester.into_tags(tester.clique_signers(&vote.hash()));
|
||||||
|
assert_eq!(&tags, &['A', 'B']);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn vote_add_concurrently_is_permitted() {
|
||||||
|
let tester = CliqueTester::with(10, 1, vec!['A', 'B']);
|
||||||
|
|
||||||
|
// Add a vote for `C` signed by `A`
|
||||||
|
let b = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &tester.genesis,
|
||||||
|
Some(tester.signers[&'C'].address()), 'A').unwrap();
|
||||||
|
|
||||||
|
// Empty block signed by `B`
|
||||||
|
let b = tester.new_block_and_import(CliqueBlockType::Empty, &b, None, 'B').unwrap();
|
||||||
|
|
||||||
|
// Add a vote for `D` signed by `A`
|
||||||
|
let b = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &b,
|
||||||
|
Some(tester.signers[&'D'].address()), 'A').unwrap();
|
||||||
|
|
||||||
|
// Empty block signed by `B`
|
||||||
|
let b = tester.new_block_and_import(CliqueBlockType::Empty, &b, None, 'B').unwrap();
|
||||||
|
|
||||||
|
// Empty block signed by `A`
|
||||||
|
let b = tester.new_block_and_import(CliqueBlockType::Empty, &b, None, 'A').unwrap();
|
||||||
|
|
||||||
|
// Add a vote for `D` signed by `B`
|
||||||
|
let b = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &b,
|
||||||
|
Some(tester.signers[&'D'].address()), 'B').unwrap();
|
||||||
|
|
||||||
|
// Empty block signed by `A`
|
||||||
|
let b = tester.new_block_and_import(CliqueBlockType::Empty, &b, None, 'A').unwrap();
|
||||||
|
|
||||||
|
// Add a vote for `C` signed by `B`
|
||||||
|
let b = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &b,
|
||||||
|
Some(tester.signers[&'C'].address()), 'B').unwrap();
|
||||||
|
|
||||||
|
let tags = tester.into_tags(tester.clique_signers(&b.hash()));
|
||||||
|
assert_eq!(&tags, &['A', 'B', 'C', 'D']);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn vote_rm_only_counted_once_per_signer() {
|
||||||
|
let tester = CliqueTester::with(10, 1, vec!['A', 'B']);
|
||||||
|
|
||||||
|
let mut prev_header = tester.genesis.clone();
|
||||||
|
|
||||||
|
for _ in 0..2 {
|
||||||
|
// Vote to remove `B` signed by `A`
|
||||||
|
let b = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &prev_header,
|
||||||
|
Some(tester.signers[&'B'].address()), 'A').unwrap();
|
||||||
|
// Empty block signed by `B`
|
||||||
|
let b = tester.new_block_and_import(CliqueBlockType::Empty, &b, None, 'B').unwrap();
|
||||||
|
|
||||||
|
prev_header = b.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a vote for `B` signed by `A`
|
||||||
|
let b = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &prev_header,
|
||||||
|
Some(tester.signers[&'B'].address()), 'A').unwrap();
|
||||||
|
|
||||||
|
let tags = tester.into_tags(tester.clique_signers(&b.hash()));
|
||||||
|
assert_eq!(&tags, &['A', 'B']);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn vote_rm_concurrently_is_permitted() {
|
||||||
|
let tester = CliqueTester::with(100, 1, vec!['A', 'B', 'C', 'D']);
|
||||||
|
|
||||||
|
// Add a vote for `C` signed by `A`
|
||||||
|
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &tester.genesis,
|
||||||
|
Some(tester.signers[&'C'].address()), 'A').unwrap();
|
||||||
|
|
||||||
|
// Empty block signed by `B`
|
||||||
|
let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'B').unwrap();
|
||||||
|
// Empty block signed by `C`
|
||||||
|
let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'C').unwrap();
|
||||||
|
|
||||||
|
// Add a vote for `D` signed by `A`
|
||||||
|
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &block,
|
||||||
|
Some(tester.signers[&'D'].address()), 'A').unwrap();
|
||||||
|
|
||||||
|
// Empty block signed by `B`
|
||||||
|
let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'B').unwrap();
|
||||||
|
// Empty block signed by `C`
|
||||||
|
let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'C').unwrap();
|
||||||
|
// Empty block signed by `A`
|
||||||
|
let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'A').unwrap();
|
||||||
|
|
||||||
|
// Add a vote for `D` signed by `B`
|
||||||
|
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &block,
|
||||||
|
Some(tester.signers[&'D'].address()), 'B').unwrap();
|
||||||
|
// Add a vote for `D` signed by `C`
|
||||||
|
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &block,
|
||||||
|
Some(tester.signers[&'D'].address()), 'C').unwrap();
|
||||||
|
|
||||||
|
// Empty block signed by `A`
|
||||||
|
let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'A').unwrap();
|
||||||
|
// Add a vote for `C` signed by `B`
|
||||||
|
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &block,
|
||||||
|
Some(tester.signers[&'C'].address()), 'B').unwrap();
|
||||||
|
|
||||||
|
let tags = tester.into_tags(tester.clique_signers(&block.hash()));
|
||||||
|
assert_eq!(&tags, &['A', 'B']);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn vote_to_rm_are_immediate_and_ensure_votes_are_rm() {
|
||||||
|
let tester = CliqueTester::with(100, 1, vec!['A', 'B', 'C']);
|
||||||
|
|
||||||
|
// Vote to remove `B` signed by `C`
|
||||||
|
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &tester.genesis,
|
||||||
|
Some(tester.signers[&'B'].address()), 'C').unwrap();
|
||||||
|
// Vote to remove `C` signed by `A`
|
||||||
|
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &block,
|
||||||
|
Some(tester.signers[&'C'].address()), 'A').unwrap();
|
||||||
|
// Vote to remove `C` signed by `B`
|
||||||
|
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &block,
|
||||||
|
Some(tester.signers[&'C'].address()), 'B').unwrap();
|
||||||
|
// Vote to remove `B` signed by `A`
|
||||||
|
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &block,
|
||||||
|
Some(tester.signers[&'B'].address()), 'A').unwrap();
|
||||||
|
|
||||||
|
let tags = tester.into_tags(tester.clique_signers(&block.hash()));
|
||||||
|
assert_eq!(&tags, &['A', 'B']);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn vote_to_rm_are_immediate_and_votes_should_be_dropped_from_kicked_signer() {
|
||||||
|
let tester = CliqueTester::with(100, 1, vec!['A', 'B', 'C']);
|
||||||
|
|
||||||
|
// Vote to add `D` signed by `C`
|
||||||
|
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &tester.genesis,
|
||||||
|
Some(tester.signers[&'D'].address()), 'C').unwrap();
|
||||||
|
// Vote to remove `C` signed by `A`
|
||||||
|
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &block,
|
||||||
|
Some(tester.signers[&'C'].address()), 'A').unwrap();
|
||||||
|
|
||||||
|
// Vote to remove `C` signed by `B`
|
||||||
|
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &block,
|
||||||
|
Some(tester.signers[&'C'].address()), 'B').unwrap();
|
||||||
|
|
||||||
|
// Vote to add `D` signed by `A`
|
||||||
|
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &block,
|
||||||
|
Some(tester.signers[&'D'].address()), 'A').unwrap();
|
||||||
|
|
||||||
|
let tags = tester.into_tags(tester.clique_signers(&block.hash()));
|
||||||
|
assert_eq!(&tags, &['A', 'B']);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cascading_not_allowed() {
|
||||||
|
let tester = CliqueTester::with(100, 1, vec!['A', 'B', 'C', 'D']);
|
||||||
|
|
||||||
|
// Vote against `C` signed by `A`
|
||||||
|
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &tester.genesis,
|
||||||
|
Some(tester.signers[&'C'].address()), 'A').unwrap();
|
||||||
|
|
||||||
|
// Empty block signed by `B`
|
||||||
|
let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'B').unwrap();
|
||||||
|
|
||||||
|
// Empty block signed by `C`
|
||||||
|
let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'C').unwrap();
|
||||||
|
|
||||||
|
// Vote against `D` signed by `A`
|
||||||
|
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &block,
|
||||||
|
Some(tester.signers[&'D'].address()), 'A').unwrap();
|
||||||
|
|
||||||
|
// Vote against `C` signed by `B`
|
||||||
|
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &block,
|
||||||
|
Some(tester.signers[&'C'].address()), 'B').unwrap();
|
||||||
|
|
||||||
|
// Empty block signed by `C`
|
||||||
|
let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'C').unwrap();
|
||||||
|
|
||||||
|
// Empty block signed by `A`
|
||||||
|
let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'A').unwrap();
|
||||||
|
|
||||||
|
// Vote against `D` signed by `B`
|
||||||
|
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &block,
|
||||||
|
Some(tester.signers[&'D'].address()), 'B').unwrap();
|
||||||
|
|
||||||
|
// Vote against `D` signed by `C`
|
||||||
|
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &block,
|
||||||
|
Some(tester.signers[&'D'].address()), 'C').unwrap();
|
||||||
|
|
||||||
|
let tags = tester.into_tags(tester.clique_signers(&block.hash()));
|
||||||
|
assert_eq!(&tags, &['A', 'B', 'C']);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn consensus_out_of_bounds_consensus_execute_on_touch() {
|
||||||
|
let tester = CliqueTester::with(100, 1, vec!['A', 'B', 'C', 'D']);
|
||||||
|
|
||||||
|
// Vote against `C` signed by `A`
|
||||||
|
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &tester.genesis,
|
||||||
|
Some(tester.signers[&'C'].address()), 'A').unwrap();
|
||||||
|
|
||||||
|
// Empty block signed by `B`
|
||||||
|
let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'B').unwrap();
|
||||||
|
|
||||||
|
// Empty block signed by `C`
|
||||||
|
let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'C').unwrap();
|
||||||
|
|
||||||
|
// Vote against `D` signed by `A`
|
||||||
|
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &block,
|
||||||
|
Some(tester.signers[&'D'].address()), 'A').unwrap();
|
||||||
|
|
||||||
|
// Vote against `C` signed by `B`
|
||||||
|
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &block,
|
||||||
|
Some(tester.signers[&'C'].address()), 'B').unwrap();
|
||||||
|
|
||||||
|
// Empty block signed by `C`
|
||||||
|
let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'C').unwrap();
|
||||||
|
|
||||||
|
// Empty block signed by `A`
|
||||||
|
let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'A').unwrap();
|
||||||
|
|
||||||
|
// Vote against `D` signed by `B`
|
||||||
|
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &block,
|
||||||
|
Some(tester.signers[&'D'].address()), 'B').unwrap();
|
||||||
|
|
||||||
|
// Vote against `D` signed by `C`
|
||||||
|
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &block,
|
||||||
|
Some(tester.signers[&'D'].address()), 'C').unwrap();
|
||||||
|
|
||||||
|
let tags = tester.into_tags(tester.clique_signers(&block.hash()));
|
||||||
|
assert_eq!(&tags, &['A', 'B', 'C'], "D should have been removed after 3/4 remove votes");
|
||||||
|
|
||||||
|
// Empty block signed by `A`
|
||||||
|
let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'A').unwrap();
|
||||||
|
|
||||||
|
// Vote for `C` signed by `C`
|
||||||
|
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &block,
|
||||||
|
Some(tester.signers[&'C'].address()), 'C').unwrap();
|
||||||
|
|
||||||
|
let tags = tester.into_tags(tester.clique_signers(&block.hash()));
|
||||||
|
assert_eq!(&tags, &['A', 'B']);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn consensus_out_of_bounds_first_touch() {
|
||||||
|
let tester = CliqueTester::with(100, 1, vec!['A', 'B', 'C', 'D']);
|
||||||
|
|
||||||
|
// Vote against `C` signed by `A`
|
||||||
|
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &tester.genesis,
|
||||||
|
Some(tester.signers[&'C'].address()), 'A').unwrap();
|
||||||
|
|
||||||
|
// Empty block signed by `B`
|
||||||
|
let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'B').unwrap();
|
||||||
|
|
||||||
|
// Empty block signed by `C`
|
||||||
|
let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'C').unwrap();
|
||||||
|
|
||||||
|
// Vote against `D` signed by `A`
|
||||||
|
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &block,
|
||||||
|
Some(tester.signers[&'D'].address()), 'A').unwrap();
|
||||||
|
|
||||||
|
// Vote against `C` signed by `B`
|
||||||
|
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &block,
|
||||||
|
Some(tester.signers[&'C'].address()), 'B').unwrap();
|
||||||
|
|
||||||
|
// Empty block signed by `C`
|
||||||
|
let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'C').unwrap();
|
||||||
|
|
||||||
|
// Empty block signed by `A`
|
||||||
|
let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'A').unwrap();
|
||||||
|
|
||||||
|
// Vote against `D` signed by `B`
|
||||||
|
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &block,
|
||||||
|
Some(tester.signers[&'D'].address()), 'B').unwrap();
|
||||||
|
|
||||||
|
// Vote against `D` signed by `C`
|
||||||
|
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &block,
|
||||||
|
Some(tester.signers[&'D'].address()), 'C').unwrap();
|
||||||
|
|
||||||
|
let tags = tester.into_tags(tester.clique_signers(&block.hash()));
|
||||||
|
assert_eq!(&tags, &['A', 'B', 'C']);
|
||||||
|
|
||||||
|
// Empty block signed by `A`
|
||||||
|
let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'A').unwrap();
|
||||||
|
|
||||||
|
// Vote for `C` signed by `B`
|
||||||
|
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &block,
|
||||||
|
Some(tester.signers[&'C'].address()), 'B').unwrap();
|
||||||
|
|
||||||
|
let tags = tester.into_tags(tester.clique_signers(&block.hash()));
|
||||||
|
assert_eq!(&tags, &['A', 'B', 'C']);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pending_votes_doesnt_survive_authorization_changes() {
|
||||||
|
let tester = CliqueTester::with(100, 1, vec!['A', 'B', 'C', 'D', 'E']);
|
||||||
|
|
||||||
|
let mut prev_header = tester.genesis.clone();
|
||||||
|
|
||||||
|
// Vote for `F` from [`A`, `B`, `C`]
|
||||||
|
for sign in SIGNER_TAGS.iter().take(3) {
|
||||||
|
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &prev_header,
|
||||||
|
Some(tester.signers[&'F'].address()), *sign).unwrap();
|
||||||
|
prev_header = block.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
let tags = tester.into_tags(tester.clique_signers(&prev_header.hash()));
|
||||||
|
assert_eq!(&tags, &['A', 'B', 'C', 'D', 'E', 'F'], "F should have been added");
|
||||||
|
|
||||||
|
// Vote against `F` from [`D`, `E`, `B`, `C`]
|
||||||
|
for sign in SIGNER_TAGS.iter().skip(3).chain(SIGNER_TAGS.iter().skip(1).take(2)) {
|
||||||
|
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &prev_header,
|
||||||
|
Some(tester.signers[&'F'].address()), *sign).unwrap();
|
||||||
|
prev_header = block.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
let tags = tester.into_tags(tester.clique_signers(&prev_header.hash()));
|
||||||
|
assert_eq!(&tags, &['A', 'B', 'C', 'D', 'E'], "F should have been removed");
|
||||||
|
|
||||||
|
// Vote for `F` from [`D`, `E`]
|
||||||
|
for sign in SIGNER_TAGS.iter().skip(3).take(2) {
|
||||||
|
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &prev_header,
|
||||||
|
Some(tester.signers[&'F'].address()), *sign).unwrap();
|
||||||
|
prev_header = block.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vote against `A` from [`B`, `C`, `D`]
|
||||||
|
for sign in SIGNER_TAGS.iter().skip(1).take(3) {
|
||||||
|
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &prev_header,
|
||||||
|
Some(tester.signers[&'A'].address()), *sign).unwrap();
|
||||||
|
prev_header = block.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
let tags = tester.into_tags(tester.clique_signers(&prev_header.hash()));
|
||||||
|
assert_eq!(&tags, &['B', 'C', 'D', 'E'], "A should have been removed");
|
||||||
|
|
||||||
|
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &prev_header,
|
||||||
|
Some(tester.signers[&'F'].address()), 'B').unwrap();
|
||||||
|
|
||||||
|
let tags = tester.into_tags(tester.clique_signers(&block.hash()));
|
||||||
|
assert_eq!(&tags, &['B', 'C', 'D', 'E', 'F'], "F should have been added again");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn epoch_transition_reset_all_votes() {
|
||||||
|
let tester = CliqueTester::with(3, 1, vec!['A', 'B']);
|
||||||
|
|
||||||
|
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &tester.genesis,
|
||||||
|
Some(tester.signers[&'C'].address()), 'A').unwrap();
|
||||||
|
|
||||||
|
let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'B').unwrap();
|
||||||
|
let block = tester.new_block_and_import(CliqueBlockType::Checkpoint, &block, None, 'A').unwrap();
|
||||||
|
|
||||||
|
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &block,
|
||||||
|
Some(tester.signers[&'C'].address()), 'B').unwrap();
|
||||||
|
|
||||||
|
let tags = tester.into_tags(tester.clique_signers(&block.hash()));
|
||||||
|
assert_eq!(&tags, &['A', 'B'], "Votes should have been reset after checkpoint");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unauthorized_signer_should_not_be_able_to_sign_block() {
|
||||||
|
let tester = CliqueTester::with(3, 1, vec!['A']);
|
||||||
|
let err = tester.new_block_and_import(CliqueBlockType::Empty, &tester.genesis, None, 'B').unwrap_err();
|
||||||
|
|
||||||
|
match err.kind() {
|
||||||
|
ErrorKind::Engine(EngineError::NotAuthorized(_)) => (),
|
||||||
|
_ => assert!(true == false, "Wrong error kind"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn signer_should_not_be_able_to_sign_two_consequtive_blocks() {
|
||||||
|
let tester = CliqueTester::with(3, 1, vec!['A', 'B']);
|
||||||
|
let b = tester.new_block_and_import(CliqueBlockType::Empty, &tester.genesis, None, 'A').unwrap();
|
||||||
|
let err = tester.new_block_and_import(CliqueBlockType::Empty, &b, None, 'A').unwrap_err();
|
||||||
|
|
||||||
|
match err.kind() {
|
||||||
|
ErrorKind::Engine(EngineError::CliqueTooRecentlySigned(_)) => (),
|
||||||
|
_ => assert!(true == false, "Wrong error kind"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn recent_signers_should_not_reset_on_checkpoint() {
|
||||||
|
let tester = CliqueTester::with(3, 1, vec!['A', 'B', 'C']);
|
||||||
|
|
||||||
|
let block = tester.new_block_and_import(CliqueBlockType::Empty, &tester.genesis, None, 'A').unwrap();
|
||||||
|
let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'B').unwrap();
|
||||||
|
let block = tester.new_block_and_import(CliqueBlockType::Checkpoint, &block, None, 'A').unwrap();
|
||||||
|
|
||||||
|
let err = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'A').unwrap_err();
|
||||||
|
|
||||||
|
match err.kind() {
|
||||||
|
ErrorKind::Engine(EngineError::CliqueTooRecentlySigned(_)) => (),
|
||||||
|
_ => assert!(true == false, "Wrong error kind"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not part of http://eips.ethereum.org/EIPS/eip-225
|
||||||
|
#[test]
|
||||||
|
fn bonus_consensus_should_keep_track_of_votes_before_latest_per_signer() {
|
||||||
|
let tester = CliqueTester::with(100, 1, vec!['A', 'B', 'C', 'D']);
|
||||||
|
|
||||||
|
// Add a vote for `E` signed by `A`
|
||||||
|
let vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &tester.genesis,
|
||||||
|
Some(tester.signers[&'E'].address()), 'A').unwrap();
|
||||||
|
// Empty block signed by `B`
|
||||||
|
let vote = tester.new_block_and_import(CliqueBlockType::Empty, &vote, None, 'B').unwrap();
|
||||||
|
|
||||||
|
// Empty block signed by `C`
|
||||||
|
let vote = tester.new_block_and_import(CliqueBlockType::Empty, &vote, None, 'C').unwrap();
|
||||||
|
|
||||||
|
// Empty block signed by `D`
|
||||||
|
let vote = tester.new_block_and_import(CliqueBlockType::Empty, &vote, None, 'D').unwrap();
|
||||||
|
|
||||||
|
// Add a vote for `F` signed by `A`
|
||||||
|
let vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &vote,
|
||||||
|
Some(tester.signers[&'F'].address()), 'A').unwrap();
|
||||||
|
// Empty block signed by `C`
|
||||||
|
let vote = tester.new_block_and_import(CliqueBlockType::Empty, &vote, None, 'C').unwrap();
|
||||||
|
|
||||||
|
// Empty block signed by `D`
|
||||||
|
let vote = tester.new_block_and_import(CliqueBlockType::Empty, &vote, None, 'D').unwrap();
|
||||||
|
|
||||||
|
// Add a vote for `E` signed by `B`
|
||||||
|
let vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &vote,
|
||||||
|
Some(tester.signers[&'E'].address()), 'B').unwrap();
|
||||||
|
// Empty block signed by `A`
|
||||||
|
let vote = tester.new_block_and_import(CliqueBlockType::Empty, &vote, None, 'A').unwrap();
|
||||||
|
|
||||||
|
// Empty block signed by `C`
|
||||||
|
let vote = tester.new_block_and_import(CliqueBlockType::Empty, &vote, None, 'C').unwrap();
|
||||||
|
|
||||||
|
// Empty block signed by `D`
|
||||||
|
let vote = tester.new_block_and_import(CliqueBlockType::Empty, &vote, None, 'D').unwrap();
|
||||||
|
|
||||||
|
// Add a vote for `F` signed by `B`
|
||||||
|
let vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &vote,
|
||||||
|
Some(tester.signers[&'F'].address()), 'B').unwrap();
|
||||||
|
|
||||||
|
// Empty block signed by A`
|
||||||
|
let vote = tester.new_block_and_import(CliqueBlockType::Empty, &vote, None, 'A').unwrap();
|
||||||
|
|
||||||
|
// Add a vote for `E` signed by `C`
|
||||||
|
let vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &vote,
|
||||||
|
Some(tester.signers[&'E'].address()), 'C').unwrap();
|
||||||
|
|
||||||
|
let tags = tester.into_tags(tester.clique_signers(&vote.hash()));
|
||||||
|
assert_eq!(&tags, &['A', 'B', 'C', 'D', 'E']);
|
||||||
|
}
|
115
ethcore/src/engines/clique/util.rs
Normal file
115
ethcore/src/engines/clique/util.rs
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity Ethereum.
|
||||||
|
|
||||||
|
// Parity Ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity Ethereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use std::collections::BTreeSet;
|
||||||
|
|
||||||
|
use engines::EngineError;
|
||||||
|
use engines::clique::{ADDRESS_LENGTH, SIGNATURE_LENGTH, VANITY_LENGTH, NULL_NONCE, NULL_MIXHASH};
|
||||||
|
use error::Error;
|
||||||
|
use ethereum_types::{Address, H256};
|
||||||
|
use ethkey::{public_to_address, recover as ec_recover, Signature};
|
||||||
|
use lru_cache::LruCache;
|
||||||
|
use parking_lot::RwLock;
|
||||||
|
use rlp::encode;
|
||||||
|
use types::header::Header;
|
||||||
|
|
||||||
|
/// How many recovered signature to cache in the memory.
|
||||||
|
pub const CREATOR_CACHE_NUM: usize = 4096;
|
||||||
|
lazy_static! {
|
||||||
|
/// key: header hash
|
||||||
|
/// value: creator address
|
||||||
|
static ref CREATOR_BY_HASH: RwLock<LruCache<H256, Address>> = RwLock::new(LruCache::new(CREATOR_CACHE_NUM));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Recover block creator from signature
|
||||||
|
pub fn recover_creator(header: &Header) -> Result<Address, Error> {
|
||||||
|
// Initialization
|
||||||
|
let mut cache = CREATOR_BY_HASH.write();
|
||||||
|
|
||||||
|
if let Some(creator) = cache.get_mut(&header.hash()) {
|
||||||
|
return Ok(*creator);
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = header.extra_data();
|
||||||
|
if data.len() < VANITY_LENGTH {
|
||||||
|
Err(EngineError::CliqueMissingVanity)?
|
||||||
|
}
|
||||||
|
|
||||||
|
if data.len() < VANITY_LENGTH + SIGNATURE_LENGTH {
|
||||||
|
Err(EngineError::CliqueMissingSignature)?
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split `signed_extra data` and `signature`
|
||||||
|
let (signed_data_slice, signature_slice) = data.split_at(data.len() - SIGNATURE_LENGTH);
|
||||||
|
|
||||||
|
// convert `&[u8]` to `[u8; 65]`
|
||||||
|
let signature = {
|
||||||
|
let mut s = [0; SIGNATURE_LENGTH];
|
||||||
|
s.copy_from_slice(signature_slice);
|
||||||
|
s
|
||||||
|
};
|
||||||
|
|
||||||
|
// modify header and hash it
|
||||||
|
let unsigned_header = &mut header.clone();
|
||||||
|
unsigned_header.set_extra_data(signed_data_slice.to_vec());
|
||||||
|
let msg = unsigned_header.hash();
|
||||||
|
|
||||||
|
let pubkey = ec_recover(&Signature::from(signature), &msg)?;
|
||||||
|
let creator = public_to_address(&pubkey);
|
||||||
|
|
||||||
|
cache.insert(header.hash(), creator.clone());
|
||||||
|
Ok(creator)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extract signer list from extra_data.
|
||||||
|
///
|
||||||
|
/// Layout of extra_data:
|
||||||
|
/// ----
|
||||||
|
/// VANITY: 32 bytes
|
||||||
|
/// Signers: N * 32 bytes as hex encoded (20 characters)
|
||||||
|
/// Signature: 65 bytes
|
||||||
|
/// --
|
||||||
|
pub fn extract_signers(header: &Header) -> Result<BTreeSet<Address>, Error> {
|
||||||
|
let data = header.extra_data();
|
||||||
|
|
||||||
|
if data.len() <= VANITY_LENGTH + SIGNATURE_LENGTH {
|
||||||
|
Err(EngineError::CliqueCheckpointNoSigner)?
|
||||||
|
}
|
||||||
|
|
||||||
|
// extract only the portion of extra_data which includes the signer list
|
||||||
|
let signers_raw = &data[(VANITY_LENGTH)..data.len() - (SIGNATURE_LENGTH)];
|
||||||
|
|
||||||
|
if signers_raw.len() % ADDRESS_LENGTH != 0 {
|
||||||
|
Err(EngineError::CliqueCheckpointInvalidSigners(signers_raw.len()))?
|
||||||
|
}
|
||||||
|
|
||||||
|
let num_signers = signers_raw.len() / 20;
|
||||||
|
|
||||||
|
let signers: BTreeSet<Address> = (0..num_signers)
|
||||||
|
.map(|i| {
|
||||||
|
let start = i * ADDRESS_LENGTH;
|
||||||
|
let end = start + ADDRESS_LENGTH;
|
||||||
|
signers_raw[start..end].into()
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(signers)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieve `null_seal`
|
||||||
|
pub fn null_seal() -> Vec<Vec<u8>> {
|
||||||
|
vec![encode(&NULL_MIXHASH.to_vec()), encode(&NULL_NONCE.to_vec())]
|
||||||
|
}
|
@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
mod authority_round;
|
mod authority_round;
|
||||||
mod basic_authority;
|
mod basic_authority;
|
||||||
|
mod clique;
|
||||||
mod instant_seal;
|
mod instant_seal;
|
||||||
mod null_engine;
|
mod null_engine;
|
||||||
mod validator_set;
|
mod validator_set;
|
||||||
@ -30,6 +31,7 @@ pub use self::basic_authority::BasicAuthority;
|
|||||||
pub use self::instant_seal::{InstantSeal, InstantSealParams};
|
pub use self::instant_seal::{InstantSeal, InstantSealParams};
|
||||||
pub use self::null_engine::NullEngine;
|
pub use self::null_engine::NullEngine;
|
||||||
pub use self::signer::EngineSigner;
|
pub use self::signer::EngineSigner;
|
||||||
|
pub use self::clique::Clique;
|
||||||
|
|
||||||
// TODO [ToDr] Remove re-export (#10130)
|
// TODO [ToDr] Remove re-export (#10130)
|
||||||
pub use types::engines::ForkChoice;
|
pub use types::engines::ForkChoice;
|
||||||
@ -50,7 +52,7 @@ use types::transaction::{self, UnverifiedTransaction, SignedTransaction};
|
|||||||
|
|
||||||
use ethkey::{Signature};
|
use ethkey::{Signature};
|
||||||
use machine::{self, Machine, AuxiliaryRequest, AuxiliaryData};
|
use machine::{self, Machine, AuxiliaryRequest, AuxiliaryData};
|
||||||
use ethereum_types::{H256, U256, Address};
|
use ethereum_types::{H64, H256, U256, Address};
|
||||||
use unexpected::{Mismatch, OutOfBounds};
|
use unexpected::{Mismatch, OutOfBounds};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use types::ancestry_action::AncestryAction;
|
use types::ancestry_action::AncestryAction;
|
||||||
@ -85,12 +87,45 @@ pub enum EngineError {
|
|||||||
RequiresClient,
|
RequiresClient,
|
||||||
/// Invalid engine specification or implementation.
|
/// Invalid engine specification or implementation.
|
||||||
InvalidEngine,
|
InvalidEngine,
|
||||||
|
/// Requires signer ref, but none registered.
|
||||||
|
RequiresSigner,
|
||||||
|
/// Checkpoint is missing
|
||||||
|
CliqueMissingCheckpoint(H256),
|
||||||
|
/// Missing vanity data
|
||||||
|
CliqueMissingVanity,
|
||||||
|
/// Missing signature
|
||||||
|
CliqueMissingSignature,
|
||||||
|
/// Missing signers
|
||||||
|
CliqueCheckpointNoSigner,
|
||||||
|
/// List of signers is invalid
|
||||||
|
CliqueCheckpointInvalidSigners(usize),
|
||||||
|
/// Wrong author on a checkpoint
|
||||||
|
CliqueWrongAuthorCheckpoint(Mismatch<Address>),
|
||||||
|
/// Wrong checkpoint authors recovered
|
||||||
|
CliqueFaultyRecoveredSigners(Vec<String>),
|
||||||
|
/// Invalid nonce (should contain vote)
|
||||||
|
CliqueInvalidNonce(H64),
|
||||||
|
/// The signer signed a block to recently
|
||||||
|
CliqueTooRecentlySigned(Address),
|
||||||
|
/// Custom
|
||||||
|
Custom(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for EngineError {
|
impl fmt::Display for EngineError {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
use self::EngineError::*;
|
use self::EngineError::*;
|
||||||
let msg = match *self {
|
let msg = match *self {
|
||||||
|
CliqueMissingCheckpoint(ref hash) => format!("Missing checkpoint block: {}", hash),
|
||||||
|
CliqueMissingVanity => format!("Extra data is missing vanity data"),
|
||||||
|
CliqueMissingSignature => format!("Extra data is missing signature"),
|
||||||
|
CliqueCheckpointInvalidSigners(len) => format!("Checkpoint block list was of length: {} of checkpoint but
|
||||||
|
it needs to be bigger than zero and a divisible by 20", len),
|
||||||
|
CliqueCheckpointNoSigner => format!("Checkpoint block list of signers was empty"),
|
||||||
|
CliqueInvalidNonce(ref mis) => format!("Unexpected nonce {} expected {} or {}", mis, 0_u64, u64::max_value()),
|
||||||
|
CliqueWrongAuthorCheckpoint(ref oob) => format!("Unexpected checkpoint author: {}", oob),
|
||||||
|
CliqueFaultyRecoveredSigners(ref mis) => format!("Faulty recovered signers {:?}", mis),
|
||||||
|
CliqueTooRecentlySigned(ref address) => format!("The signer: {} has signed a block too recently", address),
|
||||||
|
Custom(ref s) => s.clone(),
|
||||||
DoubleVote(ref address) => format!("Author {} issued too many blocks.", address),
|
DoubleVote(ref address) => format!("Author {} issued too many blocks.", address),
|
||||||
NotProposer(ref mis) => format!("Author is not a current proposer: {}", mis),
|
NotProposer(ref mis) => format!("Author is not a current proposer: {}", mis),
|
||||||
NotAuthorized(ref address) => format!("Signer {} is not authorized.", address),
|
NotAuthorized(ref address) => format!("Signer {} is not authorized.", address),
|
||||||
@ -100,6 +135,7 @@ impl fmt::Display for EngineError {
|
|||||||
FailedSystemCall(ref msg) => format!("Failed to make system call: {}", msg),
|
FailedSystemCall(ref msg) => format!("Failed to make system call: {}", msg),
|
||||||
MalformedMessage(ref msg) => format!("Received malformed consensus message: {}", msg),
|
MalformedMessage(ref msg) => format!("Received malformed consensus message: {}", msg),
|
||||||
RequiresClient => format!("Call requires client but none registered"),
|
RequiresClient => format!("Call requires client but none registered"),
|
||||||
|
RequiresSigner => format!("Call requires signer but none registered"),
|
||||||
InvalidEngine => format!("Invalid engine specification or implementation"),
|
InvalidEngine => format!("Invalid engine specification or implementation"),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -120,7 +156,7 @@ pub enum Seal {
|
|||||||
Proposal(Vec<Bytes>),
|
Proposal(Vec<Bytes>),
|
||||||
/// Regular block seal; should be part of the blockchain.
|
/// Regular block seal; should be part of the blockchain.
|
||||||
Regular(Vec<Bytes>),
|
Regular(Vec<Bytes>),
|
||||||
/// Engine does generate seal for this block right now.
|
/// Engine does not generate seal for this block right now.
|
||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -263,6 +299,9 @@ pub trait Engine<M: Machine>: Sync + Send {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Allow mutating the header during seal generation. Currently only used by Clique.
|
||||||
|
fn on_seal_block(&self, _block: &mut ExecutedBlock) -> Result<(), Error> { Ok(()) }
|
||||||
|
|
||||||
/// None means that it requires external input (e.g. PoW) to seal a block.
|
/// None means that it requires external input (e.g. PoW) to seal a block.
|
||||||
/// Some(true) means the engine is currently prime for seal generation (i.e. node is the current validator).
|
/// Some(true) means the engine is currently prime for seal generation (i.e. node is the current validator).
|
||||||
/// Some(false) means that the node might seal internally but is not qualified now.
|
/// Some(false) means that the node might seal internally but is not qualified now.
|
||||||
@ -387,7 +426,7 @@ pub trait Engine<M: Machine>: Sync + Send {
|
|||||||
fn step(&self) {}
|
fn step(&self) {}
|
||||||
|
|
||||||
/// Stops any services that the may hold the Engine and makes it safe to drop.
|
/// Stops any services that the may hold the Engine and makes it safe to drop.
|
||||||
fn stop(&self) {}
|
fn stop(&mut self) {}
|
||||||
|
|
||||||
/// Create a factory for building snapshot chunks and restoring from them.
|
/// Create a factory for building snapshot chunks and restoring from them.
|
||||||
/// Returning `None` indicates that this engine doesn't support snapshot creation.
|
/// Returning `None` indicates that this engine doesn't support snapshot creation.
|
||||||
@ -421,6 +460,11 @@ pub trait Engine<M: Machine>: Sync + Send {
|
|||||||
|
|
||||||
/// Check whether the given new block is the best block, after finalization check.
|
/// Check whether the given new block is the best block, after finalization check.
|
||||||
fn fork_choice(&self, new: &ExtendedHeader, best: &ExtendedHeader) -> ForkChoice;
|
fn fork_choice(&self, new: &ExtendedHeader, best: &ExtendedHeader) -> ForkChoice;
|
||||||
|
|
||||||
|
/// Returns author should used when executing tx's for this block.
|
||||||
|
fn executive_author(&self, header: &Header) -> Result<Address, Error> {
|
||||||
|
Ok(*header.author())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check whether a given block is the best block based on the default total difficulty rule.
|
/// Check whether a given block is the best block based on the default total difficulty rule.
|
||||||
|
@ -94,6 +94,11 @@ pub fn new_mix<'a, T: Into<SpecParams<'a>>>(params: T) -> Spec {
|
|||||||
load(params.into(), include_bytes!("../../res/ethereum/mix.json"))
|
load(params.into(), include_bytes!("../../res/ethereum/mix.json"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a new Callisto chain spec
|
||||||
|
pub fn new_callisto<'a, T: Into<SpecParams<'a>>>(params: T) -> Spec {
|
||||||
|
load(params.into(), include_bytes!("../../res/ethereum/callisto.json"))
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a new Morden testnet chain spec.
|
/// Create a new Morden testnet chain spec.
|
||||||
pub fn new_morden<'a, T: Into<SpecParams<'a>>>(params: T) -> Spec {
|
pub fn new_morden<'a, T: Into<SpecParams<'a>>>(params: T) -> Spec {
|
||||||
load(params.into(), include_bytes!("../../res/ethereum/morden.json"))
|
load(params.into(), include_bytes!("../../res/ethereum/morden.json"))
|
||||||
@ -109,16 +114,26 @@ pub fn new_kovan<'a, T: Into<SpecParams<'a>>>(params: T) -> Spec {
|
|||||||
load(params.into(), include_bytes!("../../res/ethereum/kovan.json"))
|
load(params.into(), include_bytes!("../../res/ethereum/kovan.json"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a new Rinkeby testnet chain spec.
|
||||||
|
pub fn new_rinkeby<'a, T: Into<SpecParams<'a>>>(params: T) -> Spec {
|
||||||
|
load(params.into(), include_bytes!("../../res/ethereum/rinkeby.json"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new Görli testnet chain spec.
|
||||||
|
pub fn new_goerli<'a, T: Into<SpecParams<'a>>>(params: T) -> Spec {
|
||||||
|
load(params.into(), include_bytes!("../../res/ethereum/goerli.json"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new Kotti testnet chain spec.
|
||||||
|
pub fn new_kotti<'a, T: Into<SpecParams<'a>>>(params: T) -> Spec {
|
||||||
|
load(params.into(), include_bytes!("../../res/ethereum/kotti.json"))
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a new POA Sokol testnet chain spec.
|
/// Create a new POA Sokol testnet chain spec.
|
||||||
pub fn new_sokol<'a, T: Into<SpecParams<'a>>>(params: T) -> Spec {
|
pub fn new_sokol<'a, T: Into<SpecParams<'a>>>(params: T) -> Spec {
|
||||||
load(params.into(), include_bytes!("../../res/ethereum/poasokol.json"))
|
load(params.into(), include_bytes!("../../res/ethereum/poasokol.json"))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new Callisto chaun spec
|
|
||||||
pub fn new_callisto<'a, T: Into<SpecParams<'a>>>(params: T) -> Spec {
|
|
||||||
load(params.into(), include_bytes!("../../res/ethereum/callisto.json"))
|
|
||||||
}
|
|
||||||
|
|
||||||
// For tests
|
// For tests
|
||||||
|
|
||||||
/// Create a new Foundation Frontier-era chain spec as though it never changes to Homestead.
|
/// Create a new Foundation Frontier-era chain spec as though it never changes to Homestead.
|
||||||
|
@ -214,7 +214,6 @@ impl Author {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
struct SealingWork {
|
struct SealingWork {
|
||||||
queue: UsingQueue<ClosedBlock>,
|
queue: UsingQueue<ClosedBlock>,
|
||||||
enabled: bool,
|
enabled: bool,
|
||||||
@ -630,7 +629,10 @@ impl Miner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attempts to perform internal sealing (one that does not require work) and handles the result depending on the type of Seal.
|
// TODO: (https://github.com/paritytech/parity-ethereum/issues/10407)
|
||||||
|
// This is only used in authority_round path, and should be refactored to merge with the other seal() path.
|
||||||
|
// Attempts to perform internal sealing (one that does not require work) and handles the result depending on the
|
||||||
|
// type of Seal.
|
||||||
fn seal_and_import_block_internally<C>(&self, chain: &C, block: ClosedBlock) -> bool
|
fn seal_and_import_block_internally<C>(&self, chain: &C, block: ClosedBlock) -> bool
|
||||||
where C: BlockChain + SealedBlockImporter,
|
where C: BlockChain + SealedBlockImporter,
|
||||||
{
|
{
|
||||||
|
@ -35,7 +35,7 @@ use vm::{EnvInfo, CallType, ActionValue, ActionParams, ParamsType};
|
|||||||
|
|
||||||
use builtin::Builtin;
|
use builtin::Builtin;
|
||||||
use engines::{
|
use engines::{
|
||||||
EthEngine, NullEngine, InstantSeal, InstantSealParams, BasicAuthority,
|
EthEngine, NullEngine, InstantSeal, InstantSealParams, BasicAuthority, Clique,
|
||||||
AuthorityRound, DEFAULT_BLOCKHASH_CONTRACT
|
AuthorityRound, DEFAULT_BLOCKHASH_CONTRACT
|
||||||
};
|
};
|
||||||
use error::Error;
|
use error::Error;
|
||||||
@ -99,9 +99,9 @@ pub struct CommonParams {
|
|||||||
pub validate_receipts_transition: BlockNumber,
|
pub validate_receipts_transition: BlockNumber,
|
||||||
/// Validate transaction chain id.
|
/// Validate transaction chain id.
|
||||||
pub validate_chain_id_transition: BlockNumber,
|
pub validate_chain_id_transition: BlockNumber,
|
||||||
/// Number of first block where EIP-140 (Metropolis: REVERT opcode) rules begin.
|
/// Number of first block where EIP-140 rules begin.
|
||||||
pub eip140_transition: BlockNumber,
|
pub eip140_transition: BlockNumber,
|
||||||
/// Number of first block where EIP-210 (Metropolis: BLOCKHASH changes) rules begin.
|
/// Number of first block where EIP-210 rules begin.
|
||||||
pub eip210_transition: BlockNumber,
|
pub eip210_transition: BlockNumber,
|
||||||
/// EIP-210 Blockhash contract address.
|
/// EIP-210 Blockhash contract address.
|
||||||
pub eip210_contract_address: Address,
|
pub eip210_contract_address: Address,
|
||||||
@ -109,8 +109,7 @@ pub struct CommonParams {
|
|||||||
pub eip210_contract_code: Bytes,
|
pub eip210_contract_code: Bytes,
|
||||||
/// Gas allocated for EIP-210 blockhash update.
|
/// Gas allocated for EIP-210 blockhash update.
|
||||||
pub eip210_contract_gas: U256,
|
pub eip210_contract_gas: U256,
|
||||||
/// Number of first block where EIP-211 (Metropolis: RETURNDATASIZE/RETURNDATACOPY) rules
|
/// Number of first block where EIP-211 rules begin.
|
||||||
/// begin.
|
|
||||||
pub eip211_transition: BlockNumber,
|
pub eip211_transition: BlockNumber,
|
||||||
/// Number of first block where EIP-214 rules begin.
|
/// Number of first block where EIP-214 rules begin.
|
||||||
pub eip214_transition: BlockNumber,
|
pub eip214_transition: BlockNumber,
|
||||||
@ -611,6 +610,8 @@ impl Spec {
|
|||||||
ethjson::spec::Engine::InstantSeal(Some(instant_seal)) => Arc::new(InstantSeal::new(instant_seal.params.into(), machine)),
|
ethjson::spec::Engine::InstantSeal(Some(instant_seal)) => Arc::new(InstantSeal::new(instant_seal.params.into(), machine)),
|
||||||
ethjson::spec::Engine::InstantSeal(None) => Arc::new(InstantSeal::new(InstantSealParams::default(), machine)),
|
ethjson::spec::Engine::InstantSeal(None) => Arc::new(InstantSeal::new(InstantSealParams::default(), machine)),
|
||||||
ethjson::spec::Engine::BasicAuthority(basic_authority) => Arc::new(BasicAuthority::new(basic_authority.params.into(), machine)),
|
ethjson::spec::Engine::BasicAuthority(basic_authority) => Arc::new(BasicAuthority::new(basic_authority.params.into(), machine)),
|
||||||
|
ethjson::spec::Engine::Clique(clique) => Clique::new(clique.params.into(), machine)
|
||||||
|
.expect("Failed to start Clique consensus engine."),
|
||||||
ethjson::spec::Engine::AuthorityRound(authority_round) => AuthorityRound::new(authority_round.params.into(), machine)
|
ethjson::spec::Engine::AuthorityRound(authority_round) => AuthorityRound::new(authority_round.params.into(), machine)
|
||||||
.expect("Failed to start AuthorityRound consensus engine."),
|
.expect("Failed to start AuthorityRound consensus engine."),
|
||||||
}
|
}
|
||||||
@ -827,7 +828,6 @@ impl Spec {
|
|||||||
ethjson::spec::Spec::load(reader)
|
ethjson::spec::Spec::load(reader)
|
||||||
.map_err(fmt_err)
|
.map_err(fmt_err)
|
||||||
.map(load_machine_from)
|
.map(load_machine_from)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Loads spec from json file. Provide factories for executing contracts and ensuring
|
/// Loads spec from json file. Provide factories for executing contracts and ensuring
|
||||||
|
57
json/src/spec/clique.rs
Normal file
57
json/src/spec/clique.rs
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity Ethereum.
|
||||||
|
|
||||||
|
// Parity Ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity Ethereum is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
//! Clique params deserialization.
|
||||||
|
|
||||||
|
use std::num::NonZeroU64;
|
||||||
|
|
||||||
|
/// Clique params deserialization.
|
||||||
|
#[derive(Debug, PartialEq, Deserialize)]
|
||||||
|
pub struct CliqueParams {
|
||||||
|
/// period as defined in EIP
|
||||||
|
pub period: Option<u64>,
|
||||||
|
/// epoch length as defined in EIP
|
||||||
|
pub epoch: Option<NonZeroU64>
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clique engine deserialization.
|
||||||
|
#[derive(Debug, PartialEq, Deserialize)]
|
||||||
|
pub struct Clique {
|
||||||
|
/// CliqueEngine params
|
||||||
|
pub params: CliqueParams,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use serde_json;
|
||||||
|
use uint::Uint;
|
||||||
|
use ethereum_types::U256;
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn clique_deserialization() {
|
||||||
|
let s = r#"{
|
||||||
|
"params": {
|
||||||
|
"period": 5,
|
||||||
|
"epoch": 30000
|
||||||
|
}
|
||||||
|
}"#;
|
||||||
|
|
||||||
|
let deserialized: Clique = serde_json::from_str(s).unwrap();
|
||||||
|
assert_eq!(deserialized.params.period, Some(5u64));
|
||||||
|
assert_eq!(deserialized.params.epoch, NonZeroU64::new(30000));
|
||||||
|
}
|
||||||
|
}
|
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
//! Engine deserialization.
|
//! Engine deserialization.
|
||||||
|
|
||||||
use super::{Ethash, BasicAuthority, AuthorityRound, NullEngine, InstantSeal};
|
use super::{Ethash, BasicAuthority, AuthorityRound, NullEngine, InstantSeal, Clique};
|
||||||
|
|
||||||
/// Engine deserialization.
|
/// Engine deserialization.
|
||||||
#[derive(Debug, PartialEq, Deserialize)]
|
#[derive(Debug, PartialEq, Deserialize)]
|
||||||
@ -34,6 +34,8 @@ pub enum Engine {
|
|||||||
BasicAuthority(BasicAuthority),
|
BasicAuthority(BasicAuthority),
|
||||||
/// AuthorityRound engine.
|
/// AuthorityRound engine.
|
||||||
AuthorityRound(AuthorityRound),
|
AuthorityRound(AuthorityRound),
|
||||||
|
/// Clique engine.
|
||||||
|
Clique(Clique)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@ -130,5 +132,19 @@ mod tests {
|
|||||||
Engine::AuthorityRound(_) => {}, // AuthorityRound is unit tested in its own file.
|
Engine::AuthorityRound(_) => {}, // AuthorityRound is unit tested in its own file.
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let s = r#"{
|
||||||
|
"clique": {
|
||||||
|
"params": {
|
||||||
|
"period": 15,
|
||||||
|
"epoch": 30000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}"#;
|
||||||
|
let deserialized: Engine = serde_json::from_str(s).unwrap();
|
||||||
|
match deserialized {
|
||||||
|
Engine::Clique(_) => {}, // Clique is unit tested in its own file.
|
||||||
|
_ => panic!(),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,7 @@ pub mod authority_round;
|
|||||||
pub mod null_engine;
|
pub mod null_engine;
|
||||||
pub mod instant_seal;
|
pub mod instant_seal;
|
||||||
pub mod hardcoded_sync;
|
pub mod hardcoded_sync;
|
||||||
|
pub mod clique;
|
||||||
|
|
||||||
pub use self::account::Account;
|
pub use self::account::Account;
|
||||||
pub use self::builtin::{Builtin, Pricing, Linear};
|
pub use self::builtin::{Builtin, Pricing, Linear};
|
||||||
@ -44,6 +45,7 @@ pub use self::ethash::{Ethash, EthashParams, BlockReward};
|
|||||||
pub use self::validator_set::ValidatorSet;
|
pub use self::validator_set::ValidatorSet;
|
||||||
pub use self::basic_authority::{BasicAuthority, BasicAuthorityParams};
|
pub use self::basic_authority::{BasicAuthority, BasicAuthorityParams};
|
||||||
pub use self::authority_round::{AuthorityRound, AuthorityRoundParams};
|
pub use self::authority_round::{AuthorityRound, AuthorityRoundParams};
|
||||||
|
pub use self::clique::{Clique, CliqueParams};
|
||||||
pub use self::null_engine::{NullEngine, NullEngineParams};
|
pub use self::null_engine::{NullEngine, NullEngineParams};
|
||||||
pub use self::instant_seal::{InstantSeal, InstantSealParams};
|
pub use self::instant_seal::{InstantSeal, InstantSealParams};
|
||||||
pub use self::hardcoded_sync::HardcodedSync;
|
pub use self::hardcoded_sync::HardcodedSync;
|
||||||
|
@ -300,7 +300,7 @@ usage! {
|
|||||||
|
|
||||||
ARG arg_chain: (String) = "foundation", or |c: &Config| c.parity.as_ref()?.chain.clone(),
|
ARG arg_chain: (String) = "foundation", or |c: &Config| c.parity.as_ref()?.chain.clone(),
|
||||||
"--chain=[CHAIN]",
|
"--chain=[CHAIN]",
|
||||||
"Specify the blockchain type. CHAIN may be either a JSON chain specification file or ethereum, classic, poacore, tobalaba, expanse, musicoin, ellaism, easthub, social, mix, callisto, morden, ropsten, kovan, poasokol, testnet, or dev.",
|
"Specify the blockchain type. CHAIN may be either a JSON chain specification file or ethereum, classic, poacore, tobalaba, expanse, musicoin, ellaism, easthub, social, mix, callisto, morden, ropsten, kovan, rinkeby, goerli, kotti, poasokol, testnet, or dev.",
|
||||||
|
|
||||||
ARG arg_keys_path: (String) = "$BASE/keys", or |c: &Config| c.parity.as_ref()?.keys_path.clone(),
|
ARG arg_keys_path: (String) = "$BASE/keys", or |c: &Config| c.parity.as_ref()?.keys_path.clone(),
|
||||||
"--keys-path=[PATH]",
|
"--keys-path=[PATH]",
|
||||||
|
@ -47,6 +47,9 @@ pub enum SpecType {
|
|||||||
Morden,
|
Morden,
|
||||||
Ropsten,
|
Ropsten,
|
||||||
Kovan,
|
Kovan,
|
||||||
|
Rinkeby,
|
||||||
|
Goerli,
|
||||||
|
Kotti,
|
||||||
Sokol,
|
Sokol,
|
||||||
Dev,
|
Dev,
|
||||||
Custom(String),
|
Custom(String),
|
||||||
@ -77,6 +80,9 @@ impl str::FromStr for SpecType {
|
|||||||
"morden" | "classic-testnet" => SpecType::Morden,
|
"morden" | "classic-testnet" => SpecType::Morden,
|
||||||
"ropsten" => SpecType::Ropsten,
|
"ropsten" => SpecType::Ropsten,
|
||||||
"kovan" | "testnet" => SpecType::Kovan,
|
"kovan" | "testnet" => SpecType::Kovan,
|
||||||
|
"rinkeby" => SpecType::Rinkeby,
|
||||||
|
"goerli" | "görli" => SpecType::Goerli,
|
||||||
|
"kotti" => SpecType::Kotti,
|
||||||
"sokol" | "poasokol" => SpecType::Sokol,
|
"sokol" | "poasokol" => SpecType::Sokol,
|
||||||
"dev" => SpecType::Dev,
|
"dev" => SpecType::Dev,
|
||||||
other => SpecType::Custom(other.into()),
|
other => SpecType::Custom(other.into()),
|
||||||
@ -102,6 +108,9 @@ impl fmt::Display for SpecType {
|
|||||||
SpecType::Morden => "morden",
|
SpecType::Morden => "morden",
|
||||||
SpecType::Ropsten => "ropsten",
|
SpecType::Ropsten => "ropsten",
|
||||||
SpecType::Kovan => "kovan",
|
SpecType::Kovan => "kovan",
|
||||||
|
SpecType::Rinkeby => "rinkeby",
|
||||||
|
SpecType::Goerli => "goerli",
|
||||||
|
SpecType::Kotti => "kotti",
|
||||||
SpecType::Sokol => "sokol",
|
SpecType::Sokol => "sokol",
|
||||||
SpecType::Dev => "dev",
|
SpecType::Dev => "dev",
|
||||||
SpecType::Custom(ref custom) => custom,
|
SpecType::Custom(ref custom) => custom,
|
||||||
@ -127,6 +136,9 @@ impl SpecType {
|
|||||||
SpecType::Morden => Ok(ethereum::new_morden(params)),
|
SpecType::Morden => Ok(ethereum::new_morden(params)),
|
||||||
SpecType::Ropsten => Ok(ethereum::new_ropsten(params)),
|
SpecType::Ropsten => Ok(ethereum::new_ropsten(params)),
|
||||||
SpecType::Kovan => Ok(ethereum::new_kovan(params)),
|
SpecType::Kovan => Ok(ethereum::new_kovan(params)),
|
||||||
|
SpecType::Rinkeby => Ok(ethereum::new_rinkeby(params)),
|
||||||
|
SpecType::Goerli => Ok(ethereum::new_goerli(params)),
|
||||||
|
SpecType::Kotti => Ok(ethereum::new_kotti(params)),
|
||||||
SpecType::Sokol => Ok(ethereum::new_sokol(params)),
|
SpecType::Sokol => Ok(ethereum::new_sokol(params)),
|
||||||
SpecType::Dev => Ok(Spec::new_instant()),
|
SpecType::Dev => Ok(Spec::new_instant()),
|
||||||
SpecType::Custom(ref filename) => {
|
SpecType::Custom(ref filename) => {
|
||||||
@ -385,6 +397,10 @@ mod tests {
|
|||||||
assert_eq!(SpecType::Ropsten, "ropsten".parse().unwrap());
|
assert_eq!(SpecType::Ropsten, "ropsten".parse().unwrap());
|
||||||
assert_eq!(SpecType::Kovan, "kovan".parse().unwrap());
|
assert_eq!(SpecType::Kovan, "kovan".parse().unwrap());
|
||||||
assert_eq!(SpecType::Kovan, "testnet".parse().unwrap());
|
assert_eq!(SpecType::Kovan, "testnet".parse().unwrap());
|
||||||
|
assert_eq!(SpecType::Rinkeby, "rinkeby".parse().unwrap());
|
||||||
|
assert_eq!(SpecType::Goerli, "goerli".parse().unwrap());
|
||||||
|
assert_eq!(SpecType::Goerli, "görli".parse().unwrap());
|
||||||
|
assert_eq!(SpecType::Kotti, "kotti".parse().unwrap());
|
||||||
assert_eq!(SpecType::Sokol, "sokol".parse().unwrap());
|
assert_eq!(SpecType::Sokol, "sokol".parse().unwrap());
|
||||||
assert_eq!(SpecType::Sokol, "poasokol".parse().unwrap());
|
assert_eq!(SpecType::Sokol, "poasokol".parse().unwrap());
|
||||||
}
|
}
|
||||||
@ -410,6 +426,9 @@ mod tests {
|
|||||||
assert_eq!(format!("{}", SpecType::Morden), "morden");
|
assert_eq!(format!("{}", SpecType::Morden), "morden");
|
||||||
assert_eq!(format!("{}", SpecType::Ropsten), "ropsten");
|
assert_eq!(format!("{}", SpecType::Ropsten), "ropsten");
|
||||||
assert_eq!(format!("{}", SpecType::Kovan), "kovan");
|
assert_eq!(format!("{}", SpecType::Kovan), "kovan");
|
||||||
|
assert_eq!(format!("{}", SpecType::Rinkeby), "rinkeby");
|
||||||
|
assert_eq!(format!("{}", SpecType::Goerli), "goerli");
|
||||||
|
assert_eq!(format!("{}", SpecType::Kotti), "kotti");
|
||||||
assert_eq!(format!("{}", SpecType::Sokol), "sokol");
|
assert_eq!(format!("{}", SpecType::Sokol), "sokol");
|
||||||
assert_eq!(format!("{}", SpecType::Dev), "dev");
|
assert_eq!(format!("{}", SpecType::Dev), "dev");
|
||||||
assert_eq!(format!("{}", SpecType::Custom("foo/bar".into())), "foo/bar");
|
assert_eq!(format!("{}", SpecType::Custom("foo/bar".into())), "foo/bar");
|
||||||
|
@ -19,6 +19,7 @@ track = "nightly"
|
|||||||
foundation = { forkBlock = 7280000, critical = false }
|
foundation = { forkBlock = 7280000, critical = false }
|
||||||
ropsten = { forkBlock = 4939394, critical = false }
|
ropsten = { forkBlock = 4939394, critical = false }
|
||||||
kovan = { forkBlock = 10255201, critical = false }
|
kovan = { forkBlock = 10255201, critical = false }
|
||||||
|
goerli = { forkBlock = 0, critical = false }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
parity-bytes = "0.1"
|
parity-bytes = "0.1"
|
||||||
|
Loading…
Reference in New Issue
Block a user