Broadcast blocks before integrating in ChainDAG

This PR fixes two issues with block publishing:

* Gossip-valid blocks are published before integrating them into the
chain, giving broadcasting a head start, both for rest block and
* Outright invalid blocks from the API that could lead to the descoring
of the node are no longer broadcast

Bonus:

* remove undocumented and duplicated `post_v1_validator_block` JSON-RPC
call
This commit is contained in:
Jacek Sieka 2021-12-03 14:58:12 +01:00 committed by zah
parent 1a8b7469e3
commit 850eece949
5 changed files with 117 additions and 168 deletions

View File

@ -799,6 +799,7 @@ proc installBeaconApiHandlers*(router: var RestRouter, node: BeaconNode) =
return RestApiResponse.jsonError(Http503, BeaconNodeInSyncError)
if not(res.get()):
return RestApiResponse.jsonError(Http202, BlockValidationError)
return RestApiResponse.jsonMsgResponse(BlockValidationSuccess)
# https://ethereum.github.io/beacon-APIs/#/Beacon/getBlock

View File

@ -401,28 +401,18 @@ proc installBeaconApiHandlers*(rpcServer: RpcServer, node: BeaconNode) {.
)
rpcServer.rpc("post_v1_beacon_blocks") do (blck: phase0.SignedBeaconBlock) -> int:
if not(node.syncManager.inProgress):
raise newException(CatchableError,
"Beacon node is currently syncing, try again later.")
let head = node.dag.head
if head.slot >= blck.message.slot:
node.network.broadcastBeaconBlock(blck)
# The block failed validation, but was successfully broadcast anyway.
# It was not integrated into the beacon node's database.
return 202
let res = await sendBeaconBlock(node, ForkedSignedBeaconBlock.init(blck))
if res.isErr():
raise (ref CatchableError)(msg: $res.error())
if res.get():
# The block was validated successfully and has been broadcast.
# It has also been integrated into the beacon node's database.
return 200
else:
let res = await proposeSignedBlock(
node, head, AttachedValidator(),
ForkedSignedBeaconBlock.init(blck))
if res == head:
node.network.broadcastBeaconBlock(blck)
# The block failed validation, but was successfully broadcast anyway.
# It was not integrated into the beacon node''s database.
return 202
else:
# The block was validated successfully and has been broadcast.
# It has also been integrated into the beacon node's database.
return 200
# The block failed validation, but was successfully broadcast anyway.
# It was not integrated into the beacon node''s database.
return 202
rpcServer.rpc("get_v1_beacon_blocks_blockId") do (
blockId: string) -> phase0.TrustedSignedBeaconBlock:

View File

