nimbus-eth2/beacon_chain/rpc/validator_api.nim
Jacek Sieka 5713a3ce4c
performance fixes (#2259)
* performance fixes

* don't mark tree cache as dirty on read-only List accesses
* store only blob in memory for keys and signatures, parse blob lazily
* compare public keys by blob instead of parsing / converting to raw
* compare Eth2Digest using non-constant-time comparison
* avoid some unnecessary validator copying

This branch will in particular speed up deposit processing which has
been slowing down block replay.

Pre (mainnet, 1600 blocks):

```
All time are ms
     Average,       StdDev,          Min,          Max,      Samples,         Test
Validation is turned off meaning that no BLS operations are performed
    3450.269,        0.000,     3450.269,     3450.269,            1, Initialize DB
       0.417,        0.822,        0.036,       21.098,         1400, Load block from database
      16.521,        0.000,       16.521,       16.521,            1, Load state from database
      27.906,       50.846,        8.104,     1507.633,         1350, Apply block
      52.617,       37.029,       20.640,      135.938,           50, Apply epoch block
```

Post:

```
    3502.715,        0.000,     3502.715,     3502.715,            1, Initialize DB
       0.080,        0.560,        0.035,       21.015,         1400, Load block from database
      17.595,        0.000,       17.595,       17.595,            1, Load state from database
      15.706,       11.028,        8.300,      107.537,         1350, Apply block
      33.217,       12.622,       17.331,       60.580,           50, Apply epoch block
```

* more perf fixes

* load EpochRef cache into StateCache more aggressively
* point out security concern with public key cache
* reuse proposer index from state when processing block
* avoid genericAssign in a few more places
* don't parse key when signature is unparseable
* fix `==` overload for Eth2Digest
* preallocate validator list when getting active validators
* speed up proposer index calculation a little bit
* reuse cache when replaying blocks in ncli_db
* avoid a few more copying loops

```
     Average,       StdDev,          Min,          Max,      Samples,         Test
Validation is turned off meaning that no BLS operations are performed
    3279.158,        0.000,     3279.158,     3279.158,            1, Initialize DB
       0.072,        0.357,        0.035,       13.400,         1400, Load block from database
      17.295,        0.000,       17.295,       17.295,            1, Load state from database
       5.918,        9.896,        0.198,       98.028,         1350, Apply block
      15.888,       10.951,        7.902,       39.535,           50, Apply epoch block
       0.000,        0.000,        0.000,        0.000,            0, Database block store
```

* clear full balance cache before processing rewards and penalties

```
All time are ms
     Average,       StdDev,          Min,          Max,      Samples,         Test
Validation is turned off meaning that no BLS operations are performed
    3947.901,        0.000,     3947.901,     3947.901,            1, Initialize DB
       0.124,        0.506,        0.026,      202.370,       363345, Load block from database
      97.614,        0.000,       97.614,       97.614,            1, Load state from database
       0.186,        0.188,        0.012,       99.561,       357262, Advance slot, non-epoch
      14.161,        5.966,        1.099,      395.511,        11524, Advance slot, epoch
       1.372,        4.170,        0.017,      276.401,       363345, Apply block, no slot processing
       0.000,        0.000,        0.000,        0.000,            0, Database block store
```
2021-01-25 13:04:18 +01:00

166 lines
7.3 KiB
Nim

# beacon_chain
# Copyright (c) 2018-2021 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
# Standard library
std/[tables],
# Nimble packages
stew/[objects],
json_rpc/[rpcserver, jsonmarshal],
chronicles,
# Local modules
../spec/[datatypes, digest, crypto, helpers, network, signatures],
../spec/eth2_apis/callsigs_types,
../block_pools/[chain_dag, spec_cache], ../ssz/merkleization,
../beacon_node_common, ../beacon_node_types, ../attestation_pool,
../validator_duties, ../eth2_network,
../eth2_json_rpc_serialization,
./rpc_utils
logScope: topics = "valapi"
type
RpcServer* = RpcHttpServer
proc installValidatorApiHandlers*(rpcServer: RpcServer, node: BeaconNode) =
rpcServer.rpc("get_v1_validator_block") do (
slot: Slot, graffiti: GraffitiBytes, randao_reveal: ValidatorSig) -> BeaconBlock:
debug "get_v1_validator_block", slot = slot
let head = node.doChecksAndGetCurrentHead(slot)
let proposer = node.chainDag.getProposer(head, slot)
if proposer.isNone():
raise newException(CatchableError, "could not retrieve block for slot: " & $slot)
let message = makeBeaconBlockForHeadAndSlot(
node, randao_reveal, proposer.get()[0], graffiti, head, slot)
if message.isNone():
raise newException(CatchableError, "could not retrieve block for slot: " & $slot)
return message.get()
rpcServer.rpc("post_v1_validator_block") do (body: 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 == proposeSignedBlock(node, head, AttachedValidator(), 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
let
head = node.doChecksAndGetCurrentHead(slot)
epochRef = node.chainDag.getEpochRef(head, slot.epoch)
return makeAttestationData(epochRef, head.atSlot(slot), committee_index)
rpcServer.rpc("get_v1_validator_aggregate_attestation") do (
slot: Slot, attestation_data_root: Eth2Digest)-> Attestation:
debug "get_v1_validator_aggregate_attestation"
let res = node.attestationPool[].getAggregatedAttestation(slot, attestation_data_root)
if res.isSome:
return res.get
raise newException(CatchableError, "Could not retrieve an aggregated attestation")
rpcServer.rpc("post_v1_validator_aggregate_and_proofs") do (
payload: SignedAggregateAndProof) -> bool:
debug "post_v1_validator_aggregate_and_proofs"
node.network.broadcast(node.topicAggregateAndProofs, payload)
notice "Aggregated attestation sent",
attestation = shortLog(payload.message.aggregate)
rpcServer.rpc("get_v1_validator_duties_attester") do (
epoch: Epoch, public_keys: seq[ValidatorPubKey]) -> seq[AttesterDuties]:
debug "get_v1_validator_duties_attester", epoch = epoch
let
head = node.doChecksAndGetCurrentHead(epoch)
epochRef = node.chainDag.getEpochRef(head, epoch)
committees_per_slot = get_committee_count_per_slot(epochRef)
for i in 0 ..< SLOTS_PER_EPOCH:
let slot = compute_start_slot_at_epoch(epoch) + i
for committee_index in 0'u64..<committees_per_slot:
let committee = get_beacon_committee(
epochRef, slot, committee_index.CommitteeIndex)
for index_in_committee, validatorIdx in committee:
if validatorIdx < epochRef.validator_keys.len.ValidatorIndex:
let curr_val_pubkey = epochRef.validator_keys[validatorIdx]
if public_keys.findIt(it == curr_val_pubkey) != -1:
result.add((public_key: curr_val_pubkey,
validator_index: validatorIdx,
committee_index: committee_index.CommitteeIndex,
committee_length: committee.lenu64,
validator_committee_index: index_in_committee.uint64,
slot: slot))
rpcServer.rpc("get_v1_validator_duties_proposer") do (
epoch: Epoch) -> seq[ValidatorPubkeySlotPair]:
debug "get_v1_validator_duties_proposer", epoch = epoch
let
head = node.doChecksAndGetCurrentHead(epoch)
epochRef = node.chainDag.getEpochRef(head, epoch)
for i in 0 ..< SLOTS_PER_EPOCH:
if epochRef.beacon_proposers[i].isSome():
result.add((public_key: epochRef.beacon_proposers[i].get()[1],
slot: compute_start_slot_at_epoch(epoch) + i))
rpcServer.rpc("post_v1_validator_beacon_committee_subscriptions") do (
committee_index: CommitteeIndex, slot: Slot, aggregator: bool,
validator_pubkey: ValidatorPubKey, slot_signature: ValidatorSig) -> bool:
debug "post_v1_validator_beacon_committee_subscriptions",
committee_index, slot
if committee_index.uint64 >= ATTESTATION_SUBNET_COUNT.uint64:
raise newException(CatchableError,
"Invalid committee index")
if node.syncManager.inProgress:
raise newException(CatchableError,
"Beacon node is currently syncing and not serving request on that endpoint")
let wallSlot = node.beaconClock.now.slotOrZero
if wallSlot > slot + 1:
raise newException(CatchableError,
"Past slot requested")
let epoch = slot.epoch
if epoch >= wallSlot.epoch and epoch - wallSlot.epoch > 1:
raise newException(CatchableError,
"Slot requested not in current or next wall-slot epoch")
if not verify_slot_signature(
node.chainDag.headState.data.data.fork,
node.chainDag.headState.data.data.genesis_validators_root,
slot, validator_pubkey, slot_signature):
raise newException(CatchableError,
"Invalid slot signature")
let
head = node.doChecksAndGetCurrentHead(epoch)
epochRef = node.chainDag.getEpochRef(head, epoch)
subnet = compute_subnet_for_attestation(
get_committee_count_per_slot(epochRef), slot, committee_index).uint8
# Either subnet already subscribed or not. If not, subscribe. If it is,
# extend subscription. All one knows from the API combined with how far
# ahead one can check for attestation schedule is that it might be used
# for up to the end of next epoch. Therefore, arrange for subscriptions
# to last at least that long.
if subnet notin node.attestationSubnets.subscribedSubnets:
# When to subscribe. Since it's not clear when from the API it's first
# needed, do so immediately.
node.attestationSubnets.subscribeSlot[subnet] =
min(node.attestationSubnets.subscribeSlot[subnet], wallSlot)
node.attestationSubnets.unsubscribeSlot[subnet] =
max(
compute_start_slot_at_epoch(epoch + 2),
node.attestationSubnets.unsubscribeSlot[subnet])