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:
parent
1a8b7469e3
commit
850eece949
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue