Chain creation and network simulation start script

This commit is contained in:
Zahary Karadjov 2018-12-19 14:58:53 +02:00 committed by zah
parent 0b0c66ebd9
commit abb199d6dc
13 changed files with 297 additions and 70 deletions

View File

@ -16,13 +16,16 @@ proc init*(T: type BeaconChainDB, dataDir: string): BeaconChainDB =
proc lastFinalizedState*(db: BeaconChainDB): BeaconStateRef =
try:
var stateJson = parseJson readFile(db.dataRoot / "BeaconState.json")
# TODO implement this
let stateFile = db.dataRoot / "BeaconState.json"
if fileExists stateFile:
new result
Json.loadFile(stateFile, result[])
except:
error "Failed to load the latest finalized state",
err = getCurrentExceptionMsg()
return nil
proc persistBlock*(db: BeaconChainDB, s: BeaconState, b: BeaconBlock) =
let stateJson = StringJsonWriter.encode(s, pretty = true)
writeFile(db.dataRoot / "BeaconState.json", stateJson)
Json.saveFile(db.dataRoot / "BeaconState.json", s, pretty = true)
debug "State persisted"

View File

@ -1,8 +1,8 @@
import
os, net, sequtils,
std_shims/os_shims, net, sequtils, options,
asyncdispatch2, chronicles, confutils, eth_p2p, eth_keys,
spec/[beaconstate, datatypes, helpers, crypto], conf, time, fork_choice, ssz,
beacon_chain_db, validator_pool, mainchain_monitor,
spec/[beaconstate, datatypes, helpers, crypto, digest], conf, time,
fork_choice, ssz, beacon_chain_db, validator_pool, mainchain_monitor,
sync_protocol, gossipsub_protocol, trusted_state_snapshots
type
@ -34,6 +34,7 @@ proc ensureNetworkKeys*(dataDir: string): KeyPair =
proc init*(T: type BeaconNode, conf: BeaconNodeConf): T =
new result
result.config = conf
result.db = BeaconChainDB.init(string conf.dataDir)
result.keys = ensureNetworkKeys(string conf.dataDir)
@ -41,13 +42,36 @@ proc init*(T: type BeaconNode, conf: BeaconNodeConf): T =
address.ip = parseIpAddress("0.0.0.0")
address.tcpPort = Port(conf.tcpPort)
address.udpPort = Port(conf.udpPort)
result.network = newEthereumNode(result.keys, address, 0, nil, clientId)
writeFile(string(conf.dataDir) / "beacon_node.address",
$result.network.listeningAddress)
proc connectToNetwork(node: BeaconNode) {.async.} =
var bootstrapNodes = newSeq[ENode]()
for node in node.config.bootstrapNodes:
bootstrapNodes.add initENode(node)
let bootstrapFile = string node.config.bootstrapNodesFile
if bootstrapFile.len > 0:
for ln in lines(bootstrapFile):
bootstrapNodes.add initENode(string ln)
if bootstrapNodes.len > 0:
await node.network.connectToNetwork(bootstrapNodes)
else:
node.network.startListening()
proc sync*(node: BeaconNode): Future[bool] {.async.} =
let persistedState = node.db.lastFinalizedState()
if persistedState.isNil or
persistedState[].slotDistanceFromNow() > WEAK_SUBJECTVITY_PERIOD:
node.beaconState = await obtainTrustedStateSnapshot(node.db)
if node.config.stateSnapshot.isSome:
node.beaconState = node.config.stateSnapshot.get
else:
node.beaconState = await obtainTrustedStateSnapshot(node.db)
else:
node.beaconState = persistedState[]
var targetSlot = toSlot timeSinceGenesis(node.beaconState)
@ -76,22 +100,19 @@ template findIt(s: openarray, predicate: untyped): int =
res
proc addLocalValidators*(node: BeaconNode) =
for validator in node.config.validatorKeys:
# 1. Parse the validator keys
let privKey = loadPrivKey(validator)
let pubKey = privKey.pubKey()
let randao = loadRandao(validator)
for validator in node.config.validators:
let
privKey = validator.privKey
pubKey = privKey.pubKey()
randao = validator.randao
# 2. Check whether the validators exist in the beacon state.
# (Report a warning otherwise)
let idx = node.beaconState.validator_registry.findIt(it.pubKey == pubKey)
if idx == -1:
warn "Validator not in registry", pubKey
else:
# 3. Add the validators to node.attachedValidators
# TODO: Parse randao secret
node.attachedValidators.addLocalValidator(idx, pubKey, privKey, randao)
info "Local validators attached ", count = node.attachedValidators.count
proc getAttachedValidator(node: BeaconNode, idx: int): AttachedValidator =
let validatorKey = node.beaconState.validator_registry[idx].pubkey
@ -198,15 +219,38 @@ proc processBlocks*(node: BeaconNode) {.async.} =
# Attestations are verified as aggregated groups
node.attestations.add(getAttestationCandidate a, node.beaconState)
var gPidFile: string
proc createPidFile(filename: string) =
createDir splitFile(filename).dir
writeFile filename, $getCurrentProcessId()
gPidFile = filename
addQuitProc proc {.noconv.} = removeFile gPidFile
when isMainModule:
let config = BeaconNodeConf.load()
waitFor syncrhronizeClock()
var node = BeaconNode.init config
case config.cmd
of createChain:
let outfile = string config.outputStateFile
let initialState = get_initial_beacon_state(
config.chainStartupData.validatorDeposits,
config.chainStartupData.genesisTime,
Eth2Digest())
Json.saveFile(outfile, initialState, pretty = true)
echo "Wrote ", outfile
quit 0
if not waitFor node.sync():
quit 1
of noCommand:
waitFor syncrhronizeClock()
createPidFile(string(config.dataDir) / "beacon_node.pid")
node.addLocalValidators()
var node = BeaconNode.init config
waitFor node.connectToNetwork()
waitFor node.processBlocks()
if not waitFor node.sync():
quit 1
node.addLocalValidators()
waitFor node.processBlocks()

