From f8c3ed5cb05d9f320df74c85b29b65b3f4a9deaf Mon Sep 17 00:00:00 2001 From: Etan Kissling Date: Tue, 14 Mar 2023 00:43:24 +0100 Subject: [PATCH 1/8] allow trusted node sync based on LC trusted block root Extends `trustedNodeSync` with a new `--trusted-block-root` option that allows initializing a light client. No `--state-id` must be provided. The beacon node will then use this light client to obtain the latest finalized state from the remote server in a trust-minimized fashion. Note that the provided `--trusted-block-root` should be somewhat recent, and that security precautions such as comparing the state root against block explorers is still recommended. --- beacon_chain/conf.nim | 8 +- beacon_chain/nimbus_beacon_node.nim | 19 +- .../eth2_apis/eth2_rest_serialization.nim | 70 +++- .../spec/eth2_apis/rest_beacon_client.nim | 6 +- beacon_chain/spec/eth2_apis/rest_common.nim | 23 ++ .../eth2_apis/rest_light_client_calls.nim | 300 ++++++++++++++++++ beacon_chain/trusted_node_sync.nim | 215 ++++++++++++- 7 files changed, 627 insertions(+), 14 deletions(-) create mode 100644 beacon_chain/spec/eth2_apis/rest_light_client_calls.nim diff --git a/beacon_chain/conf.nim b/beacon_chain/conf.nim index 3238ac6190..b42be39997 100644 --- a/beacon_chain/conf.nim +++ b/beacon_chain/conf.nim @@ -770,15 +770,19 @@ type stateId* {. desc: "State id to sync to - this can be \"finalized\", a slot number or state hash or \"head\"" - defaultValue: "finalized", name: "state-id" - .}: string + .}: Option[string] blockId* {. hidden desc: "Block id to sync to - this can be a block root, slot number, \"finalized\" or \"head\" (deprecated)" .}: Option[string] + lcTrustedBlockRoot* {. + hidden + desc: "Recent trusted finalized block root to initialize light client from" + name: "trusted-block-root" .}: Option[Eth2Digest] + backfillBlocks* {. desc: "Backfill blocks directly from REST server instead of fetching via API" defaultValue: true diff --git a/beacon_chain/nimbus_beacon_node.nim b/beacon_chain/nimbus_beacon_node.nim index e572011301..cfdc03bd41 100644 --- a/beacon_chain/nimbus_beacon_node.nim +++ b/beacon_chain/nimbus_beacon_node.nim @@ -1961,6 +1961,23 @@ proc handleStartUpCmd(config: var BeaconNodeConf) {.raises: [Defect, CatchableEr let network = loadEth2Network(config) cfg = network.cfg + syncTarget = + if config.stateId.isSome: + if config.lcTrustedBlockRoot.isSome: + warn "Ignoring `trustedBlockRoot`, `stateId` is set", + stateId = config.stateId, + trustedBlockRoot = config.lcTrustedBlockRoot + TrustedNodeSyncTarget( + kind: TrustedNodeSyncKind.StateId, + stateId: config.stateId.get) + elif config.lcTrustedBlockRoot.isSome: + TrustedNodeSyncTarget( + kind: TrustedNodeSyncKind.TrustedBlockRoot, + trustedBlockRoot: config.lcTrustedBlockRoot.get) + else: + TrustedNodeSyncTarget( + kind: TrustedNodeSyncKind.StateId, + stateId: "finalized") genesis = if network.genesisData.len > 0: newClone(readSszForkedHashedBeaconState( @@ -1977,7 +1994,7 @@ proc handleStartUpCmd(config: var BeaconNodeConf) {.raises: [Defect, CatchableEr config.databaseDir, config.eraDir, config.trustedNodeUrl, - config.stateId, + syncTarget, config.backfillBlocks, config.reindex, config.downloadDepositSnapshot, diff --git a/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim b/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim index 31c7977ed0..7751f864d0 100644 --- a/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim +++ b/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim @@ -28,6 +28,20 @@ export from web3/ethtypes import BlockHash export ethtypes.BlockHash +func decodeMediaType*( + contentType: Opt[ContentTypeData]): Result[MediaType, cstring] = + if contentType.isNone or isWildCard(contentType.get.mediaType): + return err("Missing or incorrect Content-Type") + ok contentType.get.mediaType + +func decodeEthConsensusVersion*( + value: string): Result[ConsensusFork, cstring] = + let normalizedValue = value.toLowerAscii() + for consensusFork in ConsensusFork: + if normalizedValue == ($consensusFork).toLowerAscii(): + return ok consensusFork + err(cstring("Unsupported Eth-Consensus-Version: " & value)) + Json.createFlavor RestJson ## The RestJson format implements JSON serialization in the way specified @@ -136,7 +150,9 @@ type Web3SignerSignatureResponse | Web3SignerStatusResponse | GetStateRootResponse | - GetBlockRootResponse + GetBlockRootResponse | + SomeForkedLightClientObject | + seq[SomeForkedLightClientObject] RestVersioned*[T] = object data*: T @@ -1797,6 +1813,49 @@ proc writeValue*(writer: var JsonWriter[RestJson], value: ForkedHashedBeaconStat writer.writeField("data", value.denebData.data) writer.endRecord() +## SomeForkedLightClientObject +proc readValue*[T: SomeForkedLightClientObject]( + reader: var JsonReader[RestJson], value: var T) {. + raises: [IOError, SerializationError, Defect].} = + var + version: Opt[ConsensusFork] + data: Opt[JsonString] + + for fieldName in readObjectFields(reader): + case fieldName + of "version": + if version.isSome: + reader.raiseUnexpectedField("Multiple version fields found", T.name) + let consensusFork = + decodeEthConsensusVersion(reader.readValue(string)).valueOr: + reader.raiseUnexpectedValue("Incorrect version field value") + version.ok consensusFork + of "data": + if data.isSome: + reader.raiseUnexpectedField("Multiple data fields found", T.name) + data.ok reader.readValue(JsonString) + else: + unrecognizedFieldWarning() + + if version.isNone: + reader.raiseUnexpectedValue("Field version is missing") + if data.isNone: + reader.raiseUnexpectedValue("Field data is missing") + + withLcDataFork(lcDataForkAtConsensusFork(version.get)): + when lcDataFork > LightClientDataFork.None: + value = T(kind: lcDataFork) + try: + value.forky(lcDataFork) = RestJson.decode( + string(data.get()), + T.Forky(lcDataFork), + requireAllFields = true, + allowUnknownFields = true) + except SerializationError: + reader.raiseUnexpectedValue("Incorrect format (" & $lcDataFork & ")") + else: + reader.raiseUnexpectedValue("Unsupported fork " & $version.get) + ## Web3SignerRequest proc writeValue*(writer: var JsonWriter[RestJson], value: Web3SignerRequest) {. @@ -2876,7 +2935,14 @@ proc decodeBytes*[T: DecodeTypes]( proc encodeString*(value: string): RestResult[string] = ok(value) -proc encodeString*(value: Epoch|Slot|CommitteeIndex|SyncSubcommitteeIndex): RestResult[string] = +proc encodeString*( + value: + uint64 | + SyncCommitteePeriod | + Epoch | + Slot | + CommitteeIndex | + SyncSubcommitteeIndex): RestResult[string] = ok(Base10.toString(uint64(value))) proc encodeString*(value: ValidatorSig): RestResult[string] = diff --git a/beacon_chain/spec/eth2_apis/rest_beacon_client.nim b/beacon_chain/spec/eth2_apis/rest_beacon_client.nim index 43b2adb011..ed24cc1a54 100644 --- a/beacon_chain/spec/eth2_apis/rest_beacon_client.nim +++ b/beacon_chain/spec/eth2_apis/rest_beacon_client.nim @@ -10,12 +10,14 @@ import chronos, presto/client, "."/[ rest_beacon_calls, rest_config_calls, rest_debug_calls, - rest_node_calls, rest_validator_calls, rest_keymanager_calls, + rest_keymanager_calls, rest_light_client_calls, + rest_node_calls, rest_validator_calls, rest_nimbus_calls, rest_common ] export chronos, client, rest_beacon_calls, rest_config_calls, rest_debug_calls, - rest_node_calls, rest_validator_calls, rest_keymanager_calls, + rest_keymanager_calls, rest_light_client_calls, + rest_node_calls, rest_validator_calls, rest_nimbus_calls, rest_common diff --git a/beacon_chain/spec/eth2_apis/rest_common.nim b/beacon_chain/spec/eth2_apis/rest_common.nim index c274528903..cb9e30b8ad 100644 --- a/beacon_chain/spec/eth2_apis/rest_common.nim +++ b/beacon_chain/spec/eth2_apis/rest_common.nim @@ -29,3 +29,26 @@ proc raiseUnknownStatusError*(resp: RestPlainResponse) {. noreturn, raises: [RestError, Defect].} = let msg = "Unknown response status error (" & $resp.status & ")" raise newException(RestError, msg) + +proc getBodyBytesWithCap*( + response: HttpClientResponseRef, + maxBytes: int): Future[Opt[seq[byte]]] {.async.} = + var reader = response.getBodyReader() + try: + let + data = await reader.read(maxBytes) + isComplete = reader.atEof() + await reader.closeWait() + reader = nil + await response.finish() + if not isComplete: + return err() + return ok data + except CancelledError as exc: + if not(isNil(reader)): + await reader.closeWait() + raise exc + except AsyncStreamError: + if not(isNil(reader)): + await reader.closeWait() + raise newHttpReadError("Could not read response") diff --git a/beacon_chain/spec/eth2_apis/rest_light_client_calls.nim b/beacon_chain/spec/eth2_apis/rest_light_client_calls.nim new file mode 100644 index 0000000000..2aa1601e9c --- /dev/null +++ b/beacon_chain/spec/eth2_apis/rest_light_client_calls.nim @@ -0,0 +1,300 @@ +# Copyright (c) 2023 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: [].} + +import + chronos, + stew/results, + presto/client, + ../helpers, + "."/[rest_common, eth2_rest_serialization] + +func checkForkConsistency( + obj: SomeForkedLightClientObject, + cfg: RuntimeConfig, + consensusFork = err(Opt[ConsensusFork])) {.raises: [RestError].} = + let objectFork = withForkyObject(obj): + when lcDataFork > LightClientDataFork.None: + cfg.consensusForkAtEpoch(forkyObject.contextEpoch) + else: + raiseRestDecodingBytesError("Invalid data") + + if lcDataForkAtConsensusFork(objectFork) != obj.kind: + raiseRestDecodingBytesError(cstring("Inconsistent forks" & + " (kind: " & $(obj.kind) & ", data: " & $objectFork & ")")) + + if consensusFork.isSome: + if objectFork != consensusFork.get: + raiseRestDecodingBytesError(cstring("Inconsistent forks" & + " (header: " & $(consensusFork.get) & ", data: " & $objectFork & ")")) + +func checkForkConsistency( + obj: SomeForkedLightClientObject, + cfg: RuntimeConfig, + consensusFork: ConsensusFork) {.raises: [RestError].} = + obj.checkForkConsistency(cfg, Opt[ConsensusFork].ok(consensusFork)) + +proc decodeHttpLightClientObject[T: SomeForkedLightClientObject]( + x: typedesc[T], + data: seq[byte], + contentType: Opt[ContentTypeData], + consensusFork: ConsensusFork, + cfg: RuntimeConfig): T {.raises: [RestError].} = + let mediaType = decodeMediaType(contentType).valueOr(resError): + raise newException(RestError, $resError) + + if mediaType == OctetStreamMediaType: + try: + withLcDataFork(lcDataForkAtConsensusFork(consensusFork)): + when lcDataFork > LightClientDataFork.None: + var obj = T(kind: lcDataFork) + obj.forky(lcDataFork) = SSZ.decode(data, T.Forky(lcDataFork)) + obj.checkForkConsistency(cfg, consensusFork) + return obj + else: + raiseRestDecodingBytesError( + cstring("Unsupported fork: " & $consensusFork)) + except SszError as exc: + raiseRestDecodingBytesError(cstring("Malformed data: " & $exc.msg)) + + if mediaType == ApplicationJsonMediaType: + let obj = decodeBytes(T, data, contentType).valueOr(resError): + raiseRestDecodingBytesError(resError) + obj.checkForkConsistency(cfg, consensusFork) + return obj + + raise newException(RestError, "Unsupported content-type") + +proc decodeHttpLightClientObjects[S: seq[SomeForkedLightClientObject]]( + x: typedesc[S], + data: seq[byte], + contentType: Opt[ContentTypeData], + cfg: RuntimeConfig, + forkDigests: ref ForkDigests): S {.raises: [RestError].} = + let mediaType = decodeMediaType(contentType).valueOr(resError): + raise newException(RestError, $resError) + + if mediaType == OctetStreamMediaType: + let l = data.len + var + res: S + o = 0 + while l - o != 0: + # response_chunk_len + if l - o < 8: + raiseRestDecodingBytesError("Malformed data: Incomplete length") + let responseChunkLen = uint64.fromBytesLE data.toOpenArray(o, o + 8 - 1) + o = o + 8 + + # response_chunk + if responseChunkLen > int.high.uint64: + raiseRestDecodingBytesError("Malformed data: Unsupported length") + if l - o < responseChunkLen.int: + raiseRestDecodingBytesError("Malformed data: Incomplete chunk") + let + begin = o + after = o + responseChunkLen.int + o += responseChunkLen.int + + # context + if responseChunkLen < 4: + raiseRestDecodingBytesError("Malformed data: Incomplete context") + let + context = ForkDigest [ + data[begin + 0], data[begin + 1], data[begin + 2], data[begin + 3]] + consensusFork = forkDigests[].consensusForkForDigest(context).valueOr: + raiseRestDecodingBytesError("Malformed data: Invalid context") + + # payload + try: + withLcDataFork(lcDataForkAtConsensusFork(consensusFork)): + when lcDataFork > LightClientDataFork.None: + type T = typeof(res[0]) + var obj = T(kind: lcDataFork) + obj.forky(lcDataFork) = SSZ.decode( + data.toOpenArray(begin + 4, after - 1), T.Forky(lcDataFork)) + obj.checkForkConsistency(cfg, consensusFork) + res.add obj + else: + raiseRestDecodingBytesError( + cstring("Unsupported fork: " & $consensusFork)) + except SszError as exc: + raiseRestDecodingBytesError(cstring("Malformed data: " & $exc.msg)) + return res + + if mediaType == ApplicationJsonMediaType: + let res = decodeBytes(S, data, contentType).valueOr(resError): + raiseRestDecodingBytesError(resError) + for obj in res: + obj.checkForkConsistency(cfg) + return res + + raise newException(RestError, "Unsupported content-type") + +proc getLightClientBootstrapPlain( + block_root: Eth2Digest): RestHttpResponseRef {. + rest, endpoint: "/eth/v1/beacon/light_client/bootstrap/{block_root}", + accept: preferSSZ, + meth: MethodGet.} + ## https://ethereum.github.io/beacon-APIs/#/Beacon/getLightClientBootstrap + +proc getLightClientBootstrap*( + client: RestClientRef, block_root: Eth2Digest, + cfg: RuntimeConfig, forkDigests: ref ForkDigests, + restAccept = ""): Future[ForkedLightClientBootstrap] {.async.} = + let resp = + if len(restAccept) > 0: + await client.getLightClientBootstrapPlain( + block_root, restAcceptType = restAccept) + else: + await client.getLightClientBootstrapPlain(block_root) + const maxBodyBytes = 128 * 1024 + let data = (await resp.getBodyBytesWithCap(maxBodyBytes)).valueOr: + raiseRestDecodingBytesError("Response too long") + return + case resp.status + of 200: + let consensusFork = decodeEthConsensusVersion( + resp.headers.getString("eth-consensus-version")).valueOr(resError): + raiseRestDecodingBytesError(resError) + ForkedLightClientBootstrap.decodeHttpLightClientObject( + data, resp.contentType, consensusFork, cfg) + of 404: + default(ForkedLightClientBootstrap) + of 400, 406, 500: + let error = + decodeBytes(RestErrorMessage, data, resp.contentType).valueOr: + raiseRestDecodingBytesError(error) + raise newException(RestError, + "Error response (" & $resp.status & ") [" & error.message & "]") + else: + raiseRestResponseError(RestPlainResponse( + status: resp.status, + contentType: resp.contentType, + data: data)) + +from ../../networking/eth2_network import MAX_REQUEST_LIGHT_CLIENT_UPDATES +export MAX_REQUEST_LIGHT_CLIENT_UPDATES + +proc getLightClientUpdatesByRangePlain( + start_period: SyncCommitteePeriod, count: uint64): RestHttpResponseRef {. + rest, endpoint: "/eth/v1/beacon/light_client/updates", + accept: preferSSZ, + meth: MethodGet.} + ## https://ethereum.github.io/beacon-APIs/#/Beacon/getLightClientUpdatesByRange + +proc getLightClientUpdatesByRange*( + client: RestClientRef, start_period: SyncCommitteePeriod, count: uint64, + cfg: RuntimeConfig, forkDigests: ref ForkDigests, + restAccept = ""): Future[seq[ForkedLightClientUpdate]] {.async.} = + let resp = + if len(restAccept) > 0: + await client.getLightClientUpdatesByRangePlain( + start_period, count, restAcceptType = restAccept) + else: + await client.getLightClientUpdatesByRangePlain(start_period, count) + const maxBodyBytes = MAX_REQUEST_LIGHT_CLIENT_UPDATES * 128 * 1024 + let data = (await resp.getBodyBytesWithCap(maxBodyBytes)).valueOr: + raiseRestDecodingBytesError("Response too long") + return + case resp.status + of 200: + seq[ForkedLightClientUpdate].decodeHttpLightClientObjects( + data, resp.contentType, cfg, forkDigests) + of 400, 406, 500: + let error = + decodeBytes(RestErrorMessage, data, resp.contentType).valueOr: + raiseRestDecodingBytesError(error) + raise newException(RestError, + "Error response (" & $resp.status & ") [" & error.message & "]") + else: + raiseRestResponseError(RestPlainResponse( + status: resp.status, + contentType: resp.contentType, + data: data)) + +proc getLightClientFinalityUpdatePlain(): RestHttpResponseRef {. + rest, endpoint: "/eth/v1/beacon/light_client/finality_update", + accept: preferSSZ, + meth: MethodGet.} + ## https://ethereum.github.io/beacon-APIs/#/Beacon/getLightClientFinalityUpdate + +proc getLightClientFinalityUpdate*( + client: RestClientRef, + cfg: RuntimeConfig, forkDigests: ref ForkDigests, + restAccept = ""): Future[ForkedLightClientFinalityUpdate] {.async.} = + let resp = + if len(restAccept) > 0: + await client.getLightClientFinalityUpdatePlain( + restAcceptType = restAccept) + else: + await client.getLightClientFinalityUpdatePlain() + const maxBodyBytes = 128 * 1024 + let data = (await resp.getBodyBytesWithCap(maxBodyBytes)).valueOr: + raiseRestDecodingBytesError("Response too long") + return + case resp.status + of 200: + let consensusFork = decodeEthConsensusVersion( + resp.headers.getString("eth-consensus-version")).valueOr(resError): + raiseRestDecodingBytesError(resError) + ForkedLightClientFinalityUpdate.decodeHttpLightClientObject( + data, resp.contentType, consensusFork, cfg) + of 404: + default(ForkedLightClientFinalityUpdate) + of 406, 500: + let error = + decodeBytes(RestErrorMessage, data, resp.contentType).valueOr: + raiseRestDecodingBytesError(error) + raise newException(RestError, + "Error response (" & $resp.status & ") [" & error.message & "]") + else: + raiseRestResponseError(RestPlainResponse( + status: resp.status, + contentType: resp.contentType, + data: data)) + +proc getLightClientOptimisticUpdatePlain(): RestHttpResponseRef {. + rest, endpoint: "/eth/v1/beacon/light_client/optimistic_update", + accept: preferSSZ, + meth: MethodGet.} + ## https://ethereum.github.io/beacon-APIs/#/Beacon/getLightClientOptimisticUpdate + +proc getLightClientOptimisticUpdate*( + client: RestClientRef, + cfg: RuntimeConfig, forkDigests: ref ForkDigests, + restAccept = ""): Future[ForkedLightClientOptimisticUpdate] {.async.} = + let resp = + if len(restAccept) > 0: + await client.getLightClientOptimisticUpdatePlain( + restAcceptType = restAccept) + else: + await client.getLightClientOptimisticUpdatePlain() + const maxBodyBytes = 128 * 1024 + let data = (await resp.getBodyBytesWithCap(maxBodyBytes)).valueOr: + raiseRestDecodingBytesError("Response too long") + return + case resp.status + of 200: + let consensusFork = decodeEthConsensusVersion( + resp.headers.getString("eth-consensus-version")).valueOr(resError): + raiseRestDecodingBytesError(resError) + ForkedLightClientOptimisticUpdate.decodeHttpLightClientObject( + data, resp.contentType, consensusFork, cfg) + of 404: + default(ForkedLightClientOptimisticUpdate) + of 406, 500: + let error = + decodeBytes(RestErrorMessage, data, resp.contentType).valueOr: + raiseRestDecodingBytesError(error) + raise newException(RestError, + "Error response (" & $resp.status & ") [" & error.message & "]") + else: + raiseRestResponseError(RestPlainResponse( + status: resp.status, + contentType: resp.contentType, + data: data)) diff --git a/beacon_chain/trusted_node_sync.nim b/beacon_chain/trusted_node_sync.nim index f28b60e25d..b3028a84c6 100644 --- a/beacon_chain/trusted_node_sync.nim +++ b/beacon_chain/trusted_node_sync.nim @@ -12,7 +12,7 @@ import ./sync/sync_manager, ./consensus_object_pools/[block_clearance, blockchain_dag], ./spec/eth2_apis/rest_beacon_client, - ./spec/[beaconstate, eth2_merkleization, forks, presets, + ./spec/[beaconstate, eth2_merkleization, forks, light_client_sync, presets, state_transition, deposit_snapshots], "."/[beacon_clock, beacon_chain_db, era_db] @@ -45,19 +45,40 @@ proc fetchDepositSnapshot(client: RestClientRef): from ./spec/datatypes/deneb import asSigVerified, shortLog +type + TrustedNodeSyncKind* {.pure.} = enum + TrustedBlockRoot, + StateId + + TrustedNodeSyncTarget* = object + case kind*: TrustedNodeSyncKind + of TrustedNodeSyncKind.TrustedBlockRoot: + trustedBlockRoot*: Eth2Digest + of TrustedNodeSyncKind.StateId: + stateId*: string + +func shortLog*(v: TrustedNodeSyncTarget): auto = + case v.kind + of TrustedNodeSyncKind.TrustedBlockRoot: + "trustedBlockRoot(" & $v.trustedBlockRoot & ")" + of TrustedNodeSyncKind.StateId: + v.stateId + +chronicles.formatIt(TrustedNodeSyncTarget): shortLog(it) + proc doTrustedNodeSync*( cfg: RuntimeConfig, databaseDir: string, eraDir: string, restUrl: string, - stateId: string, + syncTarget: TrustedNodeSyncTarget, backfill: bool, reindex: bool, downloadDepositSnapshot: bool, genesisState: ref ForkedHashedBeaconState = nil) {.async.} = logScope: restUrl - stateId + syncTarget notice "Starting trusted node sync", databaseDir, backfill, reindex @@ -134,6 +155,174 @@ proc doTrustedNodeSync*( Opt.none(BlockId) if head.isNone: + var stateRoot: Opt[Eth2Digest] + let stateId = + case syncTarget.kind + of TrustedNodeSyncKind.TrustedBlockRoot: + # https://github.com/ethereum/consensus-specs/blob/v1.3.0-rc.3/specs/altair/light-client/light-client.md#light-client-sync-process + const lcDataFork = LightClientDataFork.high + var bestViableCheckpoint: Opt[tuple[slot: Slot, state_root: Eth2Digest]] + func trackBestViableCheckpoint(store: lcDataFork.LightClientStore) = + if store.finalized_header.beacon.slot.is_epoch: + bestViableCheckpoint.ok(( + slot: store.finalized_header.beacon.slot, + state_root: store.finalized_header.beacon.state_root)) + + if genesisState == nil: + error "Genesis state is required when using `trustedBlockRoot`" + quit 1 + let + beaconClock = BeaconClock.init( + getStateField(genesisState[], genesis_time)) + getBeaconTime = beaconClock.getBeaconTimeFn() + + genesis_validators_root = + getStateField(genesisState[], genesis_validators_root) + forkDigests = newClone ForkDigests.init(cfg, genesis_validators_root) + + trustedBlockRoot = syncTarget.trustedBlockRoot + + var bootstrap = + try: + notice "Downloading LC bootstrap", trustedBlockRoot + awaitWithTimeout( + client.getLightClientBootstrap( + trustedBlockRoot, cfg, forkDigests), + smallRequestsTimeout + ): + error "Attempt to download LC bootstrap timed out" + quit 1 + except CatchableError as exc: + error "Unable to download LC bootstrap", error = exc.msg + quit 1 + if bootstrap.kind == LightClientDataFork.None: + error "LC bootstrap unavailable on server" + quit 1 + bootstrap.migrateToDataFork(lcDataFork) + + var store = + initialize_light_client_store( + trustedBlockRoot, bootstrap.forky(lcDataFork), cfg + ).valueOr(resError): + error "`initialize_light_client_store` failed", resError + quit 1 + store.trackBestViableCheckpoint() + + while true: + let + finalized = + store.finalized_header.beacon.slot.sync_committee_period + optimistic = + store.optimistic_header.beacon.slot.sync_committee_period + current = + getBeaconTime().slotOrZero().sync_committee_period + isNextSyncCommitteeKnown = + store.is_next_sync_committee_known + + let + periods: Slice[SyncCommitteePeriod] = + if finalized == optimistic and not isNextSyncCommitteeKnown: + if finalized >= current: + finalized .. finalized + else: + finalized ..< current + elif finalized + 1 < current: + finalized + 1 ..< current + else: + break + startPeriod = periods.a + lastPeriod = periods.b + count = min(periods.len, MAX_REQUEST_LIGHT_CLIENT_UPDATES).uint64 + + var updates = + try: + notice "Downloading LC updates", startPeriod, count + awaitWithTimeout( + client.getLightClientUpdatesByRange( + startPeriod, count, cfg, forkDigests), + smallRequestsTimeout + ): + error "Attempt to download LC updates timed out" + quit 1 + except CatchableError as exc: + error "Unable to download LC updates", error = exc.msg + quit 1 + if updates.lenu64 > count: + error "Malformed LC updates response: Too many values" + quit 1 + if updates.len == 0: + warn "Server does not appear to be fully synced" + break + var expectedPeriod = startPeriod + for i in 0 ..< updates.len: + doAssert updates[i].kind > LightClientDataFork.None + updates[i].migrateToDataFork(lcDataFork) + let + attPeriod = updates[i].forky(lcDataFork) + .attested_header.beacon.slot.sync_committee_period + sigPeriod = updates[i].forky(lcDataFork) + .signature_slot.sync_committee_period + if attPeriod != sigPeriod: + error "Malformed LC updates response: Conflicting periods" + quit 1 + if attPeriod < expectedPeriod: + error "Malformed LC updates response: Unexpected period" + quit 1 + if attPeriod > expectedPeriod: + if attPeriod > lastPeriod: + error "Malformed LC updates response: Period too high" + quit 1 + expectedPeriod = attPeriod + inc expectedPeriod + + let res = process_light_client_update( + store, updates[i].forky(lcDataFork), + getBeaconTime().slotOrZero(), cfg, genesis_validators_root) + if not res.isOk: + error "`process_light_client_update` failed", resError = res.error + quit 1 + store.trackBestViableCheckpoint() + + var finalityUpdate = + try: + notice "Downloading LC finality update" + awaitWithTimeout( + client.getLightClientFinalityUpdate(cfg, forkDigests), + smallRequestsTimeout + ): + error "Attempt to download LC finality update timed out" + quit 1 + except CatchableError as exc: + error "Unable to download LC finality update", error = exc.msg + quit 1 + if bootstrap.kind == LightClientDataFork.None: + error "LC finality update unavailable on server" + quit 1 + finalityUpdate.migrateToDataFork(lcDataFork) + + let res = process_light_client_update( + store, finalityUpdate.forky(lcDataFork), + getBeaconTime().slotOrZero(), cfg, genesis_validators_root) + if not res.isOk: + error "`process_light_client_update` failed", resError = res.error + quit 1 + store.trackBestViableCheckpoint() + + if bestViableCheckpoint.isErr: + error "CP not on epoch boundary. Retry later", + latestCheckpointSlot = store.finalized_header.beacon.slot + quit 1 + if not store.finalized_header.beacon.slot.is_epoch: + warn "CP not on epoch boundary. Using older one", + latestCheckpointSlot = store.finalized_header.beacon.slot, + bestViableCheckpointSlot = bestViableCheckpoint.get.slot + + stateRoot.ok bestViableCheckpoint.get.state_root + Base10.toString(distinctBase(bestViableCheckpoint.get.slot)) + of TrustedNodeSyncKind.StateId: + syncTarget.stateId + logScope: stateId + notice "Downloading checkpoint state" let @@ -156,10 +345,18 @@ proc doTrustedNodeSync*( quit 1 if state == nil: - error "No state found a given checkpoint", - stateId + error "No state found a given checkpoint" quit 1 + if stateRoot.isSome: + if state[].getStateRoot() != stateRoot.get: + error "Checkpoint state root has incorrect root!", + expectedStateRoot = stateRoot.get, + actualStateRoot = state[].getStateRoot() + quit 1 + info "Checkpoint state validated against LC data", + stateRoot = stateRoot.get + if not getStateField(state[], slot).is_epoch(): error "State slot must fall on an epoch boundary", slot = getStateField(state[], slot), @@ -331,8 +528,12 @@ when isMainModule: std/[os], networking/network_metadata - let backfill = os.paramCount() > 5 and os.paramStr(6) == "true" + let + syncTarget = TrustedNodeSyncTarget( + kind: TrustedNodeSyncKind.StateId, + stateId: os.paramStr(5)) + backfill = os.paramCount() > 5 and os.paramStr(6) == "true" waitFor doTrustedNodeSync( getRuntimeConfig(some os.paramStr(1)), os.paramStr(2), os.paramStr(3), - os.paramStr(4), os.paramStr(5), backfill, false, true) + os.paramStr(4), syncTarget, backfill, false, true) From 3eed77c52d90edacaa985fed996651dce1aac5b4 Mon Sep 17 00:00:00 2001 From: Etan Kissling Date: Wed, 15 Mar 2023 15:07:22 +0100 Subject: [PATCH 2/8] fix --- beacon_chain/trusted_node_sync.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beacon_chain/trusted_node_sync.nim b/beacon_chain/trusted_node_sync.nim index b3028a84c6..9f5e6cf1b5 100644 --- a/beacon_chain/trusted_node_sync.nim +++ b/beacon_chain/trusted_node_sync.nim @@ -350,7 +350,7 @@ proc doTrustedNodeSync*( if stateRoot.isSome: if state[].getStateRoot() != stateRoot.get: - error "Checkpoint state root has incorrect root!", + error "Checkpoint state has incorrect root!", expectedStateRoot = stateRoot.get, actualStateRoot = state[].getStateRoot() quit 1 From dd33c284cd45d79e8fa20af9f3cd953438344b52 Mon Sep 17 00:00:00 2001 From: Etan Kissling Date: Thu, 6 Apr 2023 20:45:47 +0200 Subject: [PATCH 3/8] workaround for `valueOr` limitations --- .../eth2_apis/rest_light_client_calls.nim | 55 +++++++++++-------- beacon_chain/trusted_node_sync.nim | 11 ++-- 2 files changed, 39 insertions(+), 27 deletions(-) diff --git a/beacon_chain/spec/eth2_apis/rest_light_client_calls.nim b/beacon_chain/spec/eth2_apis/rest_light_client_calls.nim index 2aa1601e9c..1ac6c2593b 100644 --- a/beacon_chain/spec/eth2_apis/rest_light_client_calls.nim +++ b/beacon_chain/spec/eth2_apis/rest_light_client_calls.nim @@ -44,8 +44,10 @@ proc decodeHttpLightClientObject[T: SomeForkedLightClientObject]( contentType: Opt[ContentTypeData], consensusFork: ConsensusFork, cfg: RuntimeConfig): T {.raises: [RestError].} = - let mediaType = decodeMediaType(contentType).valueOr(resError): - raise newException(RestError, $resError) + let mediaTypeRes = decodeMediaType(contentType) + if mediaTypeRes.isErr: + raise newException(RestError, $mediaTypeRes.error) + template mediaType: auto = mediaTypeRes.get if mediaType == OctetStreamMediaType: try: @@ -62,8 +64,10 @@ proc decodeHttpLightClientObject[T: SomeForkedLightClientObject]( raiseRestDecodingBytesError(cstring("Malformed data: " & $exc.msg)) if mediaType == ApplicationJsonMediaType: - let obj = decodeBytes(T, data, contentType).valueOr(resError): - raiseRestDecodingBytesError(resError) + let objRes = decodeBytes(T, data, contentType) + if objRes.isErr: + raiseRestDecodingBytesError(objRes.error) + template obj: auto = objRes.get obj.checkForkConsistency(cfg, consensusFork) return obj @@ -75,8 +79,10 @@ proc decodeHttpLightClientObjects[S: seq[SomeForkedLightClientObject]]( contentType: Opt[ContentTypeData], cfg: RuntimeConfig, forkDigests: ref ForkDigests): S {.raises: [RestError].} = - let mediaType = decodeMediaType(contentType).valueOr(resError): - raise newException(RestError, $resError) + let mediaTypeRes = decodeMediaType(contentType) + if mediaTypeRes.isErr: + raise newException(RestError, $mediaTypeRes.error) + template mediaType: auto = mediaTypeRes.get if mediaType == OctetStreamMediaType: let l = data.len @@ -127,11 +133,13 @@ proc decodeHttpLightClientObjects[S: seq[SomeForkedLightClientObject]]( return res if mediaType == ApplicationJsonMediaType: - let res = decodeBytes(S, data, contentType).valueOr(resError): - raiseRestDecodingBytesError(resError) - for obj in res: + let objsRes = decodeBytes(S, data, contentType) + if objsRes.isErr: + raiseRestDecodingBytesError(objsRes.error) + template objs: auto = objsRes.get + for obj in objs: obj.checkForkConsistency(cfg) - return res + return objs raise newException(RestError, "Unsupported content-type") @@ -158,11 +166,12 @@ proc getLightClientBootstrap*( return case resp.status of 200: - let consensusFork = decodeEthConsensusVersion( - resp.headers.getString("eth-consensus-version")).valueOr(resError): - raiseRestDecodingBytesError(resError) + let consensusForkRes = decodeEthConsensusVersion( + resp.headers.getString("eth-consensus-version")) + if consensusForkRes.isErr: + raiseRestDecodingBytesError(consensusForkRes.error) ForkedLightClientBootstrap.decodeHttpLightClientObject( - data, resp.contentType, consensusFork, cfg) + data, resp.contentType, consensusForkRes.get, cfg) of 404: default(ForkedLightClientBootstrap) of 400, 406, 500: @@ -239,11 +248,12 @@ proc getLightClientFinalityUpdate*( return case resp.status of 200: - let consensusFork = decodeEthConsensusVersion( - resp.headers.getString("eth-consensus-version")).valueOr(resError): - raiseRestDecodingBytesError(resError) + let consensusForkRes = decodeEthConsensusVersion( + resp.headers.getString("eth-consensus-version")) + if consensusForkRes.isErr: + raiseRestDecodingBytesError(consensusForkRes.error) ForkedLightClientFinalityUpdate.decodeHttpLightClientObject( - data, resp.contentType, consensusFork, cfg) + data, resp.contentType, consensusForkRes.get, cfg) of 404: default(ForkedLightClientFinalityUpdate) of 406, 500: @@ -280,11 +290,12 @@ proc getLightClientOptimisticUpdate*( return case resp.status of 200: - let consensusFork = decodeEthConsensusVersion( - resp.headers.getString("eth-consensus-version")).valueOr(resError): - raiseRestDecodingBytesError(resError) + let consensusForkRes = decodeEthConsensusVersion( + resp.headers.getString("eth-consensus-version")) + if consensusForkRes.isErr: + raiseRestDecodingBytesError(consensusForkRes.error) ForkedLightClientOptimisticUpdate.decodeHttpLightClientObject( - data, resp.contentType, consensusFork, cfg) + data, resp.contentType, consensusForkRes.get, cfg) of 404: default(ForkedLightClientOptimisticUpdate) of 406, 500: diff --git a/beacon_chain/trusted_node_sync.nim b/beacon_chain/trusted_node_sync.nim index 9f5e6cf1b5..916afd7b14 100644 --- a/beacon_chain/trusted_node_sync.nim +++ b/beacon_chain/trusted_node_sync.nim @@ -200,12 +200,13 @@ proc doTrustedNodeSync*( quit 1 bootstrap.migrateToDataFork(lcDataFork) - var store = + var storeRes = initialize_light_client_store( - trustedBlockRoot, bootstrap.forky(lcDataFork), cfg - ).valueOr(resError): - error "`initialize_light_client_store` failed", resError - quit 1 + trustedBlockRoot, bootstrap.forky(lcDataFork), cfg) + if storeRes.isErr: + error "`initialize_light_client_store` failed", err = storeRes.error + quit 1 + template store: auto = storeRes.get store.trackBestViableCheckpoint() while true: From 4959f7acc589342c806ddd0d4698b71f8ef524df Mon Sep 17 00:00:00 2001 From: Etan Kissling Date: Wed, 12 Apr 2023 19:33:14 +0200 Subject: [PATCH 4/8] reduce magic numbers --- .../spec/eth2_apis/rest_light_client_calls.nim | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/beacon_chain/spec/eth2_apis/rest_light_client_calls.nim b/beacon_chain/spec/eth2_apis/rest_light_client_calls.nim index 1ac6c2593b..2fdd22ddb4 100644 --- a/beacon_chain/spec/eth2_apis/rest_light_client_calls.nim +++ b/beacon_chain/spec/eth2_apis/rest_light_client_calls.nim @@ -91,13 +91,15 @@ proc decodeHttpLightClientObjects[S: seq[SomeForkedLightClientObject]]( o = 0 while l - o != 0: # response_chunk_len - if l - o < 8: + type lType = uint64 + const lLen = sizeof lType # 8 + if l - o < lLen: raiseRestDecodingBytesError("Malformed data: Incomplete length") - let responseChunkLen = uint64.fromBytesLE data.toOpenArray(o, o + 8 - 1) - o = o + 8 + let responseChunkLen = lType.fromBytesLE data.toOpenArray(o, o + lLen - 1) + o = o + lLen # response_chunk - if responseChunkLen > int.high.uint64: + if responseChunkLen > int.high.lType: raiseRestDecodingBytesError("Malformed data: Unsupported length") if l - o < responseChunkLen.int: raiseRestDecodingBytesError("Malformed data: Incomplete chunk") @@ -107,7 +109,8 @@ proc decodeHttpLightClientObjects[S: seq[SomeForkedLightClientObject]]( o += responseChunkLen.int # context - if responseChunkLen < 4: + const dLen = sizeof ForkDigest # 4 + if responseChunkLen < dLen.lType: raiseRestDecodingBytesError("Malformed data: Incomplete context") let context = ForkDigest [ @@ -122,7 +125,7 @@ proc decodeHttpLightClientObjects[S: seq[SomeForkedLightClientObject]]( type T = typeof(res[0]) var obj = T(kind: lcDataFork) obj.forky(lcDataFork) = SSZ.decode( - data.toOpenArray(begin + 4, after - 1), T.Forky(lcDataFork)) + data.toOpenArray(begin + dLen, after - 1), T.Forky(lcDataFork)) obj.checkForkConsistency(cfg, consensusFork) res.add obj else: From d967655674939997919611414c40ec7a143012be Mon Sep 17 00:00:00 2001 From: Etan Kissling Date: Wed, 12 Apr 2023 19:36:15 +0200 Subject: [PATCH 5/8] digest len > context len for readability --- beacon_chain/spec/eth2_apis/rest_light_client_calls.nim | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/beacon_chain/spec/eth2_apis/rest_light_client_calls.nim b/beacon_chain/spec/eth2_apis/rest_light_client_calls.nim index 2fdd22ddb4..6bc64ef551 100644 --- a/beacon_chain/spec/eth2_apis/rest_light_client_calls.nim +++ b/beacon_chain/spec/eth2_apis/rest_light_client_calls.nim @@ -109,8 +109,8 @@ proc decodeHttpLightClientObjects[S: seq[SomeForkedLightClientObject]]( o += responseChunkLen.int # context - const dLen = sizeof ForkDigest # 4 - if responseChunkLen < dLen.lType: + const cLen = sizeof ForkDigest # 4 + if responseChunkLen < cLen.lType: raiseRestDecodingBytesError("Malformed data: Incomplete context") let context = ForkDigest [ @@ -125,7 +125,7 @@ proc decodeHttpLightClientObjects[S: seq[SomeForkedLightClientObject]]( type T = typeof(res[0]) var obj = T(kind: lcDataFork) obj.forky(lcDataFork) = SSZ.decode( - data.toOpenArray(begin + dLen, after - 1), T.Forky(lcDataFork)) + data.toOpenArray(begin + cLen, after - 1), T.Forky(lcDataFork)) obj.checkForkConsistency(cfg, consensusFork) res.add obj else: From 51ead479359a7009abb9bd0756d877705fdb7940 Mon Sep 17 00:00:00 2001 From: Etan Kissling Date: Wed, 12 Apr 2023 19:56:39 +0200 Subject: [PATCH 6/8] move `cstring` conversion to caller --- beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim | 6 +++--- beacon_chain/spec/eth2_apis/rest_light_client_calls.nim | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim b/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim index c65fb3371c..c2c341e735 100644 --- a/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim +++ b/beacon_chain/spec/eth2_apis/eth2_rest_serialization.nim @@ -29,18 +29,18 @@ from web3/ethtypes import BlockHash export ethtypes.BlockHash func decodeMediaType*( - contentType: Opt[ContentTypeData]): Result[MediaType, cstring] = + contentType: Opt[ContentTypeData]): Result[MediaType, string] = if contentType.isNone or isWildCard(contentType.get.mediaType): return err("Missing or incorrect Content-Type") ok contentType.get.mediaType func decodeEthConsensusVersion*( - value: string): Result[ConsensusFork, cstring] = + value: string): Result[ConsensusFork, string] = let normalizedValue = value.toLowerAscii() for consensusFork in ConsensusFork: if normalizedValue == ($consensusFork).toLowerAscii(): return ok consensusFork - err(cstring("Unsupported Eth-Consensus-Version: " & value)) + err("Unsupported Eth-Consensus-Version: " & value) Json.createFlavor RestJson diff --git a/beacon_chain/spec/eth2_apis/rest_light_client_calls.nim b/beacon_chain/spec/eth2_apis/rest_light_client_calls.nim index 6bc64ef551..063d636c5c 100644 --- a/beacon_chain/spec/eth2_apis/rest_light_client_calls.nim +++ b/beacon_chain/spec/eth2_apis/rest_light_client_calls.nim @@ -46,7 +46,7 @@ proc decodeHttpLightClientObject[T: SomeForkedLightClientObject]( cfg: RuntimeConfig): T {.raises: [RestError].} = let mediaTypeRes = decodeMediaType(contentType) if mediaTypeRes.isErr: - raise newException(RestError, $mediaTypeRes.error) + raise newException(RestError, mediaTypeRes.error) template mediaType: auto = mediaTypeRes.get if mediaType == OctetStreamMediaType: @@ -81,7 +81,7 @@ proc decodeHttpLightClientObjects[S: seq[SomeForkedLightClientObject]]( forkDigests: ref ForkDigests): S {.raises: [RestError].} = let mediaTypeRes = decodeMediaType(contentType) if mediaTypeRes.isErr: - raise newException(RestError, $mediaTypeRes.error) + raise newException(RestError, mediaTypeRes.error) template mediaType: auto = mediaTypeRes.get if mediaType == OctetStreamMediaType: From 53961d1bc5e22a86b501ded9a0efde317f8643ef Mon Sep 17 00:00:00 2001 From: Etan Kissling Date: Wed, 12 Apr 2023 19:56:58 +0200 Subject: [PATCH 7/8] avoid abbreviations --- .../eth2_apis/rest_light_client_calls.nim | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/beacon_chain/spec/eth2_apis/rest_light_client_calls.nim b/beacon_chain/spec/eth2_apis/rest_light_client_calls.nim index 063d636c5c..935cb2e582 100644 --- a/beacon_chain/spec/eth2_apis/rest_light_client_calls.nim +++ b/beacon_chain/spec/eth2_apis/rest_light_client_calls.nim @@ -91,15 +91,16 @@ proc decodeHttpLightClientObjects[S: seq[SomeForkedLightClientObject]]( o = 0 while l - o != 0: # response_chunk_len - type lType = uint64 - const lLen = sizeof lType # 8 - if l - o < lLen: + type chunkLenType = uint64 + const chunkLenLen = sizeof chunkLenType # 8 + if l - o < chunkLenLen: raiseRestDecodingBytesError("Malformed data: Incomplete length") - let responseChunkLen = lType.fromBytesLE data.toOpenArray(o, o + lLen - 1) - o = o + lLen + let responseChunkLen = chunkLenType.fromBytesLE( + data.toOpenArray(o, o + chunkLenLen - 1)) + o = o + chunkLenLen # response_chunk - if responseChunkLen > int.high.lType: + if responseChunkLen > int.high.chunkLenType: raiseRestDecodingBytesError("Malformed data: Unsupported length") if l - o < responseChunkLen.int: raiseRestDecodingBytesError("Malformed data: Incomplete chunk") @@ -109,8 +110,8 @@ proc decodeHttpLightClientObjects[S: seq[SomeForkedLightClientObject]]( o += responseChunkLen.int # context - const cLen = sizeof ForkDigest # 4 - if responseChunkLen < cLen.lType: + const contextLen = sizeof ForkDigest # 4 + if responseChunkLen < contextLen.chunkLenType: raiseRestDecodingBytesError("Malformed data: Incomplete context") let context = ForkDigest [ @@ -125,7 +126,8 @@ proc decodeHttpLightClientObjects[S: seq[SomeForkedLightClientObject]]( type T = typeof(res[0]) var obj = T(kind: lcDataFork) obj.forky(lcDataFork) = SSZ.decode( - data.toOpenArray(begin + cLen, after - 1), T.Forky(lcDataFork)) + data.toOpenArray(begin + contextLen, after - 1), + T.Forky(lcDataFork)) obj.checkForkConsistency(cfg, consensusFork) res.add obj else: @@ -172,7 +174,7 @@ proc getLightClientBootstrap*( let consensusForkRes = decodeEthConsensusVersion( resp.headers.getString("eth-consensus-version")) if consensusForkRes.isErr: - raiseRestDecodingBytesError(consensusForkRes.error) + raiseRestDecodingBytesError(cstring(consensusForkRes.error)) ForkedLightClientBootstrap.decodeHttpLightClientObject( data, resp.contentType, consensusForkRes.get, cfg) of 404: @@ -254,7 +256,7 @@ proc getLightClientFinalityUpdate*( let consensusForkRes = decodeEthConsensusVersion( resp.headers.getString("eth-consensus-version")) if consensusForkRes.isErr: - raiseRestDecodingBytesError(consensusForkRes.error) + raiseRestDecodingBytesError(cstring(consensusForkRes.error)) ForkedLightClientFinalityUpdate.decodeHttpLightClientObject( data, resp.contentType, consensusForkRes.get, cfg) of 404: @@ -296,7 +298,7 @@ proc getLightClientOptimisticUpdate*( let consensusForkRes = decodeEthConsensusVersion( resp.headers.getString("eth-consensus-version")) if consensusForkRes.isErr: - raiseRestDecodingBytesError(consensusForkRes.error) + raiseRestDecodingBytesError(cstring(consensusForkRes.error)) ForkedLightClientOptimisticUpdate.decodeHttpLightClientObject( data, resp.contentType, consensusForkRes.get, cfg) of 404: From 69b319e889114cdd77446cdfc428cc22ef7aea3d Mon Sep 17 00:00:00 2001 From: Etan Kissling Date: Fri, 14 Apr 2023 15:48:03 +0200 Subject: [PATCH 8/8] `return` codestyle --- .../eth2_apis/rest_light_client_calls.nim | 162 +++++++++--------- 1 file changed, 83 insertions(+), 79 deletions(-) diff --git a/beacon_chain/spec/eth2_apis/rest_light_client_calls.nim b/beacon_chain/spec/eth2_apis/rest_light_client_calls.nim index 935cb2e582..b26d2fece3 100644 --- a/beacon_chain/spec/eth2_apis/rest_light_client_calls.nim +++ b/beacon_chain/spec/eth2_apis/rest_light_client_calls.nim @@ -49,29 +49,31 @@ proc decodeHttpLightClientObject[T: SomeForkedLightClientObject]( raise newException(RestError, mediaTypeRes.error) template mediaType: auto = mediaTypeRes.get - if mediaType == OctetStreamMediaType: - try: - withLcDataFork(lcDataForkAtConsensusFork(consensusFork)): - when lcDataFork > LightClientDataFork.None: - var obj = T(kind: lcDataFork) - obj.forky(lcDataFork) = SSZ.decode(data, T.Forky(lcDataFork)) - obj.checkForkConsistency(cfg, consensusFork) - return obj - else: - raiseRestDecodingBytesError( - cstring("Unsupported fork: " & $consensusFork)) - except SszError as exc: - raiseRestDecodingBytesError(cstring("Malformed data: " & $exc.msg)) + return + if mediaType == OctetStreamMediaType: + try: + withLcDataFork(lcDataForkAtConsensusFork(consensusFork)): + when lcDataFork > LightClientDataFork.None: + var obj = T(kind: lcDataFork) + obj.forky(lcDataFork) = SSZ.decode(data, T.Forky(lcDataFork)) + obj.checkForkConsistency(cfg, consensusFork) + obj + else: + raiseRestDecodingBytesError( + cstring("Unsupported fork: " & $consensusFork)) + except SszError as exc: + raiseRestDecodingBytesError(cstring("Malformed data: " & $exc.msg)) - if mediaType == ApplicationJsonMediaType: - let objRes = decodeBytes(T, data, contentType) - if objRes.isErr: - raiseRestDecodingBytesError(objRes.error) - template obj: auto = objRes.get - obj.checkForkConsistency(cfg, consensusFork) - return obj + elif mediaType == ApplicationJsonMediaType: + let objRes = decodeBytes(T, data, contentType) + if objRes.isErr: + raiseRestDecodingBytesError(objRes.error) + template obj: auto = objRes.get + obj.checkForkConsistency(cfg, consensusFork) + obj - raise newException(RestError, "Unsupported content-type") + else: + raise newException(RestError, "Unsupported content-type") proc decodeHttpLightClientObjects[S: seq[SomeForkedLightClientObject]]( x: typedesc[S], @@ -84,69 +86,71 @@ proc decodeHttpLightClientObjects[S: seq[SomeForkedLightClientObject]]( raise newException(RestError, mediaTypeRes.error) template mediaType: auto = mediaTypeRes.get - if mediaType == OctetStreamMediaType: - let l = data.len - var - res: S - o = 0 - while l - o != 0: - # response_chunk_len - type chunkLenType = uint64 - const chunkLenLen = sizeof chunkLenType # 8 - if l - o < chunkLenLen: - raiseRestDecodingBytesError("Malformed data: Incomplete length") - let responseChunkLen = chunkLenType.fromBytesLE( - data.toOpenArray(o, o + chunkLenLen - 1)) - o = o + chunkLenLen + return + if mediaType == OctetStreamMediaType: + let l = data.len + var + res: S + o = 0 + while l - o != 0: + # response_chunk_len + type chunkLenType = uint64 + const chunkLenLen = sizeof chunkLenType # 8 + if l - o < chunkLenLen: + raiseRestDecodingBytesError("Malformed data: Incomplete length") + let responseChunkLen = chunkLenType.fromBytesLE( + data.toOpenArray(o, o + chunkLenLen - 1)) + o = o + chunkLenLen - # response_chunk - if responseChunkLen > int.high.chunkLenType: - raiseRestDecodingBytesError("Malformed data: Unsupported length") - if l - o < responseChunkLen.int: - raiseRestDecodingBytesError("Malformed data: Incomplete chunk") - let - begin = o - after = o + responseChunkLen.int - o += responseChunkLen.int + # response_chunk + if responseChunkLen > int.high.chunkLenType: + raiseRestDecodingBytesError("Malformed data: Unsupported length") + if l - o < responseChunkLen.int: + raiseRestDecodingBytesError("Malformed data: Incomplete chunk") + let + begin = o + after = o + responseChunkLen.int + o += responseChunkLen.int - # context - const contextLen = sizeof ForkDigest # 4 - if responseChunkLen < contextLen.chunkLenType: - raiseRestDecodingBytesError("Malformed data: Incomplete context") - let - context = ForkDigest [ - data[begin + 0], data[begin + 1], data[begin + 2], data[begin + 3]] - consensusFork = forkDigests[].consensusForkForDigest(context).valueOr: - raiseRestDecodingBytesError("Malformed data: Invalid context") + # context + const contextLen = sizeof ForkDigest # 4 + if responseChunkLen < contextLen.chunkLenType: + raiseRestDecodingBytesError("Malformed data: Incomplete context") + let + context = ForkDigest [ + data[begin + 0], data[begin + 1], data[begin + 2], data[begin + 3]] + consensusFork = forkDigests[].consensusForkForDigest(context).valueOr: + raiseRestDecodingBytesError("Malformed data: Invalid context") - # payload - try: - withLcDataFork(lcDataForkAtConsensusFork(consensusFork)): - when lcDataFork > LightClientDataFork.None: - type T = typeof(res[0]) - var obj = T(kind: lcDataFork) - obj.forky(lcDataFork) = SSZ.decode( - data.toOpenArray(begin + contextLen, after - 1), - T.Forky(lcDataFork)) - obj.checkForkConsistency(cfg, consensusFork) - res.add obj - else: - raiseRestDecodingBytesError( - cstring("Unsupported fork: " & $consensusFork)) - except SszError as exc: - raiseRestDecodingBytesError(cstring("Malformed data: " & $exc.msg)) - return res + # payload + try: + withLcDataFork(lcDataForkAtConsensusFork(consensusFork)): + when lcDataFork > LightClientDataFork.None: + type T = typeof(res[0]) + var obj = T(kind: lcDataFork) + obj.forky(lcDataFork) = SSZ.decode( + data.toOpenArray(begin + contextLen, after - 1), + T.Forky(lcDataFork)) + obj.checkForkConsistency(cfg, consensusFork) + res.add obj + else: + raiseRestDecodingBytesError( + cstring("Unsupported fork: " & $consensusFork)) + except SszError as exc: + raiseRestDecodingBytesError(cstring("Malformed data: " & $exc.msg)) + res - if mediaType == ApplicationJsonMediaType: - let objsRes = decodeBytes(S, data, contentType) - if objsRes.isErr: - raiseRestDecodingBytesError(objsRes.error) - template objs: auto = objsRes.get - for obj in objs: - obj.checkForkConsistency(cfg) - return objs + elif mediaType == ApplicationJsonMediaType: + let objsRes = decodeBytes(S, data, contentType) + if objsRes.isErr: + raiseRestDecodingBytesError(objsRes.error) + template objs: auto = objsRes.get + for obj in objs: + obj.checkForkConsistency(cfg) + objs - raise newException(RestError, "Unsupported content-type") + else: + raise newException(RestError, "Unsupported content-type") proc getLightClientBootstrapPlain( block_root: Eth2Digest): RestHttpResponseRef {.