2018-11-23 23:58:49 +00:00
|
|
|
import
|
2019-02-18 10:34:39 +00:00
|
|
|
options, tables,
|
2019-02-06 17:56:04 +00:00
|
|
|
chronicles, eth/[rlp, p2p], chronos, ranges/bitranges, eth/p2p/rlpx,
|
2019-02-18 10:34:39 +00:00
|
|
|
spec/[datatypes, crypto, digest],
|
|
|
|
beacon_node, beacon_chain_db, time, ssz
|
2018-11-23 23:58:49 +00:00
|
|
|
|
|
|
|
type
|
|
|
|
ValidatorChangeLogEntry* = object
|
|
|
|
case kind*: ValidatorSetDeltaFlags
|
2018-12-03 17:46:22 +00:00
|
|
|
of Activation:
|
2018-11-29 01:08:34 +00:00
|
|
|
pubkey: ValidatorPubKey
|
2018-11-23 23:58:49 +00:00
|
|
|
else:
|
|
|
|
index: uint32
|
|
|
|
|
2019-01-17 18:27:11 +00:00
|
|
|
ValidatorSet = seq[Validator]
|
2018-11-23 23:58:49 +00:00
|
|
|
|
2019-02-18 10:34:39 +00:00
|
|
|
BeaconSyncState* = ref object
|
|
|
|
node*: BeaconNode
|
|
|
|
db*: BeaconChainDB
|
|
|
|
|
|
|
|
func toHeader(b: BeaconBlock): BeaconBlockHeader =
|
2019-03-01 20:09:20 +00:00
|
|
|
BeaconBlockHeader(
|
|
|
|
slot: b.slot,
|
|
|
|
parent_root: b.parent_root,
|
|
|
|
state_root: b.state_root,
|
|
|
|
randao_reveal: b.randao_reveal,
|
|
|
|
eth1_data : b.eth1_data,
|
|
|
|
signature: b.signature,
|
|
|
|
body: hash_tree_root_final(b.body)
|
|
|
|
)
|
2019-02-18 10:34:39 +00:00
|
|
|
|
|
|
|
proc fromHeader(b: var BeaconBlock, h: BeaconBlockHeader) =
|
|
|
|
b.slot = h.slot
|
|
|
|
b.parent_root = h.parent_root
|
|
|
|
b.state_root = h.state_root
|
|
|
|
b.randao_reveal = h.randao_reveal
|
|
|
|
b.eth1_data = h.eth1_data
|
|
|
|
b.signature = h.signature
|
|
|
|
|
|
|
|
proc importBlocks(node: BeaconNode, roots: openarray[(Eth2Digest, uint64)], headers: openarray[BeaconBlockHeader], bodies: openarray[BeaconBlockBody]) =
|
|
|
|
var bodyMap = initTable[Eth2Digest, int]()
|
|
|
|
|
|
|
|
for i, b in bodies:
|
|
|
|
bodyMap[hash_tree_root_final(b)] = i
|
|
|
|
|
|
|
|
var goodBlocks, badBlocks = 0
|
|
|
|
for h in headers:
|
|
|
|
let iBody = bodyMap.getOrDefault(h.body, -1)
|
|
|
|
if iBody >= 0:
|
|
|
|
var blk: BeaconBlock
|
|
|
|
blk.fromHeader(h)
|
|
|
|
blk.body = bodies[iBody]
|
|
|
|
node.onBeaconBlock(blk)
|
|
|
|
inc goodBlocks
|
|
|
|
else:
|
|
|
|
inc badBlocks
|
|
|
|
|
|
|
|
info "Forward sync imported blocks", goodBlocks, badBlocks, headers = headers.len, bodies = bodies.len, roots = roots.len
|
|
|
|
|
|
|
|
|
2018-11-29 01:08:34 +00:00
|
|
|
p2pProtocol BeaconSync(version = 1,
|
2019-02-18 10:34:39 +00:00
|
|
|
shortName = "bcs",
|
|
|
|
networkState = BeaconSyncState):
|
|
|
|
|
|
|
|
onPeerConnected do(peer: Peer):
|
|
|
|
const
|
|
|
|
protocolVersion = 1 # TODO: Spec doesn't specify this yet
|
|
|
|
networkId = 1
|
|
|
|
let node = peer.networkState.node
|
|
|
|
|
|
|
|
var
|
|
|
|
latestFinalizedRoot: Eth2Digest # TODO
|
|
|
|
latestFinalizedEpoch: uint64 = node.state.data.finalized_epoch
|
|
|
|
bestRoot: Eth2Digest # TODO
|
|
|
|
bestSlot: uint64 = node.state.data.slot
|
|
|
|
|
|
|
|
await peer.status(protocolVersion, networkId, latestFinalizedRoot, latestFinalizedEpoch,
|
|
|
|
bestRoot, bestSlot)
|
|
|
|
|
|
|
|
let m = await peer.nextMsg(BeaconSync.status)
|
|
|
|
let bestDiff = cmp((latestFinalizedEpoch, bestSlot), (m.latestFinalizedEpoch, m.bestSlot))
|
|
|
|
if bestDiff == 0:
|
|
|
|
# Nothing to do?
|
|
|
|
trace "Nothing to sync", peer = peer.node
|
|
|
|
else:
|
|
|
|
# TODO: Check for WEAK_SUBJECTIVITY_PERIOD difference and terminate the
|
|
|
|
# connection if it's too big.
|
|
|
|
let db = peer.networkState.db
|
|
|
|
|
|
|
|
if bestDiff > 0:
|
|
|
|
# Send roots
|
|
|
|
# TODO: Currently we send all block roots in one "packet". Maybe
|
|
|
|
# they should be split to multiple packets.
|
|
|
|
type Root = (Eth2Digest, uint64)
|
|
|
|
var roots = newSeqOfCap[Root](128)
|
|
|
|
for i in m.bestSlot .. bestSlot:
|
|
|
|
for r in db.getBlockRootsForSlot(i):
|
|
|
|
roots.add((r, i))
|
|
|
|
|
|
|
|
await peer.beaconBlockRoots(roots)
|
|
|
|
else:
|
|
|
|
# Receive roots
|
|
|
|
let roots = await peer.nextMsg(BeaconSync.beaconBlockRoots)
|
|
|
|
let headers = await peer.getBeaconBlockHeaders(bestRoot, bestSlot, roots.roots.len, 0)
|
|
|
|
var bodiesRequest = newSeqOfCap[Eth2Digest](roots.roots.len)
|
|
|
|
for r in roots.roots:
|
|
|
|
bodiesRequest.add(r[0])
|
|
|
|
let bodies = await peer.getBeaconBlockBodies(bodiesRequest)
|
|
|
|
node.importBlocks(roots.roots, headers.get.blockHeaders, bodies.get.blockBodies)
|
|
|
|
|
|
|
|
proc status(peer: Peer, protocolVersion, networkId: int, latestFinalizedRoot: Eth2Digest,
|
|
|
|
latestFinalizedEpoch: uint64, bestRoot: Eth2Digest, bestSlot: uint64)
|
|
|
|
|
|
|
|
proc beaconBlockRoots(peer: Peer, roots: openarray[(Eth2Digest, uint64)])
|
|
|
|
|
|
|
|
requestResponse:
|
|
|
|
proc getBeaconBlockHeaders(peer: Peer, blockRoot: Eth2Digest, slot: uint64, maxHeaders: int, skipSlots: int) =
|
|
|
|
# TODO: validate maxHeaders
|
|
|
|
var s = slot
|
|
|
|
var headers = newSeqOfCap[BeaconBlockHeader](maxHeaders)
|
|
|
|
let db = peer.networkState.db
|
|
|
|
while headers.len < maxHeaders:
|
|
|
|
let blkRoots = db.getBlockRootsForSlot(s)
|
|
|
|
for r in blkRoots:
|
|
|
|
headers.add(db.getBlock(r).get().toHeader)
|
|
|
|
if headers.len == maxHeaders: break
|
|
|
|
inc s
|
|
|
|
await peer.beaconBlockHeaders(reqId, headers)
|
|
|
|
|
|
|
|
proc beaconBlockHeaders(peer: Peer, blockHeaders: openarray[BeaconBlockHeader])
|
|
|
|
|
|
|
|
requestResponse:
|
|
|
|
proc getBeaconBlockBodies(peer: Peer, blockRoots: openarray[Eth2Digest]) =
|
|
|
|
# TODO: Validate blockRoots.len
|
|
|
|
var bodies = newSeqOfCap[BeaconBlockBody](blockRoots.len)
|
|
|
|
let db = peer.networkState.db
|
|
|
|
for r in blockRoots:
|
|
|
|
if (let blk = db.getBlock(r); blk.isSome):
|
|
|
|
bodies.add(blk.get().body)
|
|
|
|
await peer.beaconBlockBodies(reqId, bodies)
|
|
|
|
|
|
|
|
proc beaconBlockBodies(peer: Peer, blockBodies: openarray[BeaconBlockBody])
|
|
|
|
|
|
|
|
|
2018-11-23 23:58:49 +00:00
|
|
|
requestResponse:
|
2019-01-25 14:17:35 +00:00
|
|
|
proc getValidatorChangeLog(peer: Peer, changeLogHead: Eth2Digest) =
|
|
|
|
var bb: BeaconBlock
|
|
|
|
var bs: BeaconState
|
|
|
|
# TODO: get the changelog from the DB.
|
|
|
|
await peer.validatorChangeLog(reqId, bb, bs, [], [], @[])
|
2018-11-23 23:58:49 +00:00
|
|
|
|
|
|
|
proc validatorChangeLog(peer: Peer,
|
|
|
|
signedBlock: BeaconBlock,
|
|
|
|
beaconState: BeaconState,
|
2018-11-29 01:08:34 +00:00
|
|
|
added: openarray[ValidatorPubKey],
|
2018-11-23 23:58:49 +00:00
|
|
|
removed: openarray[uint32],
|
|
|
|
order: seq[byte])
|
|
|
|
|
|
|
|
type
|
|
|
|
# A bit shorter names for convenience
|
|
|
|
ChangeLog = BeaconSync.validatorChangeLog
|
|
|
|
ChangeLogEntry = ValidatorChangeLogEntry
|
|
|
|
|
2018-11-29 01:08:34 +00:00
|
|
|
func validate*(log: ChangeLog): bool =
|
|
|
|
# TODO:
|
|
|
|
# Assert that the number of raised bits in log.order (a.k.a population count)
|
|
|
|
# matches the number of elements in log.added
|
|
|
|
# https://en.wikichip.org/wiki/population_count
|
|
|
|
return true
|
|
|
|
|
|
|
|
iterator changes*(log: ChangeLog): ChangeLogEntry =
|
2018-11-23 23:58:49 +00:00
|
|
|
var
|
2018-11-29 01:08:34 +00:00
|
|
|
bits = log.added.len + log.removed.len
|
2018-11-23 23:58:49 +00:00
|
|
|
addedIdx = 0
|
|
|
|
removedIdx = 0
|
|
|
|
|
2018-11-29 01:08:34 +00:00
|
|
|
template nextItem(collection): auto =
|
|
|
|
let idx = `collection Idx`
|
|
|
|
inc `collection Idx`
|
|
|
|
log.collection[idx]
|
|
|
|
|
2018-11-23 23:58:49 +00:00
|
|
|
for i in 0 ..< bits:
|
2018-11-29 01:08:34 +00:00
|
|
|
yield if log.order.getBit(i):
|
2018-12-03 17:46:22 +00:00
|
|
|
ChangeLogEntry(kind: Activation, pubkey: nextItem(added))
|
2018-11-23 23:58:49 +00:00
|
|
|
else:
|
2018-12-11 17:55:45 +00:00
|
|
|
ChangeLogEntry(kind: ValidatorSetDeltaFlags.Exit, index: nextItem(removed))
|
2018-11-23 23:58:49 +00:00
|
|
|
|
2018-11-29 01:08:34 +00:00
|
|
|
proc getValidatorChangeLog*(node: EthereumNode, changeLogHead: Eth2Digest):
|
2018-11-23 23:58:49 +00:00
|
|
|
Future[(Peer, ChangeLog)] {.async.} =
|
|
|
|
while true:
|
2018-11-29 01:08:34 +00:00
|
|
|
let peer = node.randomPeerWith(BeaconSync)
|
2018-11-23 23:58:49 +00:00
|
|
|
if peer == nil: return
|
|
|
|
|
2018-11-29 01:08:34 +00:00
|
|
|
let res = await peer.getValidatorChangeLog(changeLogHead, timeout = 1)
|
2018-11-23 23:58:49 +00:00
|
|
|
if res.isSome:
|
|
|
|
return (peer, res.get)
|
|
|
|
|
2018-11-29 01:08:34 +00:00
|
|
|
proc applyValidatorChangeLog*(log: ChangeLog,
|
2018-11-23 23:58:49 +00:00
|
|
|
outBeaconState: var BeaconState): bool =
|
|
|
|
# TODO:
|
|
|
|
#
|
|
|
|
# 1. Validate that the signedBlock state root hash matches the
|
|
|
|
# provided beaconState
|
|
|
|
#
|
|
|
|
# 2. Validate that the applied changelog produces the correct
|
|
|
|
# new change log head
|
|
|
|
#
|
|
|
|
# 3. Check that enough signatures from the known validator set
|
|
|
|
# are present
|
|
|
|
#
|
|
|
|
# 4. Apply all changes to the validator set
|
|
|
|
#
|
|
|
|
|
2019-01-29 03:22:22 +00:00
|
|
|
outBeaconState.finalized_epoch =
|
2019-02-20 01:33:58 +00:00
|
|
|
log.signedBlock.slot div SLOTS_PER_EPOCH
|
2018-11-23 23:58:49 +00:00
|
|
|
|
2018-12-03 21:41:24 +00:00
|
|
|
outBeaconState.validator_registry_delta_chain_tip =
|
|
|
|
log.beaconState.validator_registry_delta_chain_tip
|
2018-11-23 23:58:49 +00:00
|
|
|
|