View File

@ -1,29 +1,73 @@
import
confutils/defs, spec/crypto, milagro_crypto, randao
os, options,
confutils/defs, milagro_crypto, json_serialization,
spec/[crypto, datatypes], randao, time
export
json_serialization
type
ValidatorKeyPath* = distinct string
StartUpCommand* = enum
noCommand
createChain
ChainStartupData* = object
validatorDeposits*: seq[Deposit]
genesisTime*: Timestamp
PrivateValidatorData* = object
privKey*: ValidatorPrivKey
randao*: Randao
BeaconNodeConf* = object
dataDir* {.
desc: "The directory where nimbus will store all blockchain data.",
shorthand: "d",
defaultValue: getConfigDir() / "nimbus".}: DirPath
case cmd* {.
command
defaultValue: noCommand.}: StartUpCommand
bootstrapNodes* {.
desc: "Specifies one or more bootstrap nodes to use when connecting to the network.",
shorthand: "b".}: seq[string]
of noCommand:
dataDir* {.
desc: "The directory where nimbus will store all blockchain data."
shortform: "d"
defaultValue: getConfigDir() / "nimbus".}: DirPath
tcpPort* {.
desc: "TCP listening port".}: int
bootstrapNodes* {.
desc: "Specifies one or more bootstrap nodes to use when connecting to the network."
longform: "bootstrapNode"
shortform: "b".}: seq[string]
udpPort* {.
desc: "UDP listening port".}: int
bootstrapNodesFile* {.
desc: "Specifies a line-delimited file of bootsrap Ethereum network addresses"
shortform: "f"
defaultValue: "".}: FilePath
validatorKeys* {.
desc: "A path to a pair of public and private keys for a validator. " &
"Nimbus will automatically add the extensions .privkey and .pubkey.",
shorthand: "v".}: seq[ValidatorKeyPath]
tcpPort* {.
desc: "TCP listening port".}: int
udpPort* {.
desc: "UDP listening port".}: int
validators* {.
required
desc: "A path to a pair of public and private keys for a validator. " &
"Nimbus will automatically add the extensions .privkey and .pubkey."
longform: "validator"
shortform: "v".}: seq[PrivateValidatorData]
stateSnapshot* {.
desc: "Json file specifying a recent state snapshot"
shortform: "s".}: Option[BeaconState]
of createChain:
chainStartupData* {.
desc: ""
shortform: "c".}: ChainStartupData
outputStateFile* {.
desc: "Output file where to write the initial state snapshot"
longform: "out"
shortform: "o".}: OutFilePath
proc readFileBytes(path: string): seq[byte] =
cast[seq[byte]](readFile(path))
@ -34,8 +78,21 @@ proc loadPrivKey*(p: ValidatorKeyPath): ValidatorPrivKey =
proc loadRandao*(p: ValidatorKeyPath): Randao =
initRandao(readFileBytes(string(p) & ".randao"))
proc parse*(T: type ValidatorKeyPath, input: TaintedString): T =
proc parseCmdArg*(T: type ValidatorKeyPath, input: TaintedString): T =
result = T(input)
discard loadPrivKey(result)
discard loadRandao(result)
template mustBeFilePath(input: TaintedString) =
if not fileExists(string input):
raise newException(ValueError, "")
template handledAsJsonFilename(T: untyped) {.dirty.} =
proc parseCmdArg*(_: type T, input: TaintedString): T =
input.mustBeFilePath
return Json.loadFile(string(input), T)
handledAsJsonFilename BeaconState
handledAsJsonFilename ChainStartupData
handledAsJsonFilename PrivateValidatorData

