2018-11-23 23:58:49 +00:00
|
|
|
import
|
2018-12-05 13:58:41 +00:00
|
|
|
os, net, sequtils,
|
2018-11-29 01:08:34 +00:00
|
|
|
asyncdispatch2, chronicles, confutils, eth_p2p, eth_keys,
|
2018-12-05 13:58:41 +00:00
|
|
|
spec/[beaconstate, datatypes, helpers, crypto], conf, time, fork_choice,
|
2018-11-26 13:33:06 +00:00
|
|
|
beacon_chain_db, validator_pool, mainchain_monitor,
|
|
|
|
sync_protocol, gossipsub_protocol, trusted_state_snapshots
|
2018-11-23 23:58:49 +00:00
|
|
|
|
|
|
|
type
|
|
|
|
BeaconNode* = ref object
|
|
|
|
beaconState*: BeaconState
|
|
|
|
network*: EthereumNode
|
|
|
|
db*: BeaconChainDB
|
2018-11-29 01:08:34 +00:00
|
|
|
config*: BeaconNodeConf
|
2018-11-23 23:58:49 +00:00
|
|
|
keys*: KeyPair
|
2018-11-26 13:33:06 +00:00
|
|
|
attachedValidators: ValidatorPool
|
|
|
|
attestations: AttestationPool
|
|
|
|
headBlock: BeaconBlock
|
|
|
|
mainchainMonitor: MainchainMonitor
|
2018-11-23 23:58:49 +00:00
|
|
|
|
|
|
|
const
|
2018-11-29 01:08:34 +00:00
|
|
|
version = "v0.1" # TODO: read this from the nimble file
|
2018-11-23 23:58:49 +00:00
|
|
|
clientId = "nimbus beacon node " & version
|
2018-11-26 13:33:06 +00:00
|
|
|
|
|
|
|
topicBeaconBlocks = "ethereum/2.1/beacon_chain/blocks"
|
|
|
|
topicAttestations = "ethereum/2.1/beacon_chain/attestations"
|
2018-11-23 23:58:49 +00:00
|
|
|
|
|
|
|
proc ensureNetworkKeys*(dataDir: string): KeyPair =
|
|
|
|
# TODO:
|
|
|
|
# 1. Check if keys already exist in the data dir
|
|
|
|
# 2. Generate new ones and save them in the directory
|
|
|
|
# if necessary
|
|
|
|
return newKeyPair()
|
|
|
|
|
2018-11-29 01:08:34 +00:00
|
|
|
proc init*(T: type BeaconNode, conf: BeaconNodeConf): T =
|
2018-11-23 23:58:49 +00:00
|
|
|
new result
|
|
|
|
result.config = conf
|
2018-11-29 01:08:34 +00:00
|
|
|
result.db = BeaconChainDB.init(string conf.dataDir)
|
|
|
|
result.keys = ensureNetworkKeys(string conf.dataDir)
|
2018-11-23 23:58:49 +00:00
|
|
|
|
|
|
|
var address: Address
|
|
|
|
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)
|
|
|
|
|
|
|
|
proc sync*(node: BeaconNode): Future[bool] {.async.} =
|
|
|
|
let persistedState = node.db.lastFinalizedState()
|
|
|
|
if persistedState.isNil or
|
2018-11-29 01:08:34 +00:00
|
|
|
persistedState[].slotDistanceFromNow() > WEAK_SUBJECTVITY_PERIOD:
|
2018-11-23 23:58:49 +00:00
|
|
|
node.beaconState = await obtainTrustedStateSnapshot(node.db)
|
|
|
|
else:
|
|
|
|
node.beaconState = persistedState[]
|
|
|
|
var targetSlot = toSlot timeSinceGenesis(node.beaconState)
|
|
|
|
|
2018-12-03 21:41:24 +00:00
|
|
|
while node.beaconState.finalized_slot.int < targetSlot:
|
2018-11-29 01:08:34 +00:00
|
|
|
var (peer, changeLog) = await node.network.getValidatorChangeLog(
|
2018-12-03 21:41:24 +00:00
|
|
|
node.beaconState.validator_registry_delta_chain_tip)
|
2018-11-23 23:58:49 +00:00
|
|
|
|
|
|
|
if peer == nil:
|
|
|
|
error "Failed to sync with any peer"
|
|
|
|
return false
|
|
|
|
|
|
|
|
if applyValidatorChangeLog(changeLog, node.beaconState):
|
2018-11-29 01:08:34 +00:00
|
|
|
node.db.persistBlock(node.beaconState, changeLog.signedBlock)
|
2018-11-23 23:58:49 +00:00
|
|
|
else:
|
|
|
|
warn "Ignoring invalid validator change log", sentFrom = peer
|
|
|
|
|
|
|
|
return true
|
|
|
|
|
2018-12-05 13:58:41 +00:00
|
|
|
template findIt(s: openarray, predicate: untyped): int =
|
|
|
|
var res = -1
|
|
|
|
for i, it {.inject.} in s:
|
|
|
|
if predicate:
|
|
|
|
res = i
|
|
|
|
break
|
|
|
|
res
|
|
|
|
|
2018-11-23 23:58:49 +00:00
|
|
|
proc addLocalValidators*(node: BeaconNode) =
|
|
|
|
for validator in node.config.validatorKeys:
|
|
|
|
# 1. Parse the validator keys
|
2018-12-05 13:58:41 +00:00
|
|
|
let privKey = loadPrivKey(validator)
|
|
|
|
let pubKey = privKey.pubKey()
|
|
|
|
|
2018-11-23 23:58:49 +00:00
|
|
|
# 2. Check whether the validators exist in the beacon state.
|
|
|
|
# (Report a warning otherwise)
|
2018-12-05 13:58:41 +00:00
|
|
|
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, @[])
|
|
|
|
|
2018-11-23 23:58:49 +00:00
|
|
|
|
2018-11-26 13:33:06 +00:00
|
|
|
proc getAttachedValidator(node: BeaconNode, idx: int): AttachedValidator =
|
2018-12-03 21:41:24 +00:00
|
|
|
let validatorKey = node.beaconState.validator_registry[idx].pubkey
|
2018-11-26 13:33:06 +00:00
|
|
|
return node.attachedValidators.getValidator(validatorKey)
|
|
|
|
|
2018-11-29 01:08:34 +00:00
|
|
|
proc makeAttestation(node: BeaconNode,
|
|
|
|
validator: AttachedValidator) {.async.} =
|
|
|
|
var attestation: Attestation
|
|
|
|
attestation.validator = validator.idx
|
2018-11-26 13:33:06 +00:00
|
|
|
|
2018-11-29 01:08:34 +00:00
|
|
|
# TODO: Populate attestation.data
|
2018-11-26 13:33:06 +00:00
|
|
|
|
2018-11-29 01:08:34 +00:00
|
|
|
attestation.signature = await validator.signAttestation(attestation.data)
|
|
|
|
await node.network.broadcast(topicAttestations, attestation)
|
2018-11-26 13:33:06 +00:00
|
|
|
|
2018-11-29 01:08:34 +00:00
|
|
|
proc proposeBlock(node: BeaconNode,
|
|
|
|
validator: AttachedValidator,
|
|
|
|
slot: int) {.async.} =
|
|
|
|
var proposal: BeaconBlock
|
2018-11-26 13:33:06 +00:00
|
|
|
|
2018-11-29 01:08:34 +00:00
|
|
|
# TODO:
|
|
|
|
# 1. Produce a RANDAO reveal from attachedVadalidator.randaoSecret
|
|
|
|
# and its matching ValidatorRecord.
|
2018-11-26 13:33:06 +00:00
|
|
|
|
2018-11-29 01:08:34 +00:00
|
|
|
# 2. Get ancestors from the beacon_db
|
2018-11-26 13:33:06 +00:00
|
|
|
|
2018-11-29 01:08:34 +00:00
|
|
|
# 3. Calculate the correct state hash
|
2018-11-26 13:33:06 +00:00
|
|
|
|
2018-11-29 01:08:34 +00:00
|
|
|
proposal.candidate_pow_receipt_root =
|
|
|
|
node.mainchainMonitor.getBeaconBlockRef()
|
2018-11-26 13:33:06 +00:00
|
|
|
|
2018-11-29 01:08:34 +00:00
|
|
|
for a in node.attestations.each(firstSlot = node.headBlock.slot.int + 1,
|
|
|
|
lastSlot = slot - MIN_ATTESTATION_INCLUSION_DELAY):
|
|
|
|
# TODO: this is not quite right,
|
|
|
|
# the attestations from individual validators have to be merged.
|
|
|
|
# proposal.attestations.add a
|
|
|
|
discard
|
2018-11-26 13:33:06 +00:00
|
|
|
|
2018-11-29 01:08:34 +00:00
|
|
|
for r in node.mainchainMonitor.getValidatorActions(
|
|
|
|
node.headBlock.candidate_pow_receipt_root,
|
|
|
|
proposal.candidate_pow_receipt_root):
|
|
|
|
proposal.specials.add r
|
2018-11-26 13:33:06 +00:00
|
|
|
|
2018-11-29 01:08:34 +00:00
|
|
|
var signedData: ProposalSignedData
|
|
|
|
# TODO: populate the signed data
|
2018-11-26 13:33:06 +00:00
|
|
|
|
2018-11-29 01:08:34 +00:00
|
|
|
proposal.proposer_signature = await validator.signBlockProposal(signedData)
|
|
|
|
await node.network.broadcast(topicBeaconBlocks, proposal)
|
2018-11-26 13:33:06 +00:00
|
|
|
|
2018-11-29 01:08:34 +00:00
|
|
|
proc scheduleCycleActions(node: BeaconNode) =
|
2018-11-23 23:58:49 +00:00
|
|
|
## This schedules the required block proposals and
|
|
|
|
## attestations from our attached validators.
|
2018-12-03 21:41:24 +00:00
|
|
|
let cycleStart = node.beaconState.latest_state_recalculation_slot.int
|
2018-11-23 23:58:49 +00:00
|
|
|
|
2018-12-03 17:46:22 +00:00
|
|
|
for i in 0 ..< EPOCH_LENGTH:
|
2018-11-26 13:33:06 +00:00
|
|
|
# Schedule block proposals
|
2018-11-23 23:58:49 +00:00
|
|
|
let
|
2018-11-26 13:33:06 +00:00
|
|
|
slot = cycleStart + i
|
2018-11-29 05:23:40 +00:00
|
|
|
proposerIdx = get_beacon_proposer_index(node.beaconState, slot.uint64)
|
|
|
|
attachedValidator = node.getAttachedValidator(proposerIdx.int)
|
2018-11-23 23:58:49 +00:00
|
|
|
|
2018-11-26 13:33:06 +00:00
|
|
|
if attachedValidator != nil:
|
2018-11-23 23:58:49 +00:00
|
|
|
# TODO:
|
|
|
|
# Warm-up the proposer earlier to try to obtain previous
|
|
|
|
# missing blocks if necessary
|
|
|
|
|
2018-11-29 01:08:34 +00:00
|
|
|
addTimer(node.beaconState.slotStart(slot)) do (p: pointer):
|
|
|
|
asyncCheck proposeBlock(node, attachedValidator, slot)
|
2018-11-23 23:58:49 +00:00
|
|
|
|
2018-11-26 13:33:06 +00:00
|
|
|
# Schedule attestations
|
|
|
|
let
|
2018-12-03 21:41:24 +00:00
|
|
|
committeesIdx = get_shard_and_committees_index(node.beaconState, slot.uint64)
|
2018-11-26 13:33:06 +00:00
|
|
|
|
2018-11-29 01:08:34 +00:00
|
|
|
for shard in node.beaconState.shard_and_committee_for_slots[committees_idx]:
|
|
|
|
for validatorIdx in shard.committee:
|
|
|
|
let attachedValidator = node.getAttachedValidator(validatorIdx)
|
|
|
|
if attachedValidator != nil:
|
|
|
|
addTimer(node.beaconState.slotMiddle(slot)) do (p: pointer):
|
|
|
|
asyncCheck makeAttestation(node, attachedValidator)
|
2018-11-23 23:58:49 +00:00
|
|
|
|
|
|
|
proc processBlocks*(node: BeaconNode) {.async.} =
|
|
|
|
node.scheduleCycleActions()
|
|
|
|
|
|
|
|
node.network.subscribe(topicBeaconBlocks) do (b: BeaconBlock):
|
|
|
|
# TODO:
|
|
|
|
#
|
|
|
|
# 1. Check for missing blocks and obtain them
|
2018-11-26 13:33:06 +00:00
|
|
|
#
|
|
|
|
# 2. Apply fork-choice rule (update node.headBlock)
|
|
|
|
#
|
|
|
|
# 3. Peform block processing / state recalculation / etc
|
|
|
|
#
|
2018-11-23 23:58:49 +00:00
|
|
|
|
2018-12-03 17:46:22 +00:00
|
|
|
if b.slot mod EPOCH_LENGTH == 0:
|
2018-11-23 23:58:49 +00:00
|
|
|
node.scheduleCycleActions()
|
2018-11-26 13:33:06 +00:00
|
|
|
node.attestations.discardHistoryToSlot(b.slot)
|
|
|
|
|
|
|
|
node.network.subscribe(topicAttestations) do (a: Attestation):
|
|
|
|
# TODO
|
|
|
|
#
|
|
|
|
# 1. Validate the attestation
|
|
|
|
|
|
|
|
node.attestations.add(a, node.beaconState)
|
2018-11-23 23:58:49 +00:00
|
|
|
|
|
|
|
when isMainModule:
|
2018-11-29 01:08:34 +00:00
|
|
|
let config = BeaconNodeConf.load()
|
2018-11-23 23:58:49 +00:00
|
|
|
waitFor syncrhronizeClock()
|
2018-11-29 01:08:34 +00:00
|
|
|
var node = BeaconNode.init config
|
2018-11-23 23:58:49 +00:00
|
|
|
|
|
|
|
if not waitFor node.sync():
|
|
|
|
quit 1
|
|
|
|
|
|
|
|
node.addLocalValidators()
|
|
|
|
|
|
|
|
waitFor node.processBlocks()
|
|
|
|
|