2021-03-26 06:52:01 +00:00
# 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.
{. push raises : [ Defect ] . }
2020-10-27 09:00:57 +00:00
import
std / [ strutils , parseutils ] ,
stew / byteutils ,
2021-10-19 14:09:26 +00:00
.. / beacon_node , .. / validators / validator_duties ,
2021-03-04 09:13:44 +00:00
.. / consensus_object_pools / [ block_pools_types , blockchain_dag ] ,
2021-06-23 14:43:18 +00:00
.. / spec / datatypes / base ,
2021-08-12 13:08:20 +00:00
.. / spec / [ forks , helpers ] ,
2021-08-03 15:17:11 +00:00
.. / spec / eth2_apis / [ rpc_types , eth2_json_rpc_serialization ]
2020-10-27 09:00:57 +00:00
2021-10-13 10:20:18 +00:00
export forks , rpc_types , eth2_json_rpc_serialization , blockchain_dag
template raiseNoAltairSupport * ( ) =
raise ( ref ValueError ) ( msg :
" The JSON-RPC interface does not support certain Altair operations due to changes in block structure - see https://nimbus.guide/rest-api.html for full altair support " )
2020-10-27 09:00:57 +00:00
template withStateForStateId * ( stateId : string , body : untyped ) : untyped =
2021-03-17 10:17:15 +00:00
let
bs = node . stateIdToBlockSlot ( stateId )
template isState ( state : StateData ) : bool =
2021-06-11 17:51:46 +00:00
state . blck . atSlot ( getStateField ( state . data , slot ) ) = = bs
2021-03-17 10:17:15 +00:00
2021-06-01 11:13:40 +00:00
if isState ( node . dag . headState ) :
withStateVars ( node . dag . headState ) :
2021-06-29 15:09:29 +00:00
var cache {. inject , used . } : StateCache
2021-03-17 10:17:15 +00:00
body
else :
2021-06-01 11:13:40 +00:00
let rpcState = assignClone ( node . dag . headState )
2022-01-05 18:38:04 +00:00
node . dag . withUpdatedState ( rpcState [ ] , bs ) do :
2021-03-17 10:17:15 +00:00
body
2022-01-05 18:38:04 +00:00
do :
raise ( ref CatchableError ) ( msg : " Trying to access pruned state " )
2020-10-27 09:00:57 +00:00
2021-03-26 06:52:01 +00:00
proc parseRoot * ( str : string ) : Eth2Digest {. raises : [ Defect , ValueError ] . } =
Eth2Digest ( data : hexToByteArray [ 32 ] ( str ) )
2020-10-27 09:00:57 +00:00
2021-03-26 06:52:01 +00:00
func checkEpochToSlotOverflow * ( epoch : Epoch ) {. raises : [ Defect , ValueError ] . } =
2022-01-11 10:01:54 +00:00
const maxEpoch = epoch ( FAR_FUTURE_SLOT )
2020-10-27 09:00:57 +00:00
if epoch > = maxEpoch :
raise newException (
ValueError , " Requesting epoch for which slot would overflow " )
2021-03-26 06:52:01 +00:00
proc doChecksAndGetCurrentHead * ( node : BeaconNode , slot : Slot ) : BlockRef {. raises : [ Defect , CatchableError ] . } =
2021-06-01 11:13:40 +00:00
result = node . dag . head
2020-10-27 09:00:57 +00:00
if not node . isSynced ( result ) :
raise newException ( CatchableError , " Cannot fulfill request until node is synced " )
# TODO for now we limit the requests arbitrarily by up to 2 epochs into the future
if result . slot + uint64 ( 2 * SLOTS_PER_EPOCH ) < slot :
raise newException ( CatchableError , " Requesting way ahead of the current head " )
2021-03-26 06:52:01 +00:00
proc doChecksAndGetCurrentHead * ( node : BeaconNode , epoch : Epoch ) : BlockRef {. raises : [ Defect , CatchableError ] . } =
2020-10-27 09:00:57 +00:00
checkEpochToSlotOverflow ( epoch )
2022-01-11 10:01:54 +00:00
node . doChecksAndGetCurrentHead ( epoch . start_slot ( ) )
2020-10-27 09:00:57 +00:00
limit by-root requests to non-finalized blocks (#3293)
* limit by-root requests to non-finalized blocks
Presently, we keep a mapping from block root to `BlockRef` in memory -
this has simplified reasoning about the dag, but is not sustainable with
the chain growing.
We can distinguish between two cases where by-root access is useful:
* unfinalized blocks - this is where the beacon chain is operating
generally, by validating incoming data as interesting for future fork
choice decisions - bounded by the length of the unfinalized period
* finalized blocks - historical access in the REST API etc - no bounds,
really
In this PR, we limit the by-root block index to the first use case:
finalized chain data can more efficiently be addressed by slot number.
Future work includes:
* limiting the `BlockRef` horizon in general - each instance is 40
bytes+overhead which adds up - this needs further refactoring to deal
with the tail vs state problem
* persisting the finalized slot-to-hash index - this one also keeps
growing unbounded (albeit slowly)
Anyway, this PR easily shaves ~128mb of memory usage at the time of
writing.
* No longer honor `BeaconBlocksByRoot` requests outside of the
non-finalized period - previously, Nimbus would generously return any
block through this libp2p request - per the spec, finalized blocks
should be fetched via `BeaconBlocksByRange` instead.
* return `Opt[BlockRef]` instead of `nil` when blocks can't be found -
this becomes a lot more common now and thus deserves more attention
* `dag.blocks` -> `dag.forkBlocks` - this index only carries unfinalized
blocks from now - `finalizedBlocks` covers the other `BlockRef`
instances
* in backfill, verify that the last backfilled block leads back to
genesis, or panic
* add backfill timings to log
* fix missing check that `BlockRef` block can be fetched with
`getForkedBlock` reliably
* shortcut doppelganger check when feature is not enabled
* in REST/JSON-RPC, fetch blocks without involving `BlockRef`
* fix dag.blocks ref
2022-01-21 11:33:16 +00:00
proc parseSlot ( slot : string ) : Slot {. raises : [ Defect , CatchableError ] . } =
2020-10-27 09:00:57 +00:00
if slot . len = = 0 :
raise newException ( ValueError , " Empty slot number not allowed " )
var parsed : BiggestUInt
if parseBiggestUInt ( slot , parsed ) ! = slot . len :
raise newException ( ValueError , " Not a valid slot number " )
limit by-root requests to non-finalized blocks (#3293)
* limit by-root requests to non-finalized blocks
Presently, we keep a mapping from block root to `BlockRef` in memory -
this has simplified reasoning about the dag, but is not sustainable with
the chain growing.
We can distinguish between two cases where by-root access is useful:
* unfinalized blocks - this is where the beacon chain is operating
generally, by validating incoming data as interesting for future fork
choice decisions - bounded by the length of the unfinalized period
* finalized blocks - historical access in the REST API etc - no bounds,
really
In this PR, we limit the by-root block index to the first use case:
finalized chain data can more efficiently be addressed by slot number.
Future work includes:
* limiting the `BlockRef` horizon in general - each instance is 40
bytes+overhead which adds up - this needs further refactoring to deal
with the tail vs state problem
* persisting the finalized slot-to-hash index - this one also keeps
growing unbounded (albeit slowly)
Anyway, this PR easily shaves ~128mb of memory usage at the time of
writing.
* No longer honor `BeaconBlocksByRoot` requests outside of the
non-finalized period - previously, Nimbus would generously return any
block through this libp2p request - per the spec, finalized blocks
should be fetched via `BeaconBlocksByRange` instead.
* return `Opt[BlockRef]` instead of `nil` when blocks can't be found -
this becomes a lot more common now and thus deserves more attention
* `dag.blocks` -> `dag.forkBlocks` - this index only carries unfinalized
blocks from now - `finalizedBlocks` covers the other `BlockRef`
instances
* in backfill, verify that the last backfilled block leads back to
genesis, or panic
* add backfill timings to log
* fix missing check that `BlockRef` block can be fetched with
`getForkedBlock` reliably
* shortcut doppelganger check when feature is not enabled
* in REST/JSON-RPC, fetch blocks without involving `BlockRef`
* fix dag.blocks ref
2022-01-21 11:33:16 +00:00
Slot parsed
proc getBlockSlotFromString * ( node : BeaconNode , slot : string ) : BlockSlot {. raises : [ Defect , CatchableError ] . } =
let parsed = parseSlot ( slot )
discard node . doChecksAndGetCurrentHead ( parsed )
node . dag . getBlockAtSlot ( parsed )
proc getBlockIdFromString * ( node : BeaconNode , slot : string ) : BlockId {. raises : [ Defect , CatchableError ] . } =
let parsed = parseSlot ( slot )
discard node . doChecksAndGetCurrentHead ( parsed )
let bsid = node . dag . getBlockIdAtSlot ( parsed )
if bsid . isProposed ( ) :
bsid . bid
else :
raise ( ref ValueError ) ( msg : " Block not found " )
2020-10-27 09:00:57 +00:00
2021-03-26 06:52:01 +00:00
proc stateIdToBlockSlot * ( node : BeaconNode , stateId : string ) : BlockSlot {. raises : [ Defect , CatchableError ] . } =
2021-03-26 14:11:06 +00:00
case stateId :
of " head " :
2021-12-09 17:06:21 +00:00
node . dag . head . atSlot ( )
2021-03-26 14:11:06 +00:00
of " genesis " :
2021-12-09 17:06:21 +00:00
node . dag . genesis . atSlot ( )
2021-03-26 14:11:06 +00:00
of " finalized " :
2021-06-01 11:13:40 +00:00
node . dag . finalizedHead
2021-03-26 14:11:06 +00:00
of " justified " :
2021-06-01 11:13:40 +00:00
node . dag . head . atEpochStart (
2021-06-11 17:51:46 +00:00
getStateField ( node . dag . headState . data , current_justified_checkpoint ) . epoch )
2021-03-26 14:11:06 +00:00
else :
if stateId . startsWith ( " 0x " ) :
2021-12-09 17:06:21 +00:00
let stateRoot = parseRoot ( stateId )
if stateRoot = = getStateRoot ( node . dag . headState . data ) :
node . dag . headState . blck . atSlot ( )
else :
# We don't have a state root -> BlockSlot mapping
raise ( ref ValueError ) ( msg : " State not found " )
else : # Parse as slot number
2021-03-26 14:11:06 +00:00
node . getBlockSlotFromString ( stateId )