View File

@ -1,7 +1,7 @@
import spec/[digest, helpers]
type Randao* = object
seed: Eth2Digest
seed*: Eth2Digest
const MaxRandaoLevels = 10000 # TODO: This number is arbitrary

View File

@ -10,8 +10,11 @@
# hashed out. This layer helps isolate those chagnes.
import
milagro_crypto, hashes
export milagro_crypto.`$`
hashes,
milagro_crypto, json_serialization
export
json_serialization, milagro_crypto.`$`
type
ValidatorPubKey* = milagro_crypto.VerKey
@ -38,3 +41,22 @@ func bls_verify*(
# name from spec!
# TODO domain!
sig.verifyMessage(msg, pubkey)
proc writeValue*(writer: var JsonWriter, value: ValidatorPubKey) {.inline.} =
writer.writeValue $value
proc readValue*(reader: var JsonReader, value: var ValidatorPubKey) {.inline.} =
value = initVerKey reader.readValue(string)
proc writeValue*(writer: var JsonWriter, value: ValidatorSig) {.inline.} =
writer.writeValue $value
proc readValue*(reader: var JsonReader, value: var ValidatorSig) {.inline.} =
value = initSignature reader.readValue(string)
proc writeValue*(writer: var JsonWriter, value: ValidatorPrivKey) {.inline.} =
writer.writeValue $value
proc readValue*(reader: var JsonReader, value: var ValidatorPrivKey) {.inline.} =
value = initSigKey reader.readValue(string)

View File

@ -392,8 +392,8 @@ type
when true:
# TODO: Remove these once RLP serialization is no longer used
import nimcrypto, rlp
export append, read
import nimcrypto, rlp, json_serialization
export append, read, json_serialization
proc append*(rlpWriter: var RlpWriter, value: ValidatorPubKey) =
discard
@ -412,3 +412,5 @@ when true:
proc read*(rlp: var Rlp, T: type ValidatorSig): T {.inline.} =
discard

View File

@ -20,9 +20,11 @@
# In our code base, to enable a smooth transition, we call this function
# `eth2hash`, and it outputs a `Eth2Digest`. Easy to sed :)
import nimcrypto/[blake2, hash]
import
nimcrypto/[blake2, hash], eth_common/eth_types_json_serialization
export hash.`$`
export
eth_types_json_serialization, hash.`$`
type
Eth2Digest* = MDigest[32 * 8] ## `hash32` from spec

View File

@ -4,11 +4,13 @@ import
spec/datatypes
type
Timestamp = uint64 # Unix epoch timestamp in millisecond resolution
Timestamp* = uint64 # Unix epoch timestamp in millisecond resolution
var
detectedClockDrift: int64
template now*: auto = fastEpochTime()
proc timeSinceGenesis*(s: BeaconState): Timestamp =
Timestamp(int64(fastEpochTime() - s.genesis_time * 1000) -
detectedClockDrift)

View File

@ -1,36 +1,73 @@
import os, ospaths, milagro_crypto, nimcrypto, ./spec/digest
import
os, ospaths, strutils, strformat,
milagro_crypto, nimcrypto, json_serialization,
spec/[datatypes, digest, crypto], conf, randao, time, ssz,
../tests/testutil
proc writeFile(filename: string, content: openarray[byte]) =
var s = newString(content.len)
if content.len != 0:
copyMem(addr s[0], unsafeAddr content[0], content.len)
writeFile(filename, s)
proc writeFile(filename: string, value: auto) =
Json.saveFile(filename, value, pretty = true)
echo &"Wrote {filename}"
proc genKeys(path: string) =
let pk = newSigKey()
var randaoSeed: Eth2Digest
if randomBytes(randaoSeed.data) != sizeof(randaoSeed.data):
proc genSingleValidator(path: string): (ValidatorPubKey,
ValidatorPrivKey,
Eth2Digest) =
var v: PrivateValidatorData
v.privKey = newSigKey()
if randomBytes(v.randao.seed.data) != sizeof(v.randao.seed.data):
raise newException(Exception, "Could not generate randao seed")
createDir(parentDir(path))
let pkPath = path & ".privkey"
let randaoPath = path & ".randao"
writeFile(randaoPath, randaoSeed.data)
writeFile(pkPath, pk.getRaw())
echo "Generated privkey: ", pkPath
echo "Generated randao seed: ", randaoPath
writeFile(path, v)
return (v.privKey.pubKey(), v.privKey, v.randao.initialCommitment)
proc printUsage() =
echo "Usage: validator_keygen <path>"
echo "Usage: validator_keygen <number-of-validators> <out-path>"
# TODO: Make these more comprehensive and find them a new home
type
Ether* = distinct int64
GWei* = distinct int64
template eth*(x: SomeInteger): Ether = Ether(x)
template gwei*(x: Ether): Gwei = Gwei(int(x) * 1000000000)
proc main() =
if paramCount() != 1:
if paramCount() != 2:
printUsage()
return
let path = paramStr(1)
genKeys(path)
let totalValidators = parseInt paramStr(1)
if totalValidators < 64:
echo "The number of validators must be higher than ", EPOCH_LENGTH, " (EPOCH_LENGTH)"
echo "There must be at least one validator assigned per slot."
quit 1
let outPath = paramStr(2)
var startupData: ChainStartupData
for i in 1 .. totalValidators:
let (pubKey, privKey, randaoCommitment) =
genSingleValidator(outPath / &"validator-{i:02}.json")
let withdrawalCredentials = makeFakeHash(i)
let proofOfPossession = signMessage(privkey, hash_tree_root(
(pubKey, withdrawalCredentials, randaoCommitment)))
startupData.validatorDeposits.add Deposit(
deposit_data: DepositData(
value: MAX_DEPOSIT * GWEI_PER_ETH,
timestamp: now(),
deposit_parameters: DepositParameters(
pubkey: pubKey,
proof_of_possession: proofOfPossession,
withdrawal_credentials: withdrawalCredentials,
randao_commitment: randaoCommitment)))
startupData.genesisTime = now()
writeFile(outPath / "startup.json", startupData)
when isMainModule:
main()

View File

@ -25,6 +25,9 @@ type
proc init*(T: type ValidatorPool): T =
result.validators = initTable[ValidatorPubKey, AttachedValidator]()
template count*(pool: ValidatorPool): int =
pool.validators.len
proc addLocalValidator*(pool: var ValidatorPool,
idx: int,
pubKey: ValidatorPubKey,

2
tests/simulation/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*.json

53
tests/simulation/start.sh Executable file
View File

@ -0,0 +1,53 @@
#!/bin/bash
set -eu
NUMBER_OF_VALIDATORS=99
cd $(dirname "$0")
SIMULATION_DIR=$PWD
STARTUP_FILE="$SIMULATION_DIR/startup.json"
SNAPSHOT_FILE="$SIMULATION_DIR/state_snapshot.json"
cd $(git rev-parse --show-toplevel)
ROOT_DIR=$PWD
nim c beacon_chain/validator_keygen
nim c beacon_chain/beacon_node
if [ ! -f $STARTUP_FILE ]; then
beacon_chain/validator_keygen $NUMBER_OF_VALIDATORS "$SIMULATION_DIR"
fi
if [ ! -f $SNAPSHOT_FILE ]; then
beacon_chain/beacon_node createChain \
--chainStartupData:$STARTUP_FILE \
--out:$SNAPSHOT_FILE
fi
for i in $(seq 0 9); do
DATA_DIR=$SIMULATION_DIR/data-$i
BOOTSTRAP_NODES_FLAG=--bootstrapNodesFile:"$DATA_DIR/beacon_node.address"
if [[ "$i" == "0" ]]; then
BOOTSTRAP_NODES_FLAG=""
fi
beacon_chain/beacon_node \
--dataDir:"$DATA_DIR" \
--validator:"$SIMULATION_DIR/validator-${i}1.json" \
--validator:"$SIMULATION_DIR/validator-${i}2.json" \
--validator:"$SIMULATION_DIR/validator-${i}3.json" \
--validator:"$SIMULATION_DIR/validator-${i}4.json" \
--validator:"$SIMULATION_DIR/validator-${i}5.json" \
--validator:"$SIMULATION_DIR/validator-${i}6.json" \
--validator:"$SIMULATION_DIR/validator-${i}7.json" \
--validator:"$SIMULATION_DIR/validator-${i}8.json" \
--validator:"$SIMULATION_DIR/validator-${i}9.json" \
--tcpPort:5000$i \
--udpPort:5000$i \
--stateSnapshot:"$SNAPSHOT_FILE" \
$BOOTSTRAP_NODES_FLAG &
done

View File

@ -17,7 +17,7 @@ func makeValidatorPrivKey(i: int): ValidatorPrivKey =
var i = i + 1 # 0 does not work, as private key...
copyMem(result.x[0].addr, i.addr, min(sizeof(result.x), sizeof(i)))
func makeFakeHash(i: int): Eth2Digest =
func makeFakeHash*(i: int): Eth2Digest =
copyMem(result.data[0].addr, i.unsafeAddr, min(sizeof(result.data), sizeof(i)))
func hackPrivKey(v: ValidatorRecord): ValidatorPrivKey =