@ -53,20 +53,6 @@ proc installValidatorApiHandlers*(rpcServer: RpcServer, node: BeaconNode) {.
else:
raiseNoAltairSupport()
rpcServer.rpc("post_v1_validator_block") do (body: phase0.SignedBeaconBlock) -> bool:
debug "post_v1_validator_block",
slot = body.message.slot,
prop_idx = body.message.proposer_index
let head = node.doChecksAndGetCurrentHead(body.message.slot)
if head.slot >= body.message.slot:
raise newException(CatchableError,
"Proposal is for a past slot: " & $body.message.slot)
if head == await proposeSignedBlock(
node, head, AttachedValidator(), ForkedSignedBeaconBlock.init(body)):
raise newException(CatchableError, "Could not propose block")
return true
rpcServer.rpc("get_v1_validator_attestation_data") do (
slot: Slot, committee_index: CommitteeIndex) -> AttestationData:
debug "get_v1_validator_attestation_data", slot = slot

View File

@ -7,8 +7,6 @@ import
proc get_v1_validator_block(slot: Slot, graffiti: GraffitiBytes, randao_reveal: ValidatorSig): phase0.BeaconBlock
proc post_v1_validator_block(body: phase0.SignedBeaconBlock): bool
proc get_v1_validator_attestation_data(slot: Slot, committee_index: CommitteeIndex): AttestationData
proc get_v1_validator_aggregate_attestation(slot: Slot, attestation_data_root: Eth2Digest): Attestation

View File

@ -462,32 +462,6 @@ proc makeBeaconBlockForHeadAndSlot*(node: BeaconNode,
noRollback, # Temporary state - no need for rollback
cache)
proc proposeSignedBlock*(node: BeaconNode,
head: BlockRef,
validator: AttachedValidator,
newBlock: ForkedSignedBeaconBlock):
Future[BlockRef] {.async.} =
let wallTime = node.beaconClock.now()
return withBlck(newBlock):
let newBlockRef = node.blockProcessor[].storeBlock(
blck, wallTime.slotOrZero())
if newBlockRef.isErr:
warn "Unable to add proposed block to block pool",
newBlock = blck.message, root = blck.root
return head
notice "Block proposed",
blockRoot = shortLog(blck.root), blck = shortLog(blck.message),
signature = shortLog(blck.signature), validator = shortLog(validator)
node.network.broadcastBeaconBlock(blck)
beacon_blocks_proposed.inc()
newBlockRef.get()
proc proposeBlock(node: BeaconNode,
validator: AttachedValidator,
validator_index: ValidatorIndex,
@ -522,20 +496,17 @@ proc proposeBlock(node: BeaconNode,
if newBlock.isErr():
return head # already logged elsewhere!
let blck = newBlock.get()
let forkedBlck = newBlock.get()
# TODO abstract this, or move it into makeBeaconBlockForHeadAndSlot, and in
# general this is far too much copy/paste
let forked = case blck.kind:
of BeaconBlockFork.Phase0:
let root = hash_tree_root(blck.phase0Data)
withBlck(forkedBlck):
let
blockRoot = hash_tree_root(blck)
signing_root = compute_block_root(
fork, genesis_validators_root, slot, blockRoot)
# TODO: recomputed in block proposal
let signing_root = compute_block_root(
fork, genesis_validators_root, slot, root)
let notSlashable = node.attachedValidators
.slashingProtection
.registerBlock(validator_index, validator.pubkey, slot, signing_root)
notSlashable = node.attachedValidators
.slashingProtection
.registerBlock(validator_index, validator.pubkey, slot, signing_root)
if notSlashable.isErr:
warn "Slashing protection activated",
@ -544,83 +515,58 @@ proc proposeBlock(node: BeaconNode,
existingProposal = notSlashable.error
return head
let signature =
block:
let res = await validator.signBlockProposal(fork,
genesis_validators_root, slot, root, blck)
if res.isErr():
error "Unable to sign block proposal",
validator = shortLog(validator), error_msg = res.error()
return head
res.get()
ForkedSignedBeaconBlock.init(
phase0.SignedBeaconBlock(
message: blck.phase0Data, root: root, signature: signature)
)
of BeaconBlockFork.Altair:
let root = hash_tree_root(blck.altairData)
let
signature =
block:
let res = await validator.signBlockProposal(
fork, genesis_validators_root, slot, blockRoot, forkedBlck)
if res.isErr():
error "Unable to sign block proposal",
validator = shortLog(validator), error_msg = res.error()
return head
res.get()
signedBlock =
when blck is phase0.BeaconBlock:
phase0.SignedBeaconBlock(
message: blck, signature: signature, root: blockRoot)
elif blck is altair.BeaconBlock:
altair.SignedBeaconBlock(
message: blck, signature: signature, root: blockRoot)
elif blck is merge.BeaconBlock:
merge.SignedBeaconBlock(
message: blck, signature: signature, root: blockRoot)
else:
static: doAssert "Unkown block type"
# TODO: recomputed in block proposal
let signing_root = compute_block_root(
fork, genesis_validators_root, slot, root)
let notSlashable = node.attachedValidators
.slashingProtection
.registerBlock(validator_index, validator.pubkey, slot, signing_root)
# We produced the block using a state transition, meaning the block is valid
# enough that it will not be rejected by gossip - it is unlikely but
# possible that it will be ignored due to extreme timing conditions, for
# example a delay in signing.
# We'll start broadcasting it before integrating fully in the chaindag
# so that it can start propagating through the network ASAP.
node.network.broadcastBeaconBlock(signedBlock)
if notSlashable.isErr:
warn "Slashing protection activated",
validator = validator.pubkey,
slot = slot,
existingProposal = notSlashable.error
let
wallTime = node.beaconClock.now()
# storeBlock puts the block in the chaindag, and if accepted, takes care
# of side effects such as event api notification
newBlockRef = node.blockProcessor[].storeBlock(
signedBlock, wallTime.slotOrZero())
if newBlockRef.isErr:
warn "Unable to add proposed block to block pool",
blockRoot = shortLog(blockRoot), blck = shortLog(blck),
signature = shortLog(signature), validator = shortLog(validator)
return head
let signature =
block:
let res = await validator.signBlockProposal(
fork, genesis_validators_root, slot, root, blck)
if res.isErr():
error "Unable to sign block proposal",
validator = shortLog(validator), error_msg = res.error()
return head
res.get()
notice "Block proposed",
blockRoot = shortLog(blockRoot), blck = shortLog(blck),
signature = shortLog(signature), validator = shortLog(validator)
ForkedSignedBeaconBlock.init(
altair.SignedBeaconBlock(
message: blck.altairData, root: root, signature: signature)
)
of BeaconBlockFork.Merge:
let root = hash_tree_root(blck.mergeData)
beacon_blocks_proposed.inc()
# TODO: recomputed in block proposal
let signing_root = compute_block_root(
fork, genesis_validators_root, slot, root)
let notSlashable = node.attachedValidators
.slashingProtection
.registerBlock(validator_index, validator.pubkey, slot, signing_root)
if notSlashable.isErr:
warn "Slashing protection activated",
validator = validator.pubkey,
slot = slot,
existingProposal = notSlashable.error
return head
let signature =
block:
let res = await validator.signBlockProposal(
fork, genesis_validators_root, slot, root, blck)
if res.isErr():
error "Unable to sign block proposal",
validator = shortLog(validator), error_msg = res.error()
return head
res.get()
ForkedSignedBeaconBlock.init(
merge.SignedBeaconBlock(
message: blck.mergeData, root: root, signature: signature)
)
return await node.proposeSignedBlock(head, validator, forked)
return newBlockRef.get()
proc handleAttestations(node: BeaconNode, head: BlockRef, slot: Slot) =
## Perform all attestations that the validators attached to this node should
@ -874,16 +820,17 @@ proc handleProposal(node: BeaconNode, head: BlockRef, slot: Slot):
proposerKey = node.dag.validatorKey(proposer.get).get().toPubKey
validator = node.attachedValidators[].getValidator(proposerKey)
if validator != nil:
return await proposeBlock(node, validator, proposer.get(), head, slot)
return
if validator == nil:
debug "Expecting block proposal",
headRoot = shortLog(head.root),
slot = shortLog(slot),
proposer_index = proposer.get(),
proposer = shortLog(proposerKey)
debug "Expecting block proposal",
headRoot = shortLog(head.root),
slot = shortLog(slot),
proposer_index = proposer.get(),
proposer = shortLog(proposerKey)
return head
head
else:
await proposeBlock(node, validator, proposer.get(), head, slot)
proc makeAggregateAndProof*(
pool: var AttestationPool, epochRef: EpochRef, slot: Slot, index: CommitteeIndex,
@ -1246,20 +1193,47 @@ proc sendProposerSlashing*(node: BeaconNode,
proc sendBeaconBlock*(node: BeaconNode, forked: ForkedSignedBeaconBlock
): Future[SendBlockResult] {.async.} =
# REST/JSON-RPC API helper procedure.
let head = node.dag.head
if not(node.isSynced(head)):
return SendBlockResult.err("Beacon node is currently syncing")
if head.slot >= forked.slot():
node.network.broadcastBeaconBlock(forked)
return SendBlockResult.ok(false)
block:
# Start with a quick gossip validation check such that broadcasting the
# block doesn't get the node into trouble
let res = withBlck(forked):
when blck isnot merge.SignedBeaconBlock:
validateBeaconBlock(
node.dag, node.quarantine, blck, node.beaconClock.now(),
{})
else:
return SendBlockResult.err(
"TODO merge block proposal via REST not implemented")
let res = await node.proposeSignedBlock(head, AttachedValidator(), forked)
if res == head:
# `res == head` means failure, in such case we need to broadcast block
# manually because of the specification.
node.network.broadcastBeaconBlock(forked)
return SendBlockResult.ok(false)
return SendBlockResult.ok(true)
if not res.isGoodForSending():
return SendBlockResult.err(res.error()[1])
# The block passed basic gossip validation - we can "safely" broadcast it now.
# In fact, per the spec, we should broadcast it even if it later fails to
# apply to our state.
node.network.broadcastBeaconBlock(forked)
let
head = node.dag.head
wallTime = node.beaconClock.now()
accepted = withBlck(forked):
let newBlockRef = node.blockProcessor[].storeBlock(
blck, wallTime.slotOrZero())
# The boolean we return tells the caller whether the block was integrated
# into the chain
if newBlockRef.isOk():
notice "Block published",
blockRoot = shortLog(blck.root), blck = shortLog(blck.message),
signature = shortLog(blck.signature)
true
else:
warn "Unable to add proposed block to block pool",
blockRoot = shortLog(blck.root), blck = shortLog(blck.message),
signature = shortLog(blck.signature), err = newBlockRef.error()
false
return SendBlockResult.ok(accepted)
proc registerDuty*(
node: BeaconNode, slot: Slot, subnet_id: SubnetId, vidx: ValidatorIndex,