Chain creation and network simulation start script
This commit is contained in:
parent
0b0c66ebd9
commit
abb199d6dc
|
@ -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"
|
||||
|
||||
|
|
|
@ -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,12 +42,35 @@ 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:
|
||||
if node.config.stateSnapshot.isSome:
|
||||
node.beaconState = node.config.stateSnapshot.get
|
||||
else:
|
||||
node.beaconState = await obtainTrustedStateSnapshot(node.db)
|
||||
else:
|
||||
node.beaconState = persistedState[]
|
||||
|
@ -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,10 +219,33 @@ 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()
|
||||
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
|
||||
|
||||
of noCommand:
|
||||
waitFor syncrhronizeClock()
|
||||
createPidFile(string(config.dataDir) / "beacon_node.pid")
|
||||
|
||||
var node = BeaconNode.init config
|
||||
waitFor node.connectToNetwork()
|
||||
|
||||
if not waitFor node.sync():
|
||||
quit 1
|
||||
|
|
|
@ -1,18 +1,46 @@
|
|||
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
|
||||
case cmd* {.
|
||||
command
|
||||
defaultValue: noCommand.}: StartUpCommand
|
||||
|
||||
of noCommand:
|
||||
dataDir* {.
|
||||
desc: "The directory where nimbus will store all blockchain data.",
|
||||
shorthand: "d",
|
||||
desc: "The directory where nimbus will store all blockchain data."
|
||||
shortform: "d"
|
||||
defaultValue: getConfigDir() / "nimbus".}: DirPath
|
||||
|
||||
bootstrapNodes* {.
|
||||
desc: "Specifies one or more bootstrap nodes to use when connecting to the network.",
|
||||
shorthand: "b".}: seq[string]
|
||||
desc: "Specifies one or more bootstrap nodes to use when connecting to the network."
|
||||
longform: "bootstrapNode"
|
||||
shortform: "b".}: seq[string]
|
||||
|
||||
bootstrapNodesFile* {.
|
||||
desc: "Specifies a line-delimited file of bootsrap Ethereum network addresses"
|
||||
shortform: "f"
|
||||
defaultValue: "".}: FilePath
|
||||
|
||||
tcpPort* {.
|
||||
desc: "TCP listening port".}: int
|
||||
|
@ -20,10 +48,26 @@ type
|
|||
udpPort* {.
|
||||
desc: "UDP listening port".}: int
|
||||
|
||||
validatorKeys* {.
|
||||
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.",
|
||||
shorthand: "v".}: seq[ValidatorKeyPath]
|
||||
"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
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import spec/[digest, helpers]
|
||||
|
||||
type Randao* = object
|
||||
seed: Eth2Digest
|
||||
seed*: Eth2Digest
|
||||
|
||||
const MaxRandaoLevels = 10000 # TODO: This number is arbitrary
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
*.json
|
||||
|
|
@ -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
|
||||
|
|
@ -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 =
|
||||
|
|
Loading…
Reference in New Issue