# beacon_chain # Copyright (c) 2023-2024 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 std/[json, sequtils, times], stew/saturation_arith, eth/common/[eth_types_rlp, transaction], eth/keys, eth/p2p/discoveryv5/random2, eth/rlp, eth/trie/[db, hexary], json_rpc/jsonmarshal, secp256k1, snappy, web3/[engine_api_types, eth_api_types, conversions], ../el/eth1_chain, ../spec/eth2_apis/[eth2_rest_serialization, rest_light_client_calls], ../spec/[helpers, light_client_sync], ../sync/light_client_sync_helpers, ../beacon_clock {.pragma: exported, cdecl, exportc, dynlib, raises: [].} {.pragma: exportedConst, exportc, dynlib.} proc toUnmanagedPtr[T](x: ref T): ptr T = GC_ref(x) addr x[] func asRef[T](x: ptr T): ref T = cast[ref T](x) proc destroy[T](x: ptr T) = x[].reset() GC_unref(asRef(x)) proc ETHRandomNumberCreate(): ptr HmacDrbgContext {.exported.} = ## Creates a new cryptographically secure random number generator. ## ## * The cryptographically secure random number generator must be destroyed ## with `ETHRandomNumberDestroy` once no longer needed, to release memory. ## ## Returns: ## * Pointer to an initialized cryptographically secure random number ## generator context - If successful. ## * `NULL` - If an error occurred. HmacDrbgContext.new().toUnmanagedPtr() proc ETHRandomNumberDestroy(rng: ptr HmacDrbgContext) {.exported.} = ## Destroys a cryptographically secure random number generator. ## ## * The cryptographically secure random number generator ## must no longer be used after destruction. ## ## Parameters: ## * `rng` - Cryptographically secure random number generator. rng.destroy() proc ETHConsensusConfigCreateFromYaml( configFileContent: cstring): ptr RuntimeConfig {.exported.} = ## Creates a new Ethereum Consensus Layer network configuration ## based on the given `config.yaml` file content from an ## Ethereum network definition. ## ## * The Ethereum Consensus Layer network configuration must be destroyed with ## `ETHConsensusConfigDestroy` once no longer needed, to release memory. ## ## Parameters: ## * `configFileContent` - `config.yaml` file content. NULL-terminated. ## ## Returns: ## * Pointer to an initialized Ethereum Consensus Layer network configuration ## based on the given `config.yaml` file content - If successful. ## * `NULL` - If the given `config.yaml` is malformed or incompatible. ## ## See: ## * https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.2/configs/README.md let cfg = RuntimeConfig.new() try: cfg[] = readRuntimeConfig($configFileContent, "config.yaml")[0] except IOError, PresetFileError, PresetIncompatibleError: return nil cfg.toUnmanagedPtr() proc ETHConsensusConfigDestroy(cfg: ptr RuntimeConfig) {.exported.} = ## Destroys an Ethereum Consensus Layer network configuration. ## ## * The Ethereum Consensus Layer network configuration ## must no longer be used after destruction. ## ## Parameters: ## * `cfg` - Ethereum Consensus Layer network configuration. cfg.destroy() func ETHConsensusConfigGetConsensusVersionAtEpoch( cfg: ptr RuntimeConfig, epoch: cint): cstring {.exported.} = ## Returns the expected `Eth-Consensus-Version` for a given `epoch`. ## ## * The returned `Eth-Consensus-Version` is statically allocated. ## It must neither be released nor written to. ## ## Parameters: ## * `cfg` - Ethereum Consensus Layer network configuration. ## * `epoch` - Epoch number for which to obtain `Eth-Consensus-Version` ## ## Returns: ## * Expected `Eth-Consensus-Version` for the given `epoch`. NULL-terminated. ## ## See: ## * https://github.com/ethereum/beacon-APIs/blob/v2.4.1/beacon-node-oapi.yaml#L419 withConsensusFork(cfg[].consensusForkAtEpoch(epoch.Epoch)): const consensusVersion: cstring = consensusFork.toString() consensusVersion proc ETHBeaconStateCreateFromSsz( cfg: ptr RuntimeConfig, consensusVersion: cstring, sszBytes: ptr UncheckedArray[byte], numSszBytes: cint): ptr ForkedHashedBeaconState {.exported.} = ## Creates a new beacon state based on its SSZ encoded representation. ## ## * The beacon state must be destroyed with `ETHBeaconStateDestroy` ## once no longer needed, to release memory. ## ## * When loading a `genesis.ssz` file from an Ethereum network definition, ## use `ETHConsensusConfigGetConsensusVersionAtEpoch` with `epoch = 0` ## to determine the correct `consensusVersion`. ## ## Parameters: ## * `cfg` - Ethereum Consensus Layer network configuration. ## * `consensusVersion` - `Eth-Consensus-Version` for the given `sszBytes`. ## * `sszBytes` - Buffer with SSZ encoded beacon state representation. ## * `numSszBytes` - Length of buffer. ## ## Returns: ## * Pointer to an initialized beacon state based on the given SSZ encoded ## representation - If successful. ## * `NULL` - If the given `sszBytes` is malformed. ## ## See: ## * https://github.com/ethereum/consensus-specs/blob/v1.4.0/specs/phase0/beacon-chain.md#beaconstate ## * https://github.com/ethereum/consensus-specs/blob/v1.4.0/specs/altair/beacon-chain.md#beaconstate ## * https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.2/specs/bellatrix/beacon-chain.md#beaconstate ## * https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.2/specs/capella/beacon-chain.md#beaconstate ## * https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.2/configs/README.md let consensusFork = ConsensusFork.decodeString($consensusVersion).valueOr: return nil state = ForkedHashedBeaconState.new() try: state[] = consensusFork.readSszForkedHashedBeaconState( sszBytes.toOpenArray(0, numSszBytes - 1)) except SszError: return nil withState(state[]): if cfg[].consensusForkAtEpoch(forkyState.data.slot.epoch) != state.kind: return nil state.toUnmanagedPtr() proc ETHBeaconStateDestroy(state: ptr ForkedHashedBeaconState) {.exported.} = ## Destroys a beacon state. ## ## * The beacon state must no longer be used after destruction. ## ## Parameters: ## * `state` - Beacon state. state.destroy() proc ETHBeaconStateCopyGenesisValidatorsRoot( state: ptr ForkedHashedBeaconState): ptr Eth2Digest {.exported.} = ## Copies the `genesis_validators_root` field from a beacon state. ## ## * The genesis validators root must be destroyed with `ETHRootDestroy` ## once no longer needed, to release memory. ## ## Parameters: ## * `state` - Beacon state. ## ## Returns: ## * Pointer to a copy of the given beacon state's genesis validators root. let genesisValRoot = Eth2Digest.new() genesisValRoot[] = getStateField(state[], genesis_validators_root) genesisValRoot.toUnmanagedPtr() proc ETHRootDestroy(root: ptr Eth2Digest) {.exported.} = ## Destroys a Merkle root. ## ## * The Merkle root must no longer be used after destruction. ## ## Parameters: ## * `root` - Merkle root. ## ## See: ## * https://github.com/ethereum/consensus-specs/blob/v1.4.0-beta.6/specs/phase0/beacon-chain.md#custom-types root.destroy() proc ETHForkDigestsCreateFromState( cfg: ptr RuntimeConfig, state: ptr ForkedHashedBeaconState): ptr ForkDigests {.exported.} = ## Creates a fork digests cache for a given beacon state. ## ## * The fork digests cache must be destroyed with `ETHForkDigestsDestroy` ## once no longer needed, to release memory. ## ## Parameters: ## * `cfg` - Ethereum Consensus Layer network configuration. ## * `state` - Beacon state. ## ## Returns: ## * Pointer to an initialized fork digests cache based on the beacon state. ## ## See: ## * https://github.com/ethereum/consensus-specs/blob/v1.4.0-beta.6/specs/phase0/beacon-chain.md#compute_fork_digest let forkDigests = ForkDigests.new() forkDigests[] = ForkDigests.init( cfg[], getStateField(state[], genesis_validators_root)) forkDigests.toUnmanagedPtr() proc ETHForkDigestsDestroy(forkDigests: ptr ForkDigests) {.exported.} = ## Destroys a fork digests cache. ## ## * The fork digests cache must no longer be used after destruction. ## ## Parameters: ## * `forkDigests` - Fork digests cache. forkDigests.destroy() proc ETHBeaconClockCreateFromState( cfg: ptr RuntimeConfig, state: ptr ForkedHashedBeaconState): ptr BeaconClock {.exported.} = ## Creates a beacon clock for a given beacon state's `genesis_time` field. ## ## * The beacon clock must be destroyed with `ETHBeaconClockDestroy` ## once no longer needed, to release memory. ## ## Parameters: ## * `cfg` - Ethereum Consensus Layer network configuration. ## * `state` - Beacon state. ## ## Returns: ## * Pointer to an initialized beacon clock based on the beacon state or ## NULL if the state contained an invalid time. let beaconClock = BeaconClock.new() beaconClock[] = BeaconClock.init(getStateField(state[], genesis_time)).valueOr: return nil beaconClock.toUnmanagedPtr() proc ETHBeaconClockDestroy(beaconClock: ptr BeaconClock) {.exported.} = ## Destroys a beacon clock. ## ## * The beacon clock must no longer be used after destruction. ## ## Parameters: ## * `beaconClock` - Beacon clock. beaconClock.destroy() proc ETHBeaconClockGetSlot(beaconClock: ptr BeaconClock): cint {.exported.} = ## Indicates the slot number for the current wall clock time. ## ## Parameters: ## * `beaconClock` - Beacon clock. ## ## Returns: ## * Slot number for the current wall clock time - If genesis has occurred. ## * `0` - If genesis is still pending. ## ## See: ## * https://github.com/ethereum/consensus-specs/blob/v1.4.0-beta.6/specs/phase0/beacon-chain.md#custom-types beaconClock[].now().slotOrZero().cint const lcDataFork = LightClientDataFork.high proc ETHLightClientStoreCreateFromBootstrap( cfg: ptr RuntimeConfig, trustedBlockRoot: ptr Eth2Digest, mediaType: cstring, consensusVersion: cstring, bootstrapBytes: ptr UncheckedArray[byte], numBootstrapBytes: cint ): ptr lcDataFork.LightClientStore {.exported.} = ## Creates a light client store from light client bootstrap data. ## The light client store is the primary object for syncing with ## an Ethereum network. ## ## * To create a light client store, the Ethereum network definition ## including the fork schedule, `genesis_time` and `genesis_validators_root` ## must be known. Furthermore, a beacon block root must be assumed trusted. ## The trusted block root should be within the weak subjectivity period, ## and its root should be from a finalized `Checkpoint`. ## ## * The REST `/eth/v1/beacon/light_client/bootstrap/{block_root}` beacon API ## may be used to obtain light client bootstrap data for a given ## trusted block root. Setting the `Accept: application/octet-stream` ## HTTP header in the request selects the more compact SSZ representation. ## ## * After creating a light client store, `ETHLightClientStoreGetNextSyncTask` ## may be used to determine what further REST beacon API requests to perform ## for keeping the light client store in sync with the Ethereum network. ## ## * Once synced the REST `/eth/v1/events?topics=light_client_finality_update` ## `&topics=light_client_optimistic_update` beacon API provides the most ## recent light client data. Data from this endpoint is always JSON encoded ## and may be processed with `ETHLightClientStoreProcessFinalityUpdate` and ## `ETHLightClientStoreProcessOptimisticUpdate`. ## ## * The light client store must be destroyed with ## `ETHLightClientStoreDestroy` once no longer needed, to release memory. ## ## Parameters: ## * `cfg` - Ethereum Consensus Layer network configuration. ## * `trustedBlockRoot` - Trusted block root. ## * `mediaType` - HTTP `Content-Type` associated with `bootstrapBytes`; ## `application/json` for JSON, `application/octet-stream` for SSZ. ## * `consensusVersion` - HTTP `Eth-Consensus-Version` response header ## associated with `bootstrapBytes`. ## * `bootstrapBytes` - Buffer with encoded light client bootstrap data. ## * `numBootstrapBytes` - Length of buffer. ## ## Returns: ## * Pointer to an initialized light client store based on the given ## light client bootstrap data - If successful. ## * `NULL` - If the given `bootstrapBytes` is malformed or incompatible. ## ## See: ## * https://ethereum.github.io/beacon-APIs/?urls.primaryName=v2.4.1#/Beacon/getLightClientBootstrap ## * https://ethereum.github.io/beacon-APIs/?urls.primaryName=v2.4.1#/Events/eventstream ## * https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.2/specs/altair/light-client/light-client.md ## * https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.2/specs/phase0/weak-subjectivity.md#weak-subjectivity-period let mediaType = MediaType.init($mediaType) consensusFork = ConsensusFork.decodeString($consensusVersion).valueOr: return nil var bootstrap = try: ForkedLightClientBootstrap.decodeHttpLightClientObject( bootstrapBytes.toOpenArray(0, numBootstrapBytes - 1), mediaType, consensusFork, cfg[]) except RestError: return nil doAssert bootstrap.kind > LightClientDataFork.None bootstrap.migrateToDataFork(lcDataFork) let store = lcDataFork.LightClientStore.new() store[] = initialize_light_client_store( trustedBlockRoot[], bootstrap.forky(lcDataFork), cfg[]).valueOr: return nil store.toUnmanagedPtr() proc ETHLightClientStoreDestroy( store: ptr lcDataFork.LightClientStore) {.exported.} = ## Destroys a light client store. ## ## * The light client store must no longer be used after destruction. ## ## Parameters: ## * `store` - Light client store. store.destroy() let ## Sync task to fulfill using `/eth/v1/beacon/light_client/updates`. kETHLcSyncKind_UpdatesByRange {.exportedConst.} = LcSyncKind.UpdatesByRange.cint ## Sync task to fulfill using `/eth/v1/beacon/light_client/finality_update`. kETHLcSyncKind_FinalityUpdate {.exportedConst.} = LcSyncKind.FinalityUpdate.cint ## Sync task to fulfill using `/eth/v1/beacon/light_client/optimistic_update`. kETHLcSyncKind_OptimisticUpdate {.exportedConst.} = LcSyncKind.OptimisticUpdate.cint proc ETHLightClientStoreGetNextSyncTask( store: ptr lcDataFork.LightClientStore, beaconClock: ptr BeaconClock, startPeriod #[out]#: ptr cint, count #[out]#: ptr cint): cint {.exported.} = ## Obtains the next task for keeping a light client store in sync ## with the Ethereum network. ## ## * When using the REST beacon API to fulfill a sync task, setting the ## `Accept: application/octet-stream` HTTP header in the request ## selects the more compact SSZ representation. ## ## * After fetching the requested light client data and processing it with the ## appropriate handler, `ETHLightClientStoreGetMillisecondsToNextSyncTask` ## may be used to obtain a delay until a new sync task becomes available. ## Once the delay is reached, call `ETHLightClientStoreGetNextSyncTask` ## again to obtain the next sync task. ## ## * Once synced the REST `/eth/v1/events?topics=light_client_finality_update` ## `&topics=light_client_optimistic_update` beacon API provides the most ## recent light client data. Data from this endpoint is always JSON encoded ## and may be processed with `ETHLightClientStoreProcessFinalityUpdate` and ## `ETHLightClientStoreProcessOptimisticUpdate`. Events may be processed at ## any time and do not require re-computing the delay until next sync task ## with `ETHLightClientStoreGetMillisecondsToNextSyncTask`. ## ## Parameters: ## * `store` - Light client store. ## * `beaconClock` - Beacon clock. ## * `startPeriod` [out] - `start_period` query parameter, if applicable. ## * `count` [out] - `count` query parameter, if applicable. ## ## Returns: ## * `kETHLcSyncKind_UpdatesByRange` - If the next sync task is fulfillable ## using REST `/eth/v1/beacon/light_client/updates` beacon API. ## The `startPeriod` and `count` parameters are filled, and to be passed to ## `/eth/v1/beacon/light_client/updates?start_period={startPeriod}` ## `&count={count}`. ## Process the response with `ETHLightClientStoreProcessUpdatesByRange`. ## * `kETHLcSyncKind_FinalityUpdate` - If the next sync task is fulfillable ## using REST `/eth/v1/beacon/light_client/finality_update` beacon API. ## Process the response with `ETHLightClientStoreProcessFinalityUpdate`. ## The `startPeriod` and `count` parameters are unused for this sync task. ## * `kETHLcSyncKind_OptimisticUpdate` - If the next sync task is fulfillable ## using REST `/eth/v1/beacon/light_client/optimistic_update` beacon API. ## Process the response with `ETHLightClientStoreProcessOptimisticUpdate`. ## The `startPeriod` and `count` parameters are unused for this sync task. ## ## See: ## * https://ethereum.github.io/beacon-APIs/?urls.primaryName=v2.4.1#/Beacon/getLightClientUpdatesByRange ## * https://ethereum.github.io/beacon-APIs/?urls.primaryName=v2.4.1#/Beacon/getLightClientFinalityUpdate ## * https://ethereum.github.io/beacon-APIs/?urls.primaryName=v2.4.1#/Beacon/getLightClientOptimisticUpdate ## * https://ethereum.github.io/beacon-APIs/?urls.primaryName=v2.4.1#/Events/eventstream let syncTask = nextLightClientSyncTask( current = beaconClock[].now().slotOrZero().sync_committee_period, finalized = store[].finalized_header.beacon.slot.sync_committee_period, optimistic = store[].optimistic_header.beacon.slot.sync_committee_period, isNextSyncCommitteeKnown = store[].is_next_sync_committee_known) case syncTask.kind of LcSyncKind.UpdatesByRange: startPeriod[] = syncTask.startPeriod.cint count[] = syncTask.count.cint of LcSyncKind.FinalityUpdate: startPeriod[] = 0 count[] = 0 of LcSyncKind.OptimisticUpdate: startPeriod[] = 0 count[] = 0 syncTask.kind.cint proc ETHLightClientStoreGetMillisecondsToNextSyncTask( store: ptr lcDataFork.LightClientStore, rng: ptr HmacDrbgContext, beaconClock: ptr BeaconClock, latestProcessResult: cint): cint {.exported.} = ## Indicates the delay until a new light client sync task becomes available. ## Once the delay is reached, call `ETHLightClientStoreGetNextSyncTask` ## to obtain the next sync task. ## ## Parameters: ## * `store` - Light client store. ## * `rng` - Cryptographically secure random number generator. ## * `beaconClock` - Beacon clock. ## * `latestProcessResult` - Latest sync task processing result, i.e., ## the return value of `ETHLightClientStoreProcessUpdatesByRange`, ## `ETHLightClientStoreProcessFinalityUpdate`, or ## `ETHLightClientStoreProcessOptimisticUpdate`, for latest task. ## If the data for the sync task could not be fetched, set to `1`. ## ## Returns: ## * Number of milliseconds until `ETHLightClientStoreGetNextSyncTask` ## should be called again to obtain the next light client sync task. asRef(rng).nextLcSyncTaskDelay( wallTime = beaconClock[].now(), finalized = store[].finalized_header.beacon.slot.sync_committee_period, optimistic = store[].optimistic_header.beacon.slot.sync_committee_period, isNextSyncCommitteeKnown = store[].is_next_sync_committee_known, didLatestSyncTaskProgress = (latestProcessResult == 0)).milliseconds.cint proc ETHLightClientStoreProcessUpdatesByRange( store: ptr lcDataFork.LightClientStore, cfg: ptr RuntimeConfig, forkDigests: ptr ForkDigests, genesisValRoot: ptr Eth2Digest, beaconClock: ptr BeaconClock, startPeriod: cint, count: cint, mediaType: cstring, updatesBytes: ptr UncheckedArray[byte], numUpdatesBytes: cint): cint {.exported.} = ## Processes light client update data. ## ## * This processes the response data for a sync task of kind ## `kETHLcSyncKind_UpdatesByRange`, as indicated by ## `ETHLightClientStoreGetNextSyncTask`. After processing, call ## `ETHLightClientStoreGetMillisecondsToNextSyncTask` to obtain a delay ## until a new sync task becomes available. ## ## Parameters: ## * `store` - Light client store. ## * `cfg` - Ethereum Consensus Layer network configuration. ## * `forkDigests` - Fork digests cache. ## * `genesisValRoot` - Genesis validators root. ## * `beaconClock` - Beacon clock. ## * `startPeriod` - `startPeriod` parameter associated with the sync task. ## * `count` - `count` parameter associated with the sync task. ## * `mediaType` - HTTP `Content-Type` associated with `updatesBytes`; ## `application/json` for JSON, `application/octet-stream` for SSZ. ## * `updatesBytes` - Buffer with encoded light client update data. ## * `numUpdatesBytes` - Length of buffer. ## ## Returns: ## * `0` - If the given `updatesBytes` is valid and sync did progress. ## * `1` - If the given `updatesBytes` is malformed or incompatible. ## * `2` - If the given `updatesBytes` did not advance sync progress. ## ## See: ## * https://ethereum.github.io/beacon-APIs/?urls.primaryName=v2.4.1#/Beacon/getLightClientUpdatesByRange let wallTime = beaconClock[].now() currentSlot = wallTime.slotOrZero() mediaType = MediaType.init($mediaType) var updates = try: seq[ForkedLightClientUpdate].decodeHttpLightClientObjects( updatesBytes.toOpenArray(0, numUpdatesBytes - 1), mediaType, cfg[], asRef(forkDigests)) except RestError: return 1 let e = updates.checkLightClientUpdates( startPeriod.SyncCommitteePeriod, count.uint64) if e.isErr: return 1 var didProgress = false for i in 0 ..< updates.len: doAssert updates[i].kind > LightClientDataFork.None updates[i].migrateToDataFork(lcDataFork) let res = process_light_client_update( store[], updates[i].forky(lcDataFork), currentSlot, cfg[], genesisValRoot[]) if res.isOk: didProgress = true else: case res.error of VerifierError.MissingParent: break of VerifierError.Duplicate: discard of VerifierError.UnviableFork: break of VerifierError.Invalid: return 1 if not didProgress: return 2 0 proc ETHLightClientStoreProcessFinalityUpdate( store: ptr lcDataFork.LightClientStore, cfg: ptr RuntimeConfig, forkDigests: ptr ForkDigests, genesisValRoot: ptr Eth2Digest, beaconClock: ptr BeaconClock, mediaType: cstring, consensusVersion #[optional]#: cstring, finUpdateBytes: ptr UncheckedArray[byte], numFinUpdateBytes: cint): cint {.exported.} = ## Processes light client finality update data. ## ## * This processes the response data for a sync task of kind ## `kETHLcSyncKind_FinalityUpdate`, as indicated by ## `ETHLightClientStoreGetNextSyncTask`. After processing, call ## `ETHLightClientStoreGetMillisecondsToNextSyncTask` to obtain a delay ## until a new sync task becomes available. ## ## * This also processes event data from the REST ## `/eth/v1/events?topics=light_client_finality_update` beacon API. ## Set `mediaType` to `application/json`, and `consensusVersion` to `NULL`. ## Events may be processed at any time, it is not necessary to call ## `ETHLightClientStoreGetMillisecondsToNextSyncTask`. ## ## Parameters: ## * `store` - Light client store. ## * `cfg` - Ethereum Consensus Layer network configuration. ## * `forkDigests` - Fork digests cache. ## * `genesisValRoot` - Genesis validators root. ## * `beaconClock` - Beacon clock. ## * `mediaType` - HTTP `Content-Type` associated with `finUpdateBytes`; ## `application/json` for JSON, `application/octet-stream` for SSZ. ## * `consensusVersion` - HTTP `Eth-Consensus-Version` response header ## associated with `finUpdateBytes`. `NULL` when processing event. ## * `finUpdateBytes` - Buffer with encoded finality update data. ## * `numFinUpdateBytes` - Length of buffer. ## ## Returns: ## * `0` - If the given `finUpdateBytes` is valid and sync did progress. ## * `1` - If the given `finUpdateBytes` is malformed or incompatible. ## * `2` - If the given `finUpdateBytes` did not advance sync progress. ## ## See: ## * https://ethereum.github.io/beacon-APIs/?urls.primaryName=v2.4.1#/Beacon/getLightClientFinalityUpdate ## * https://ethereum.github.io/beacon-APIs/?urls.primaryName=v2.4.1#/Events/eventstream let wallTime = beaconClock[].now() currentSlot = wallTime.slotOrZero() mediaType = MediaType.init($mediaType) var finalityUpdate = try: if consensusVersion == nil: if mediaType != ApplicationJsonMediaType: return 1 ForkedLightClientFinalityUpdate.decodeJsonLightClientObject( finUpdateBytes.toOpenArray(0, numFinUpdateBytes - 1), Opt.none(ConsensusFork), cfg[]) else: let consensusFork = ConsensusFork.decodeString( $consensusVersion).valueOr: return 1 ForkedLightClientFinalityUpdate.decodeHttpLightClientObject( finUpdateBytes.toOpenArray(0, numFinUpdateBytes - 1), mediaType, consensusFork, cfg[]) except RestError: return 1 doAssert finalityUpdate.kind > LightClientDataFork.None finalityUpdate.migrateToDataFork(lcDataFork) let res = process_light_client_update( store[], finalityUpdate.forky(lcDataFork), currentSlot, cfg[], genesisValRoot[]) return if res.isOk: 0 else: case res.error of VerifierError.MissingParent: 2 of VerifierError.Duplicate: 2 of VerifierError.UnviableFork: 2 of VerifierError.Invalid: 1 proc ETHLightClientStoreProcessOptimisticUpdate( store: ptr lcDataFork.LightClientStore, cfg: ptr RuntimeConfig, forkDigests: ptr ForkDigests, genesisValRoot: ptr Eth2Digest, beaconClock: ptr BeaconClock, mediaType: cstring, consensusVersion #[optional]#: cstring, optUpdateBytes: ptr UncheckedArray[byte], numOptUpdateBytes: cint): cint {.exported.} = ## Processes light client optimistic update data. ## ## * This processes the response data for a sync task of kind ## `kETHLcSyncKind_OptimisticUpdate`, as indicated by ## `ETHLightClientStoreGetNextSyncTask`. After processing, call ## `ETHLightClientStoreGetMillisecondsToNextSyncTask` to obtain a delay ## until a new sync task becomes available. ## ## * This also processes event data from the REST ## `/eth/v1/events?topics=light_client_optimistic_update` beacon API. ## Set `mediaType` to `application/json`, and `consensusVersion` to `NULL`. ## Events may be processed at any time, it is not necessary to call ## `ETHLightClientStoreGetMillisecondsToNextSyncTask`. ## ## Parameters: ## * `store` - Light client store. ## * `cfg` - Ethereum Consensus Layer network configuration. ## * `forkDigests` - Fork digests cache. ## * `genesisValRoot` - Genesis validators root. ## * `beaconClock` - Beacon clock. ## * `mediaType` - HTTP `Content-Type` associated with `optUpdateBytes`; ## `application/json` for JSON, `application/octet-stream` for SSZ. ## * `consensusVersion` - HTTP `Eth-Consensus-Version` response header ## associated with `optUpdateBytes`. `NULL` when processing event. ## * `optUpdateBytes` - Buffer with encoded optimistic update data. ## * `numOptUpdateBytes` - Length of buffer. ## ## Returns: ## * `0` - If the given `optUpdateBytes` is valid and sync did progress. ## * `1` - If the given `optUpdateBytes` is malformed or incompatible. ## * `2` - If the given `optUpdateBytes` did not advance sync progress. ## ## See: ## * https://ethereum.github.io/beacon-APIs/?urls.primaryName=v2.4.1#/Beacon/getLightClientOptimisticUpdate ## * https://ethereum.github.io/beacon-APIs/?urls.primaryName=v2.4.1#/Events/eventstream let wallTime = beaconClock[].now() currentSlot = wallTime.slotOrZero() mediaType = MediaType.init($mediaType) var optimisticUpdate = try: if consensusVersion == nil: if mediaType != ApplicationJsonMediaType: return 1 ForkedLightClientOptimisticUpdate.decodeJsonLightClientObject( optUpdateBytes.toOpenArray(0, numOptUpdateBytes - 1), Opt.none(ConsensusFork), cfg[]) else: let consensusFork = ConsensusFork.decodeString( $consensusVersion).valueOr: return 1 ForkedLightClientOptimisticUpdate.decodeHttpLightClientObject( optUpdateBytes.toOpenArray(0, numOptUpdateBytes - 1), mediaType, consensusFork, cfg[]) except RestError: return 1 doAssert optimisticUpdate.kind > LightClientDataFork.None optimisticUpdate.migrateToDataFork(lcDataFork) let res = process_light_client_update( store[], optimisticUpdate.forky(lcDataFork), currentSlot, cfg[], genesisValRoot[]) return if res.isOk: 0 else: case res.error of VerifierError.MissingParent: 2 of VerifierError.Duplicate: 2 of VerifierError.UnviableFork: 2 of VerifierError.Invalid: 1 func ETHLightClientStoreGetFinalizedHeader( store: ptr lcDataFork.LightClientStore ): ptr lcDataFork.LightClientHeader {.exported.} = ## Obtains the latest finalized header of a given light client store. ## ## * The returned value is allocated in the given light client store. ## It must neither be released nor written to, and the light client store ## must not be released while the returned value is in use. ## ## Parameters: ## * `store` - Light client store. ## ## Returns: ## * Latest finalized header. ## ## See: ## * https://github.com/ethereum/consensus-specs/blob/v1.4.0-beta.5/specs/capella/light-client/sync-protocol.md#modified-lightclientheader addr store[].finalized_header func ETHLightClientStoreIsNextSyncCommitteeKnown( store: ptr lcDataFork.LightClientStore): bool {.exported.} = ## Indicates whether or not the next sync committee is currently known. ## ## * The light client sync process ensures that the next sync committee ## is obtained in time, before it starts signing light client data. ## To stay in sync, use `ETHLightClientStoreGetNextSyncTask` and ## `ETHLightClientStoreGetMillisecondsToNextSyncTask`. ## ## Parameters: ## * `store` - Light client store. ## ## Returns: ## * Whether or not the next sync committee is currently known. ## ## See: ## * https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.2/specs/altair/light-client/sync-protocol.md#is_next_sync_committee_known ## * https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.2/specs/altair/light-client/light-client.md store[].is_next_sync_committee_known func ETHLightClientStoreGetOptimisticHeader( store: ptr lcDataFork.LightClientStore ): ptr lcDataFork.LightClientHeader {.exported.} = ## Obtains the latest optimistic header of a given light client store. ## ## * The returned value is allocated in the given light client store. ## It must neither be released nor written to, and the light client store ## must not be released while the returned value is in use. ## ## Parameters: ## * `store` - Light client store. ## ## Returns: ## * Latest optimistic header. ## ## See: ## * https://github.com/ethereum/consensus-specs/blob/v1.4.0-beta.5/specs/capella/light-client/sync-protocol.md#modified-lightclientheader addr store[].optimistic_header func ETHLightClientStoreGetSafetyThreshold( store: ptr lcDataFork.LightClientStore): cint {.exported.} = ## Calculates the safety threshold for a given light client store. ## ## * Light client data can only update the optimistic header if it is signed ## by more sync committee participants than the safety threshold indicates. ## ## * The finalized header is not affected by the safety threshold; ## light client data can only update the finalized header if it is signed ## by a supermajority of the sync committee, regardless of safety threshold. ## ## Parameters: ## * `store` - Light client store. ## ## Returns: ## * Light client store safety threshold. ## ## See: ## * https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.2/specs/altair/light-client/sync-protocol.md#get_safety_threshold store[].get_safety_threshold.cint proc ETHLightClientHeaderCreateCopy( header: ptr lcDataFork.LightClientHeader ): ptr lcDataFork.LightClientHeader {.exported.} = ## Creates a shallow copy of a given light client header. ## ## * The copy must be destroyed with `ETHLightClientHeaderDestroy` ## once no longer needed, to release memory. ## ## Parameters: ## * `header` - Light client header. ## ## Returns: ## * Pointer to a shallow copy of the given header. let copy = lcDataFork.LightClientHeader.new() copy[] = header[] copy.toUnmanagedPtr() proc ETHLightClientHeaderDestroy( header: ptr lcDataFork.LightClientHeader) {.exported.} = ## Destroys a light client header. ## ## * The light client header must no longer be used after destruction. ## ## Parameters: ## * `header` - Light client header. header.destroy() proc ETHLightClientHeaderCopyBeaconRoot( header: ptr lcDataFork.LightClientHeader, cfg: ptr RuntimeConfig): ptr Eth2Digest {.exported.} = ## Computes the beacon block Merkle root for a given light client header. ## ## * The Merkle root must be destroyed with `ETHRootDestroy` ## once no longer needed, to release memory. ## ## Parameters: ## * `header` - Light client header. ## * `cfg` - Ethereum Consensus Layer network configuration. ## ## Returns: ## * Pointer to a copy of the given header's beacon block root. ## ## See: ## * https://github.com/ethereum/consensus-specs/blob/v1.4.0-beta.6/specs/phase0/beacon-chain.md#hash_tree_root discard cfg # Future-proof against new fields, see `get_lc_execution_root`. let root = Eth2Digest.new() root[] = header[].beacon.hash_tree_root() root.toUnmanagedPtr() func ETHLightClientHeaderGetBeacon( header: ptr lcDataFork.LightClientHeader ): ptr BeaconBlockHeader {.exported.} = ## Obtains the beacon block header of a given light client header. ## ## * The returned value is allocated in the given light client header. ## It must neither be released nor written to, and the light client header ## must not be released while the returned value is in use. ## ## Parameters: ## * `header` - Light client header. ## ## Returns: ## * Beacon block header. ## ## See: ## * https://github.com/ethereum/consensus-specs/blob/v1.4.0-beta.6/specs/phase0/beacon-chain.md#beaconblockheader addr header[].beacon func ETHBeaconBlockHeaderGetSlot( beacon: ptr BeaconBlockHeader): cint {.exported.} = ## Obtains the slot number of a given beacon block header. ## ## Parameters: ## * `beacon` - Beacon block header. ## ## Returns: ## * Slot number. beacon[].slot.cint func ETHBeaconBlockHeaderGetProposerIndex( beacon: ptr BeaconBlockHeader): cint {.exported.} = ## Obtains the proposer validator registry index ## of a given beacon block header. ## ## Parameters: ## * `beacon` - Beacon block header. ## ## Returns: ## * Proposer validator registry index. beacon[].proposer_index.cint func ETHBeaconBlockHeaderGetParentRoot( beacon: ptr BeaconBlockHeader): ptr Eth2Digest {.exported.} = ## Obtains the parent beacon block Merkle root of a given beacon block header. ## ## * The returned value is allocated in the given beacon block header. ## It must neither be released nor written to, and the beacon block header ## must not be released while the returned value is in use. ## ## Parameters: ## * `beacon` - Beacon block header. ## ## Returns: ## * Parent beacon block root. addr beacon[].parent_root func ETHBeaconBlockHeaderGetStateRoot( beacon: ptr BeaconBlockHeader): ptr Eth2Digest {.exported.} = ## Obtains the beacon state Merkle root of a given beacon block header. ## ## * The returned value is allocated in the given beacon block header. ## It must neither be released nor written to, and the beacon block header ## must not be released while the returned value is in use. ## ## Parameters: ## * `beacon` - Beacon block header. ## ## Returns: ## * Beacon state root. addr beacon[].state_root func ETHBeaconBlockHeaderGetBodyRoot( beacon: ptr BeaconBlockHeader): ptr Eth2Digest {.exported.} = ## Obtains the beacon block body Merkle root of a given beacon block header. ## ## * The returned value is allocated in the given beacon block header. ## It must neither be released nor written to, and the beacon block header ## must not be released while the returned value is in use. ## ## Parameters: ## * `beacon` - Beacon block header. ## ## Returns: ## * Beacon block body root. addr beacon[].body_root proc ETHLightClientHeaderCopyExecutionHash( header: ptr lcDataFork.LightClientHeader, cfg: ptr RuntimeConfig ): ptr Eth2Digest {.exported.} = ## Computes the execution block hash for a given light client header. ## ## * The hash must be destroyed with `ETHRootDestroy` ## once no longer needed, to release memory. ## ## Parameters: ## * `header` - Light client header. ## * `cfg` - Ethereum Consensus Layer network configuration. ## ## Returns: ## * Pointer to a copy of the given header's execution block hash. ## ## See: ## * https://github.com/ethereum/consensus-specs/blob/v1.4.0-beta.4/specs/deneb/beacon-chain.md#executionpayloadheader discard cfg # Future-proof against SSZ execution block header, EIP-6404ff. let root = Eth2Digest.new() root[] = header[].execution.block_hash root.toUnmanagedPtr() type ExecutionPayloadHeader = typeof(default(lcDataFork.LightClientHeader).execution) func ETHLightClientHeaderGetExecution( header: ptr lcDataFork.LightClientHeader ): ptr ExecutionPayloadHeader {.exported.} = ## Obtains the execution payload header of a given light client header. ## ## * The returned value is allocated in the given light client header. ## It must neither be released nor written to, and the light client header ## must not be released while the returned value is in use. ## ## Parameters: ## * `header` - Light client header. ## ## Returns: ## * Execution payload header. ## ## See: ## * https://github.com/ethereum/consensus-specs/blob/v1.4.0/specs/deneb/beacon-chain.md#executionpayloadheader addr header[].execution func ETHExecutionPayloadHeaderGetParentHash( execution: ptr ExecutionPayloadHeader): ptr Eth2Digest {.exported.} = ## Obtains the parent execution block hash of a given ## execution payload header. ## ## * The returned value is allocated in the given execution payload header. ## It must neither be released nor written to, and the execution payload ## header must not be released while the returned value is in use. ## ## Parameters: ## * `execution` - Execution payload header. ## ## Returns: ## * Parent execution block hash. addr execution[].parent_hash func ETHExecutionPayloadHeaderGetFeeRecipient( execution: ptr ExecutionPayloadHeader): ptr ExecutionAddress {.exported.} = ## Obtains the fee recipient address of a given execution payload header. ## ## * The returned value is allocated in the given execution payload header. ## It must neither be released nor written to, and the execution payload ## header must not be released while the returned value is in use. ## ## Parameters: ## * `execution` - Execution payload header. ## ## Returns: ## * Fee recipient execution address. addr execution[].fee_recipient func ETHExecutionPayloadHeaderGetStateRoot( execution: ptr ExecutionPayloadHeader): ptr Eth2Digest {.exported.} = ## Obtains the state MPT root of a given execution payload header. ## ## * The returned value is allocated in the given execution payload header. ## It must neither be released nor written to, and the execution payload ## header must not be released while the returned value is in use. ## ## Parameters: ## * `execution` - Execution payload header. ## ## Returns: ## * Execution state root. addr execution[].state_root func ETHExecutionPayloadHeaderGetReceiptsRoot( execution: ptr ExecutionPayloadHeader): ptr Eth2Digest {.exported.} = ## Obtains the receipts MPT root of a given execution payload header. ## ## * The returned value is allocated in the given execution payload header. ## It must neither be released nor written to, and the execution payload ## header must not be released while the returned value is in use. ## ## Parameters: ## * `execution` - Execution payload header. ## ## Returns: ## * Execution receipts root. addr execution[].receipts_root func ETHExecutionPayloadHeaderGetLogsBloom( execution: ptr ExecutionPayloadHeader): ptr BloomLogs {.exported.} = ## Obtains the logs Bloom of a given execution payload header. ## ## * The returned value is allocated in the given execution payload header. ## It must neither be released nor written to, and the execution payload ## header must not be released while the returned value is in use. ## ## Parameters: ## * `execution` - Execution payload header. ## ## Returns: ## * Execution logs Bloom. addr execution[].logs_bloom func ETHExecutionPayloadHeaderGetPrevRandao( execution: ptr ExecutionPayloadHeader): ptr Eth2Digest {.exported.} = ## Obtains the previous randao mix of a given execution payload header. ## ## * The returned value is allocated in the given execution payload header. ## It must neither be released nor written to, and the execution payload ## header must not be released while the returned value is in use. ## ## Parameters: ## * `execution` - Execution payload header. ## ## Returns: ## * Previous randao mix. addr execution[].prev_randao func ETHExecutionPayloadHeaderGetBlockNumber( execution: ptr ExecutionPayloadHeader): cint {.exported.} = ## Obtains the execution block number of a given execution payload header. ## ## Parameters: ## * `execution` - Execution payload header. ## ## Returns: ## * Execution block number. execution[].block_number.cint func ETHExecutionPayloadHeaderGetGasLimit( execution: ptr ExecutionPayloadHeader): cint {.exported.} = ## Obtains the gas limit of a given execution payload header. ## ## Parameters: ## * `execution` - Execution payload header. ## ## Returns: ## * Gas limit. execution[].gas_limit.cint func ETHExecutionPayloadHeaderGetGasUsed( execution: ptr ExecutionPayloadHeader): cint {.exported.} = ## Obtains the gas used of a given execution payload header. ## ## Parameters: ## * `execution` - Execution payload header. ## ## Returns: ## * Gas used. execution[].gas_used.cint func ETHExecutionPayloadHeaderGetTimestamp( execution: ptr ExecutionPayloadHeader): cint {.exported.} = ## Obtains the timestamp of a given execution payload header. ## ## Parameters: ## * `execution` - Execution payload header. ## ## Returns: ## * Execution block timestamp. execution[].timestamp.cint func ETHExecutionPayloadHeaderGetExtraDataBytes( execution: ptr ExecutionPayloadHeader, numBytes #[out]#: ptr cint): ptr UncheckedArray[byte] {.exported.} = ## Obtains the extra data buffer of a given execution payload header. ## ## * The returned value is allocated in the given execution payload header. ## It must neither be released nor written to, and the execution payload ## header must not be released while the returned value is in use. ## ## Parameters: ## * `execution` - Execution payload header. ## * `numBytes` [out] - Length of buffer. ## ## Returns: ## * Buffer with execution block extra data. numBytes[] = execution[].extra_data.len.cint if execution[].extra_data.len == 0: # https://github.com/nim-lang/Nim/issues/22389 const defaultExtraData: cstring = "" return cast[ptr UncheckedArray[byte]](defaultExtraData) cast[ptr UncheckedArray[byte]](addr execution[].extra_data[0]) func ETHExecutionPayloadHeaderGetBaseFeePerGas( execution: ptr ExecutionPayloadHeader): ptr UInt256 {.exported.} = ## Obtains the base fee per gas of a given execution payload header. ## ## * The returned value is allocated in the given execution payload header. ## It must neither be released nor written to, and the execution payload ## header must not be released while the returned value is in use. ## ## Parameters: ## * `execution` - Execution payload header. ## ## Returns: ## * Base fee per gas. addr execution[].base_fee_per_gas func ETHExecutionPayloadHeaderGetBlobGasUsed( execution: ptr ExecutionPayloadHeader): cint {.exported.} = ## Obtains the blob gas used of a given execution payload header. ## ## Parameters: ## * `execution` - Execution payload header. ## ## Returns: ## * Blob gas used. execution[].blob_gas_used.cint func ETHExecutionPayloadHeaderGetExcessBlobGas( execution: ptr ExecutionPayloadHeader): cint {.exported.} = ## Obtains the excess blob gas of a given execution payload header. ## ## Parameters: ## * `execution` - Execution payload header. ## ## Returns: ## * Excess blob gas. execution[].excess_blob_gas.cint type ETHWithdrawal = object index: uint64 validatorIndex: uint64 address: ExecutionAddress amount: uint64 bytes: seq[byte] ETHExecutionBlockHeader = object transactionsRoot: Eth2Digest withdrawalsRoot: Eth2Digest withdrawals: seq[ETHWithdrawal] proc ETHExecutionBlockHeaderCreateFromJson( executionHash: ptr Eth2Digest, blockHeaderJson: cstring): ptr ETHExecutionBlockHeader {.exported.} = ## Verifies that a JSON execution block header is valid and that it matches ## the given `executionHash`. ## ## * The JSON-RPC `eth_getBlockByHash` with params `[executionHash, false]` ## may be used to obtain execution block header data for a given execution ## block hash. Pass the response's `result` property as `blockHeaderJson`. ## ## * The execution block header must be destroyed with ## `ETHExecutionBlockHeaderDestroy` once no longer needed, ## to release memory. ## ## Parameters: ## * `executionHash` - Execution block hash. ## * `blockHeaderJson` - Buffer with JSON encoded header. NULL-terminated. ## ## Returns: ## * Pointer to an initialized execution block header - If successful. ## * `NULL` - If the given `blockHeaderJson` is malformed or incompatible. ## ## See: ## * https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getblockbyhash let data = try: # a direct parameter like JrpcConv.decode($blockHeaderJson, BlockObject) # will cause premature garbage collector kick in. let jsonBytes = $blockHeaderJson JrpcConv.decode(jsonBytes, BlockObject) except SerializationError: return nil if data == nil: return nil # Sanity check if data.hash.asEth2Digest != executionHash[]: return nil # Check fork consistency static: doAssert totalSerializedFields(BlockObject) == 26, "Only update this number once code is adjusted to check new fields!" if data.baseFeePerGas.isNone and ( data.withdrawals.isSome or data.withdrawalsRoot.isSome or data.blobGasUsed.isSome or data.excessBlobGas.isSome): return nil if data.withdrawalsRoot.isNone and ( data.blobGasUsed.isSome or data.excessBlobGas.isSome): return nil if data.withdrawals.isSome != data.withdrawalsRoot.isSome: return nil if data.blobGasUsed.isSome != data.excessBlobGas.isSome: return nil # Construct block header static: # `GasInt` is signed. We only use it for hashing. doAssert sizeof(int64) == sizeof(data.gasLimit) doAssert sizeof(int64) == sizeof(data.gasUsed) if distinctBase(data.timestamp) > int64.high.uint64: return nil if data.nonce.isNone: return nil let blockHeader = ExecutionBlockHeader( parentHash: data.parentHash.asEth2Digest, ommersHash: data.sha3Uncles.asEth2Digest, coinbase: distinctBase(data.miner), stateRoot: data.stateRoot.asEth2Digest, txRoot: data.transactionsRoot.asEth2Digest, receiptsRoot: data.receiptsRoot.asEth2Digest, logsBloom: distinctBase(data.logsBloom), difficulty: data.difficulty, number: distinctBase(data.number), gasLimit: GasInt.saturate distinctBase(data.gasLimit), gasUsed: GasInt.saturate distinctBase(data.gasUsed), timestamp: EthTime(distinctBase(data.timestamp)), extraData: distinctBase(data.extraData), mixHash: data.mixHash.asEth2Digest, nonce: distinctBase(data.nonce.get), baseFeePerGas: data.baseFeePerGas, withdrawalsRoot: if data.withdrawalsRoot.isSome: Opt.some(data.withdrawalsRoot.get.asEth2Digest) else: Opt.none(ExecutionHash256), blobGasUsed: if data.blobGasUsed.isSome: Opt.some distinctBase(data.blobGasUsed.get) else: Opt.none(uint64), excessBlobGas: if data.excessBlobGas.isSome: Opt.some distinctBase(data.excessBlobGas.get) else: Opt.none(uint64), parentBeaconBlockRoot: if data.parentBeaconBlockRoot.isSome: Opt.some distinctBase(data.parentBeaconBlockRoot.get.asEth2Digest) else: Opt.none(ExecutionHash256)) if rlpHash(blockHeader) != executionHash[]: return nil # Construct withdrawals var wds: seq[ETHWithdrawal] if data.withdrawals.isSome: doAssert data.withdrawalsRoot.isSome # Checked above wds = newSeqOfCap[ETHWithdrawal](data.withdrawals.get.len) for data in data.withdrawals.get: # Check fork consistency static: doAssert totalSerializedFields(WithdrawalObject) == 4, "Only update this number once code is adjusted to check new fields!" # Construct withdrawal let wd = ExecutionWithdrawal( index: distinctBase(data.index), validatorIndex: distinctBase(data.validatorIndex), address: distinctBase(data.address), amount: distinctBase(data.amount)) rlpBytes = try: rlp.encode(wd) except RlpError: raiseAssert "Unreachable" wds.add ETHWithdrawal( index: wd.index, validatorIndex: wd.validatorIndex, address: ExecutionAddress(data: wd.address), amount: wd.amount, bytes: rlpBytes) var tr = initHexaryTrie(newMemoryDB()) for i, wd in wds: try: tr.put(rlp.encode(i), wd.bytes) except RlpError: raiseAssert "Unreachable" if tr.rootHash() != data.withdrawalsRoot.get.asEth2Digest: return nil let executionBlockHeader = ETHExecutionBlockHeader.new() executionBlockHeader[] = ETHExecutionBlockHeader( transactionsRoot: blockHeader.txRoot, withdrawalsRoot: blockHeader.withdrawalsRoot.get(ZERO_HASH), withdrawals: wds) executionBlockHeader.toUnmanagedPtr() proc ETHExecutionBlockHeaderDestroy( executionBlockHeader: ptr ETHExecutionBlockHeader) {.exported.} = ## Destroys an execution block header. ## ## * The execution block header must no longer be used after destruction. ## ## Parameters: ## * `executionBlockHeader` - Execution block header. executionBlockHeader.destroy() func ETHExecutionBlockHeaderGetTransactionsRoot( executionBlockHeader: ptr ETHExecutionBlockHeader ): ptr Eth2Digest {.exported.} = ## Obtains the transactions MPT root of a given execution block header. ## ## * The returned value is allocated in the given execution block header. ## It must neither be released nor written to, and the execution block ## header must not be released while the returned value is in use. ## ## Parameters: ## * `executionBlockHeader` - Execution block header. ## ## Returns: ## * Execution transactions root. addr executionBlockHeader[].transactionsRoot func ETHExecutionBlockHeaderGetWithdrawalsRoot( executionBlockHeader: ptr ETHExecutionBlockHeader ): ptr Eth2Digest {.exported.} = ## Obtains the withdrawals MPT root of a given execution block header. ## ## * The returned value is allocated in the given execution block header. ## It must neither be released nor written to, and the execution block ## header must not be released while the returned value is in use. ## ## Parameters: ## * `executionBlockHeader` - Execution block header. ## ## Returns: ## * Execution withdrawals root. addr executionBlockHeader[].withdrawalsRoot func ETHExecutionBlockHeaderGetWithdrawals( executionBlockHeader: ptr ETHExecutionBlockHeader ): ptr seq[ETHWithdrawal] {.exported.} = ## Obtains the withdrawal sequence of a given execution block header. ## ## * The returned value is allocated in the given execution block header. ## It must neither be released nor written to, and the execution block ## header must not be released while the returned value is in use. ## ## Parameters: ## * `executionBlockHeader` - Execution block header. ## ## Returns: ## * Withdrawal sequence. addr executionBlockHeader[].withdrawals type ETHAccessTuple = object address: ExecutionAddress storageKeys: seq[Eth2Digest] DestinationType {.pure.} = enum Regular, Create ETHTransaction = object hash: Eth2Digest chainId: UInt256 `from`: ExecutionAddress nonce: uint64 maxPriorityFeePerGas: uint64 maxFeePerGas: uint64 gas: uint64 destinationType: DestinationType to: ExecutionAddress value: UInt256 input: seq[byte] accessList: seq[ETHAccessTuple] maxFeePerBlobGas: UInt256 blobVersionedHashes: seq[Eth2Digest] signature: seq[byte] bytes: TypedTransaction eip6493Root: Eth2Digest eip6493Bytes: seq[byte] template toSszType*(v: ChainId): auto = uint64(v) template fromSszBytes*(T: type ChainId, bytes: openArray[byte]): T = T fromSszBytes(uint64, bytes) proc ETHTransactionsCreateFromJson( transactionsRoot #[optional]#: ptr Eth2Digest, transactionsJson: cstring): ptr seq[ETHTransaction] {.exported.} = ## Verifies that JSON transactions data is valid and that it matches ## the given `transactionsRoot`. ## ## * The JSON-RPC `eth_getBlockByHash` with params `[executionHash, true]` ## may be used to obtain transactions data for a given execution ## block hash. Pass `result.transactions` as `transactionsJson`. ## ## * The transaction sequence must be destroyed with ## `ETHTransactionsDestroy` once no longer needed, ## to release memory. ## ## Parameters: ## * `transactionsRoot` - Execution transactions root. ## * `transactionsJson` - Buffer with JSON transactions list. NULL-terminated. ## ## Returns: ## * Pointer to an initialized transaction sequence - If successful. ## * `NULL` - If the given `transactionsJson` is malformed or incompatible. ## ## See: ## * https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getblockbyhash var datas = try: # a direct parameter like JrpcConv.decode($transactionsJson, seq[TransactionObject]) # will cause premature garbage collector kick in. let jsonBytes = $transactionsJson JrpcConv.decode(jsonBytes, seq[TransactionObject]) except SerializationError: return nil var txs = newSeqOfCap[ETHTransaction](datas.len) for i, data in datas: # Sanity check if data.transactionIndex.isNone: return nil if distinctBase(data.transactionIndex.get) != i.uint64: return nil # Check fork consistency static: doAssert totalSerializedFields(TransactionObject) == 22, "Only update this number once code is adjusted to check new fields!" let txType = case data.`type`.get(0.Quantity): of 0.Quantity: if data.yParity.isSome or data.accessList.isSome or data.maxFeePerGas.isSome or data.maxPriorityFeePerGas.isSome or data.maxFeePerBlobGas.isSome or data.blobVersionedHashes.isSome: return nil TxLegacy of 1.Quantity: if data.chainId.isNone or data.accessList.isNone: return nil if data.maxFeePerGas.isSome or data.maxPriorityFeePerGas.isSome or data.maxFeePerBlobGas.isSome or data.blobVersionedHashes.isSome: return nil TxEip2930 of 2.Quantity: if data.chainId.isNone or data.accessList.isNone or data.maxFeePerGas.isNone or data.maxPriorityFeePerGas.isNone: return nil if data.maxFeePerBlobGas.isSome or data.blobVersionedHashes.isSome: return nil TxEip1559 of 3.Quantity: if data.to.isNone or data.chainId.isNone or data.accessList.isNone or data.maxFeePerGas.isNone or data.maxPriorityFeePerGas.isNone or data.maxFeePerBlobGas.isNone or data.blobVersionedHashes.isNone: return nil TxEip4844 else: return nil # Construct transaction static: doAssert sizeof(uint64) == sizeof(ChainId) doAssert sizeof(int64) == sizeof(data.gasPrice) doAssert sizeof(int64) == sizeof(data.maxPriorityFeePerGas.get) doAssert sizeof(UInt256) == sizeof(data.maxFeePerBlobGas.get) if distinctBase(data.chainId.get(0.Quantity)) > distinctBase(ChainId.high): return nil if distinctBase(data.gasPrice) > int64.high.uint64: return nil if distinctBase(data.maxFeePerGas.get(0.Quantity)) > int64.high.uint64: return nil if distinctBase(data.maxPriorityFeePerGas.get(0.Quantity)) > int64.high.uint64: return nil if data.maxFeePerBlobGas.get(0.u256) > uint64.high.u256: return nil if distinctBase(data.gas) > int64.high.uint64: return nil if distinctBase(data.v) > int64.high.uint64: return nil if data.yParity.isSome: # This is not always included, but if it is, make sure it's correct let yParity = data.yParity.get if distinctBase(yParity) > 1: return nil if yParity != data.v: return nil let tx = ExecutionTransaction( txType: txType, chainId: data.chainId.get(0.Quantity).ChainId, nonce: distinctBase(data.nonce), gasPrice: data.gasPrice.GasInt, maxPriorityFeePerGas: distinctBase(data.maxPriorityFeePerGas.get(data.gasPrice)).GasInt, maxFeePerGas: distinctBase(data.maxFeePerGas.get(data.gasPrice)).GasInt, gasLimit: distinctBase(data.gas).GasInt, to: if data.to.isSome: Opt.some(distinctBase(data.to.get)) else: Opt.none(EthAddress), value: data.value, payload: data.input, accessList: if data.accessList.isSome: data.accessList.get.mapIt(AccessPair( address: distinctBase(it.address), storageKeys: it.storageKeys.mapIt(distinctBase(it)))) else: @[], maxFeePerBlobGas: data.maxFeePerBlobGas.get(0.u256), versionedHashes: if data.blobVersionedHashes.isSome: data.blobVersionedHashes.get.mapIt( ExecutionHash256(data: distinctBase(it))) else: @[], V: data.v.uint64, R: data.r, S: data.s) rlpBytes = try: rlp.encode(tx) except RlpError: raiseAssert "Unreachable" hash = keccakHash(rlpBytes) if data.hash.asEth2Digest != hash: return nil template isEven(x: uint64): bool = (x and 1) == 0 # Compute from execution address var rawSig {.noinit.}: array[65, byte] rawSig[0 ..< 32] = tx.R.toBytesBE() rawSig[32 ..< 64] = tx.S.toBytesBE() rawSig[64] = if txType != TxLegacy: tx.V.uint8 elif tx.V.isEven: 1 else: 0 let sig = SkRecoverableSignature.fromRaw(rawSig).valueOr: return nil sigHash = SkMessage.fromBytes(tx.txHashNoSignature().data).valueOr: return nil fromPubkey = sig.recover(sigHash).valueOr: return nil fromAddress = keys.PublicKey(fromPubkey).toCanonicalAddress() if distinctBase(data.`from`) != fromAddress: return nil # Compute to execution address let destinationType = if tx.to.isSome: DestinationType.Regular else: DestinationType.Create toAddress = case destinationType of DestinationType.Regular: tx.to.get of DestinationType.Create: var res {.noinit.}: array[20, byte] res[0 ..< 20] = keccakHash(rlp.encodeList(fromAddress, tx.nonce)) .data.toOpenArray(12, 31) res # Compute EIP-6493 tranasaction const MAX_CALLDATA_SIZE = 16_777_216 MAX_ACCESS_LIST_STORAGE_KEYS = 524_288 MAX_ACCESS_LIST_SIZE = 524_288 MAX_BLOB_COMMITMENTS_PER_BLOCK = 4_096 type FeePerGas = UInt256 FeesPerGas {.sszStableContainer: 16.} = object regular: Opt[FeePerGas] blob: Opt[FeePerGas] Eip6493AccessTuple = object address: ExecutionAddress storage_keys: List[Eth2Digest, Limit MAX_ACCESS_LIST_STORAGE_KEYS] Eip6493TransactionPayload {.sszStableContainer: 32.} = object # EIP-2718 `type`: Opt[uint8] # EIP-155 chain_id: Opt[ChainId] nonce: Opt[uint64] max_fees_per_gas: Opt[FeesPerGas] gas: Opt[uint64] to: Opt[ExecutionAddress] value: Opt[UInt256] input: Opt[List[byte, Limit MAX_CALLDATA_SIZE]] # EIP-2930 access_list: Opt[List[Eip6493AccessTuple, Limit MAX_ACCESS_LIST_SIZE]] # EIP-1559 max_priority_fees_per_gas: Opt[FeesPerGas] # EIP-4844 blob_versioned_hashes: Opt[List[deneb.VersionedHash, Limit MAX_BLOB_COMMITMENTS_PER_BLOCK]] Eip6493TransactionSignature {.sszStableContainer: 16.} = object `from`: Opt[ExecutionAddress] ecdsa_signature: Opt[array[65, byte]] Eip6493Transaction = object payload: Eip6493TransactionPayload signature: Eip6493TransactionSignature var eip6493Tx: Eip6493Transaction case tx.txType of TxLegacy: eip6493Tx.payload.`type`.ok 0x00'u8 of TxEip2930: eip6493Tx.payload.`type`.ok 0x01'u8 of TxEip1559: eip6493Tx.payload.`type`.ok 0x02'u8 of TxEip4844: eip6493Tx.payload.`type`.ok 0x03'u8 if tx.txType != TxLegacy or tx.V notin [27'u64, 28'u64]: # With replay protection eip6493Tx.payload.chain_id.ok tx.chainId eip6493Tx.payload.nonce.ok tx.nonce eip6493Tx.payload.max_fees_per_gas.ok FeesPerGas( regular: Opt.some tx.maxFeePerGas.u256, blob: if tx.txType == TxEip4844: Opt.some tx.maxFeePerBlobGas else: Opt.none FeePerGas) eip6493Tx.payload.gas.ok tx.gasLimit.uint64 if tx.to.isSome: eip6493Tx.payload.to.ok(ExecutionAddress(data: tx.to.get)) eip6493Tx.payload.value.ok tx.value if tx.payload.len > MAX_CALLDATA_SIZE: return nil eip6493Tx.payload.input .ok List[byte, Limit MAX_CALLDATA_SIZE].init(tx.payload) if tx.txType >= TxEip2930: if tx.accessList.len > MAX_ACCESS_LIST_SIZE: return nil for it in tx.accessList: if it.storageKeys.len > MAX_ACCESS_LIST_STORAGE_KEYS: return nil eip6493Tx.payload.access_list .ok List[Eip6493AccessTuple, Limit MAX_ACCESS_LIST_SIZE] .init(tx.accessList.mapIt(Eip6493AccessTuple( address: ExecutionAddress(data: it.address), storage_keys: List[Eth2Digest, Limit MAX_ACCESS_LIST_STORAGE_KEYS] .init(it.storageKeys.mapIt(Eth2Digest(data: it)))))) if tx.txType >= TxEip1559: eip6493Tx.payload.max_priority_fees_per_gas.ok FeesPerGas( regular: Opt.some tx.maxPriorityFeePerGas.u256, blob: if tx.txType == TxEip4844: Opt.some FeePerGas(UInt256.zero) else: Opt.none FeePerGas) eip6493Tx.signature.`from`.ok ExecutionAddress(data: fromAddress) eip6493Tx.signature.ecdsa_signature.ok rawSig # Nim 1.6.14: Inlining `SSZ.encode` into constructor may corrupt memory. let eip6493Bytes = SSZ.encode(eip6493Tx) txs.add ETHTransaction( hash: keccakHash(rlpBytes), chainId: distinctBase(tx.chainId).u256, `from`: ExecutionAddress(data: fromAddress), nonce: tx.nonce, maxPriorityFeePerGas: tx.maxPriorityFeePerGas.uint64, maxFeePerGas: tx.maxFeePerGas.uint64, gas: tx.gasLimit.uint64, destinationType: destinationType, to: ExecutionAddress(data: toAddress), value: tx.value, input: tx.payload, accessList: tx.accessList.mapIt(ETHAccessTuple( address: ExecutionAddress(data: it.address), storageKeys: it.storageKeys.mapIt(Eth2Digest(data: it)))), maxFeePerBlobGas: tx.maxFeePerBlobGas, blobVersionedHashes: tx.versionedHashes, signature: @rawSig, bytes: rlpBytes.TypedTransaction, eip6493Root: eip6493Tx.hash_tree_root(), eip6493Bytes: eip6493Bytes) if transactionsRoot != nil: var tr = initHexaryTrie(newMemoryDB()) for i, transaction in txs: try: tr.put(rlp.encode(i), distinctBase(transaction.bytes)) except RlpError: raiseAssert "Unreachable" if tr.rootHash() != transactionsRoot[]: return nil let transactions = seq[ETHTransaction].new() transactions[] = txs transactions.toUnmanagedPtr() proc ETHTransactionsDestroy( transactions: ptr seq[ETHTransaction]) {.exported.} = ## Destroys a transaction sequence. ## ## * The transaction sequence must no longer be used after destruction. ## ## Parameters: ## * `transactions` - Transaction sequence. transactions.destroy() func ETHTransactionsGetCount( transactions: ptr seq[ETHTransaction]): cint {.exported.} = ## Indicates the total number of transactions in a transaction sequence. ## ## * Individual transactions may be inspected using `ETHTransactionsGet`. ## ## Parameters: ## * `transactions` - Transaction sequence. ## ## Returns: ## * Number of available transactions. transactions[].len.cint func ETHTransactionsGet( transactions: ptr seq[ETHTransaction], transactionIndex: cint): ptr ETHTransaction {.exported.} = ## Obtains an individual transaction by sequential index ## in a transaction sequence. ## ## * The returned value is allocated in the given transaction sequence. ## It must neither be released nor written to, and the transaction ## sequence must not be released while the returned value is in use. ## ## Parameters: ## * `transactions` - Transaction sequence. ## * `transactionIndex` - Sequential transaction index. ## ## Returns: ## * Transaction. addr transactions[][transactionIndex.int] func ETHTransactionGetHash( transaction: ptr ETHTransaction): ptr Eth2Digest {.exported.} = ## Obtains the transaction hash of a transaction. ## ## * The returned value is allocated in the given transaction. ## It must neither be released nor written to, and the transaction ## must not be released while the returned value is in use. ## ## Parameters: ## * `transaction` - Transaction. ## ## Returns: ## * Transaction hash. addr transaction[].hash func ETHTransactionGetChainId( transaction: ptr ETHTransaction): ptr UInt256 {.exported.} = ## Obtains the chain ID of a transaction. ## ## * The returned value is allocated in the given transaction. ## It must neither be released nor written to, and the transaction ## must not be released while the returned value is in use. ## ## Parameters: ## * `transaction` - Transaction. ## ## Returns: ## * Chain ID. addr transaction[].chainId func ETHTransactionGetFrom( transaction: ptr ETHTransaction): ptr ExecutionAddress {.exported.} = ## Obtains the from address of a transaction. ## ## * The returned value is allocated in the given transaction. ## It must neither be released nor written to, and the transaction ## must not be released while the returned value is in use. ## ## Parameters: ## * `transaction` - Transaction. ## ## Returns: ## * From execution address. addr transaction[].`from` func ETHTransactionGetNonce( transaction: ptr ETHTransaction): ptr uint64 {.exported.} = ## Obtains the nonce of a transaction. ## ## * The returned value is allocated in the given transaction. ## It must neither be released nor written to, and the transaction ## must not be released while the returned value is in use. ## ## Parameters: ## * `transaction` - Transaction. ## ## Returns: ## * Nonce. addr transaction[].nonce func ETHTransactionGetMaxPriorityFeePerGas( transaction: ptr ETHTransaction): ptr uint64 {.exported.} = ## Obtains the max priority fee per gas of a transaction. ## ## * The returned value is allocated in the given transaction. ## It must neither be released nor written to, and the transaction ## must not be released while the returned value is in use. ## ## Parameters: ## * `transaction` - Transaction. ## ## Returns: ## * Max priority fee per gas. addr transaction[].maxPriorityFeePerGas func ETHTransactionGetMaxFeePerGas( transaction: ptr ETHTransaction): ptr uint64 {.exported.} = ## Obtains the max fee per gas of a transaction. ## ## * The returned value is allocated in the given transaction. ## It must neither be released nor written to, and the transaction ## must not be released while the returned value is in use. ## ## Parameters: ## * `transaction` - Transaction. ## ## Returns: ## * Max fee per gas. addr transaction[].maxFeePerGas func ETHTransactionGetGas( transaction: ptr ETHTransaction): ptr uint64 {.exported.} = ## Obtains the gas of a transaction. ## ## * The returned value is allocated in the given transaction. ## It must neither be released nor written to, and the transaction ## must not be released while the returned value is in use. ## ## Parameters: ## * `transaction` - Transaction. ## ## Returns: ## * Gas. addr transaction[].gas func ETHTransactionIsCreatingContract( transaction: ptr ETHTransaction): bool {.exported.} = ## Indicates whether or not a transaction is creating a contract. ## ## Parameters: ## * `transaction` - Transaction. ## ## Returns: ## * Whether or not the transaction is creating a contract. case transaction[].destinationType of DestinationType.Regular: false of DestinationType.Create: true func ETHTransactionGetTo( transaction: ptr ETHTransaction): ptr ExecutionAddress {.exported.} = ## Obtains the to address of a transaction. ## ## * If the transaction is creating a contract, this function returns ## the address of the new contract. ## ## * The returned value is allocated in the given transaction. ## It must neither be released nor written to, and the transaction ## must not be released while the returned value is in use. ## ## Parameters: ## * `transaction` - Transaction. ## ## Returns: ## * To execution address. addr transaction[].to func ETHTransactionGetValue( transaction: ptr ETHTransaction): ptr UInt256 {.exported.} = ## Obtains the value of a transaction. ## ## * The returned value is allocated in the given transaction. ## It must neither be released nor written to, and the transaction ## must not be released while the returned value is in use. ## ## Parameters: ## * `transaction` - Transaction. ## ## Returns: ## * Value. addr transaction[].value func ETHTransactionGetInputBytes( transaction: ptr ETHTransaction, numBytes #[out]#: ptr cint): ptr UncheckedArray[byte] {.exported.} = ## Obtains the input of a transaction. ## ## * The returned value is allocated in the given transaction. ## It must neither be released nor written to, and the transaction ## must not be released while the returned value is in use. ## ## Parameters: ## * `transaction` - Transaction. ## * `numBytes` [out] - Length of buffer. ## ## Returns: ## * Buffer with input. numBytes[] = transaction[].input.len.cint if transaction[].input.len == 0: # https://github.com/nim-lang/Nim/issues/22389 const defaultInput: cstring = "" return cast[ptr UncheckedArray[byte]](defaultInput) cast[ptr UncheckedArray[byte]](addr transaction[].input[0]) func ETHTransactionGetAccessList( transaction: ptr ETHTransaction): ptr seq[ETHAccessTuple] {.exported.} = ## Obtains the access list of a transaction. ## ## * The returned value is allocated in the given transaction. ## It must neither be released nor written to, and the transaction ## must not be released while the returned value is in use. ## ## Parameters: ## * `transaction` - Transaction. ## ## Returns: ## * Transaction access list. addr transaction[].accessList func ETHAccessListGetCount( accessList: ptr seq[ETHAccessTuple]): cint {.exported.} = ## Indicates the total number of access tuples in a transaction access list. ## ## * Individual access tuples may be inspected using `ETHAccessListGet`. ## ## Parameters: ## * `accessList` - Transaction access list. ## ## Returns: ## * Number of available access tuples. accessList[].len.cint func ETHAccessListGet( accessList: ptr seq[ETHAccessTuple], accessTupleIndex: cint): ptr ETHAccessTuple {.exported.} = ## Obtains an individual access tuple by sequential index ## in a transaction access list. ## ## * The returned value is allocated in the given transaction access list. ## It must neither be released nor written to, and the transaction ## access list must not be released while the returned value is in use. ## ## Parameters: ## * `accessList` - Transaction access list. ## * `accessTupleIndex` - Sequential access tuple index. ## ## Returns: ## * Access tuple. addr accessList[][accessTupleIndex.int] func ETHAccessTupleGetAddress( accessTuple: ptr ETHAccessTuple): ptr ExecutionAddress {.exported.} = ## Obtains the address of an access tuple. ## ## * The returned value is allocated in the given access tuple. ## It must neither be released nor written to, and the access tuple ## must not be released while the returned value is in use. ## ## Parameters: ## * `accessTuple` - Access tuple. ## ## Returns: ## * Address. addr accessTuple[].address func ETHAccessTupleGetNumStorageKeys( accessTuple: ptr ETHAccessTuple): cint {.exported.} = ## Indicates the total number of storage keys in an access tuple. ## ## * Individual storage keys may be inspected using ## `ETHAccessTupleGetStorageKey`. ## ## Parameters: ## * `accessTuple` - Access tuple. ## ## Returns: ## * Number of available storage keys. accessTuple[].storageKeys.len.cint func ETHAccessTupleGetStorageKey( accessTuple: ptr ETHAccessTuple, storageKeyIndex: cint): ptr Eth2Digest {.exported.} = ## Obtains an individual storage key by sequential index ## in an access tuple. ## ## * The returned value is allocated in the given transaction access tuple. ## It must neither be released nor written to, and the transaction ## access tuple must not be released while the returned value is in use. ## ## Parameters: ## * `accessTuple` - Access tuple. ## * `storageKeyIndex` - Sequential storage key index. ## ## Returns: ## * Storage key. addr accessTuple[].storageKeys[storageKeyIndex.int] func ETHTransactionGetMaxFeePerBlobGas( transaction: ptr ETHTransaction): ptr UInt256 {.exported.} = ## Obtains the max fee per blob gas of a transaction. ## ## * The returned value is allocated in the given transaction. ## It must neither be released nor written to, and the transaction ## must not be released while the returned value is in use. ## ## Parameters: ## * `transaction` - Transaction. ## ## Returns: ## * Max fee per blob gas. addr transaction[].maxFeePerBlobGas func ETHTransactionGetNumBlobVersionedHashes( transaction: ptr ETHTransaction): cint {.exported.} = ## Indicates the total number of blob versioned hashes of a transaction. ## ## * Individual blob versioned hashes may be inspected using ## `ETHTransactionGetBlobVersionedHash`. ## ## Parameters: ## * `transaction` - Transaction. ## ## Returns: ## * Number of available blob versioned hashes. transaction[].blobVersionedHashes.len.cint func ETHTransactionGetBlobVersionedHash( transaction: ptr ETHTransaction, versionedHashIndex: cint): ptr Eth2Digest {.exported.} = ## Obtains an individual blob versioned hash by sequential index ## in a transaction. ## ## * The returned value is allocated in the given transaction. ## It must neither be released nor written to, and the transaction ## must not be released while the returned value is in use. ## ## Parameters: ## * `transaction` - Transaction. ## * `versionedHashIndex` - Sequential blob versioned hash index. ## ## Returns: ## * Blob versioned hash. addr transaction[].blobVersionedHashes[versionedHashIndex.int] func ETHTransactionGetSignatureBytes( transaction: ptr ETHTransaction, numBytes #[out]#: ptr cint): ptr UncheckedArray[byte] {.exported.} = ## Obtains the signature of a transaction. ## ## * The returned value is allocated in the given transaction. ## It must neither be released nor written to, and the transaction ## must not be released while the returned value is in use. ## ## Parameters: ## * `transaction` - Transaction. ## * `numBytes` [out] - Length of buffer. ## ## Returns: ## * Buffer with signature. numBytes[] = distinctBase(transaction[].signature).len.cint if distinctBase(transaction[].signature).len == 0: # https://github.com/nim-lang/Nim/issues/22389 const defaultBytes: cstring = "" return cast[ptr UncheckedArray[byte]](defaultBytes) cast[ptr UncheckedArray[byte]](addr distinctBase(transaction[].signature)[0]) func ETHTransactionGetBytes( transaction: ptr ETHTransaction, numBytes #[out]#: ptr cint): ptr UncheckedArray[byte] {.exported.} = ## Obtains the raw byte representation of a transaction. ## ## * The returned value is allocated in the given transaction. ## It must neither be released nor written to, and the transaction ## must not be released while the returned value is in use. ## ## Parameters: ## * `transaction` - Transaction. ## * `numBytes` [out] - Length of buffer. ## ## Returns: ## * Buffer with raw transaction data. numBytes[] = distinctBase(transaction[].bytes).len.cint if distinctBase(transaction[].bytes).len == 0: # https://github.com/nim-lang/Nim/issues/22389 const defaultBytes: cstring = "" return cast[ptr UncheckedArray[byte]](defaultBytes) cast[ptr UncheckedArray[byte]](addr distinctBase(transaction[].bytes)[0]) func ETHTransactionGetEip6493Root( transaction: ptr ETHTransaction): ptr Eth2Digest {.exported.} = ## Obtains the EIP-6493 transaction root of a transaction. ## ## * The returned value is allocated in the given transaction. ## It must neither be released nor written to, and the transaction ## must not be released while the returned value is in use. ## ## Parameters: ## * `transaction` - Transaction. ## ## Returns: ## * EIP-6493 transaction root. ## ## See: ## * https://eips.ethereum.org/EIPS/eip-6493 addr transaction[].eip6493Root func ETHTransactionGetEip6493Bytes( transaction: ptr ETHTransaction, numBytes #[out]#: ptr cint): ptr UncheckedArray[byte] {.exported.} = ## Obtains the raw EIP-6493 byte representation of a transaction. ## ## * The returned value is allocated in the given transaction. ## It must neither be released nor written to, and the transaction ## must not be released while the returned value is in use. ## ## Parameters: ## * `transaction` - Transaction. ## * `numBytes` [out] - Length of buffer. ## ## Returns: ## * Buffer with raw EIP-6493 transaction data. ## ## See: ## * https://eips.ethereum.org/EIPS/eip-6493 numBytes[] = distinctBase(transaction[].eip6493Bytes).len.cint if distinctBase(transaction[].eip6493Bytes).len == 0: # https://github.com/nim-lang/Nim/issues/22389 const defaultBytes: cstring = "" return cast[ptr UncheckedArray[byte]](defaultBytes) cast[ptr UncheckedArray[byte]]( addr distinctBase(transaction[].eip6493Bytes)[0]) func ETHTransactionGetNumEip6493SnappyBytes( transaction: ptr ETHTransaction): cint {.exported.} = ## Obtains the length of the Snappy compressed EIP-6493 byte representation ## of a transaction. ## ## Parameters: ## * `transaction` - Transaction. ## ## Returns: ## * Length of Snappy compressed EIP-6493 transaction data. ## ## See: ## * https://eips.ethereum.org/EIPS/eip-6493 snappy.encodeFramed(transaction[].eip6493Bytes).len.cint type ETHLog = object address: ExecutionAddress topics: seq[Eth2Digest] data: seq[byte] ReceiptStatusType {.pure.} = enum Root, Status # EIP-658 ETHReceipt = object statusType: ReceiptStatusType root: Eth2Digest status: bool gasUsed: uint64 logsBloom: BloomLogs logs: seq[ETHLog] bytes: seq[byte] eip6493Bytes: seq[byte] proc ETHReceiptsCreateFromJson( receiptsRoot #[optional]#: ptr Eth2Digest, receiptsJson: cstring, transactions: ptr seq[ETHTransaction]): ptr seq[ETHReceipt] {.exported.} = ## Verifies that JSON receipts data is valid and that it matches ## the given `receiptsRoot`. ## ## * The JSON-RPC `eth_getTransactionReceipt` may be used to obtain ## receipts data for a given transaction hash. For verification, it is ## necessary to obtain the receipt for _all_ transactions within a block. ## Pass a JSON array containing _all_ receipt's `result` as `receiptsJson`. ## The receipts need to be in the same order as the `transactions`. ## ## * The receipt sequence must be destroyed with `ETHReceiptsDestroy` ## once no longer needed, to release memory. ## ## Parameters: ## * `receiptsRoot` - Execution receipts root. ## * `receiptsJson` - Buffer with JSON receipts list. NULL-terminated. ## * `transactions` - Transaction sequence. ## ## Returns: ## * Pointer to an initialized receipt sequence - If successful. ## * `NULL` - If the given `receiptsJson` is malformed or incompatible. ## ## See: ## * https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_gettransactionreceipt var datas = try: # a direct parameter like JrpcConv.decode($receiptsJson, seq[ReceiptObject]) # will cause premature garbage collector kick in. let jsonBytes = $receiptsJson JrpcConv.decode(jsonBytes, seq[ReceiptObject]) except SerializationError: return nil if datas.len != ETHTransactionsGetCount(transactions): return nil var recs = newSeqOfCap[ETHReceipt](datas.len) cumulativeGasUsed = 0'u64 logIndex = uint64.high for i, data in datas: # Sanity check if distinctBase(data.transactionIndex) != i.uint64: return nil # Check fork consistency static: doAssert totalSerializedFields(ReceiptObject) == 17, "Only update this number once code is adjusted to check new fields!" static: doAssert totalSerializedFields(LogObject) == 9, "Only update this number once code is adjusted to check new fields!" let txType = case data.`type`.get(0.Quantity): of 0.Quantity: TxLegacy of 1.Quantity: TxEip2930 of 2.Quantity: TxEip1559 of 3.Quantity: TxEip4844 else: return nil if data.root.isNone and data.status.isNone or data.root.isSome and data.status.isSome: return nil if data.status.isSome and distinctBase(data.status.get) > 1: return nil if distinctBase(data.cumulativeGasUsed) != cumulativeGasUsed + distinctBase(data.gasUsed): return nil cumulativeGasUsed = distinctBase(data.cumulativeGasUsed) for log in data.logs: if log.removed: return nil if log.logIndex.isNone: return nil if distinctBase(log.logIndex.get) != logIndex + 1: return nil logIndex = distinctBase(log.logIndex.get) if log.transactionIndex.isNone: return nil if log.transactionIndex.get != data.transactionIndex: return nil if log.transactionHash.isNone: return nil if log.transactionHash.get != data.transactionHash: return nil if log.blockHash.isNone: return nil if log.blockHash.get != data.blockHash: return nil if log.blockNumber.isNone: return nil if log.blockNumber.get != data.blockNumber: return nil if log.data.len mod 32 != 0: return nil if log.topics.len > 4: return nil # Construct receipt static: doAssert sizeof(int64) == sizeof(data.cumulativeGasUsed) if distinctBase(data.cumulativeGasUsed) > int64.high.uint64: return nil let rec = ExecutionReceipt( receiptType: txType, isHash: data.root.isSome, status: distinctBase(data.status.get(1.Quantity)) != 0'u64, hash: if data.root.isSome: ExecutionHash256(data: distinctBase(data.root.get)) else: default(ExecutionHash256), cumulativeGasUsed: distinctBase(data.cumulativeGasUsed).GasInt, logsBloom: distinctBase(data.logsBloom), logs: data.logs.mapIt(Log( address: distinctBase(it.address), topics: it.topics.mapIt(distinctBase(it)), data: it.data))) rlpBytes = try: rlp.encode(rec) except RlpError: raiseAssert "Unreachable" # Compute EIP-6493 receipt const MAX_TOPICS_PER_LOG = 4 MAX_LOG_DATA_SIZE = 16_777_216 MAX_LOGS_PER_RECEIPT = 2_097_152 type Eip6493Log = object address: ExecutionAddress topics: List[Eth2Digest, Limit MAX_TOPICS_PER_LOG] data: List[byte, Limit MAX_LOG_DATA_SIZE] Eip6493Receipt {.sszStableContainer: 32.} = object root: Opt[Eth2Digest] gas_used: Opt[uint64] contract_address: Opt[ExecutionAddress] logs_bloom: Opt[BloomLogs] logs: Opt[List[Eip6493Log, MAX_LOGS_PER_RECEIPT]] # EIP-658 status: Opt[bool] var eip6493Rec: Eip6493Receipt let transaction = ETHTransactionsGet(transactions, i.cint) if rec.isHash: eip6493Rec.root.ok rec.hash eip6493Rec.gas_used.ok distinctBase(data.gasUsed) # See validity checks if ETHTransactionIsCreatingContract(transaction): eip6493Rec.contract_address.ok ETHTransactionGetTo(transaction)[] eip6493Rec.logs_bloom.ok BloomLogs(data: rec.logsBloom) eip6493Rec.logs.ok List[Eip6493Log, MAX_LOGS_PER_RECEIPT] .init(rec.logs.mapIt(Eip6493Log( address: ExecutionAddress(data: it.address), topics: List[Eth2Digest, Limit MAX_TOPICS_PER_LOG] .init(it.topics.mapIt(Eth2Digest(data: it))), data: List[byte, Limit MAX_LOG_DATA_SIZE].init(it.data)))) if not rec.isHash: eip6493Rec.status.ok rec.status # Nim 1.6.14: Inlining `SSZ.encode` into constructor may corrupt memory. let eip6493Bytes = SSZ.encode(eip6493Rec) recs.add ETHReceipt( statusType: if rec.isHash: ReceiptStatusType.Root else: ReceiptStatusType.Status, root: rec.hash, status: rec.status, gasUsed: distinctBase(data.gasUsed), # Validated during sanity checks. logsBloom: BloomLogs(data: rec.logsBloom), logs: rec.logs.mapIt(ETHLog( address: ExecutionAddress(data: it.address), topics: it.topics.mapIt(Eth2Digest(data: it)), data: it.data)), bytes: rlpBytes, eip6493Bytes: eip6493Bytes) if receiptsRoot != nil: var tr = initHexaryTrie(newMemoryDB()) for i, rec in recs: try: tr.put(rlp.encode(i), rec.bytes) except RlpError: raiseAssert "Unreachable" if tr.rootHash() != receiptsRoot[]: return nil let receipts = seq[ETHReceipt].new() receipts[] = recs receipts.toUnmanagedPtr() proc ETHReceiptsDestroy( receipts: ptr seq[ETHReceipt]) {.exported.} = ## Destroys a receipt sequence. ## ## * The receipt sequence must no longer be used after destruction. ## ## Parameters: ## * `receipts` - Receipt sequence. receipts.destroy() func ETHReceiptsGetCount( receipts: ptr seq[ETHReceipt]): cint {.exported.} = ## Indicates the total number of receipts in a receipt sequence. ## ## * Individual receipts may be inspected using `ETHReceiptsGet`. ## ## Parameters: ## * `receipts` - Receipt sequence. ## ## Returns: ## * Number of available receipts. receipts[].len.cint func ETHReceiptsGet( receipts: ptr seq[ETHReceipt], receiptIndex: cint): ptr ETHReceipt {.exported.} = ## Obtains an individual receipt by sequential index ## in a receipt sequence. ## ## * The returned value is allocated in the given receipt sequence. ## It must neither be released nor written to, and the receipt ## sequence must not be released while the returned value is in use. ## ## Parameters: ## * `receipts` - Receipt sequence. ## * `receiptIndex` - Sequential receipt index. ## ## Returns: ## * Receipt. addr receipts[][receiptIndex.int] func ETHReceiptHasStatus( receipt: ptr ETHReceipt): bool {.exported.} = ## Indicates whether or not a receipt has a status code. ## ## Parameters: ## * `receipt` - Receipt. ## ## Returns: ## * Whether or not the receipt has a status code. ## ## See: ## * https://eips.ethereum.org/EIPS/eip-658 case receipt[].statusType of ReceiptStatusType.Root: false of ReceiptStatusType.Status: true func ETHReceiptGetRoot( receipt: ptr ETHReceipt): ptr Eth2Digest {.exported.} = ## Obtains the intermediate post-state root of a receipt with no status code. ## ## * If the receipt has a status code, this function returns a zero hash. ## ## * The returned value is allocated in the given receipt. ## It must neither be released nor written to, and the receipt ## must not be released while the returned value is in use. ## ## Parameters: ## * `receipt` - Receipt. ## ## Returns: ## * Intermediate post-state root. addr receipt[].root func ETHReceiptGetStatus( receipt: ptr ETHReceipt): bool {.exported.} = ## Obtains the status code of a receipt with a status code. ## ## * If the receipt has no status code, this function returns true. ## ## Parameters: ## * `receipt` - Receipt. ## ## Returns: ## * Status code. ## ## See: ## * https://eips.ethereum.org/EIPS/eip-658 receipt[].status func ETHReceiptGetGasUsed( receipt: ptr ETHReceipt): ptr uint64 {.exported.} = ## Obtains the gas used of a receipt. ## ## * The returned value is allocated in the given receipt. ## It must neither be released nor written to, and the receipt ## must not be released while the returned value is in use. ## ## Parameters: ## * `receipt` - Receipt. ## ## Returns: ## * Gas used. addr receipt[].gasUsed func ETHReceiptGetLogsBloom( receipt: ptr ETHReceipt): ptr BloomLogs {.exported.} = ## Obtains the logs Bloom of a receipt. ## ## * The returned value is allocated in the given receipt. ## It must neither be released nor written to, and the receipt ## must not be released while the returned value is in use. ## ## Parameters: ## * `receipt` - Receipt. ## ## Returns: ## * Logs Bloom. addr receipt[].logsBloom func ETHReceiptGetLogs( receipt: ptr ETHReceipt): ptr seq[ETHLog] {.exported.} = ## Obtains the logs of a receipt. ## ## * The returned value is allocated in the given receipt. ## It must neither be released nor written to, and the receipt ## must not be released while the returned value is in use. ## ## Parameters: ## * `receipt` - Receipt. ## ## Returns: ## * Log sequence. addr receipt[].logs func ETHLogsGetCount( logs: ptr seq[ETHLog]): cint {.exported.} = ## Indicates the total number of logs in a log sequence. ## ## * Individual logs may be inspected using `ETHLogsGet`. ## ## Parameters: ## * `logs` - Log sequence. ## ## Returns: ## * Number of available logs. logs[].len.cint func ETHLogsGet( logs: ptr seq[ETHLog], logIndex: cint): ptr ETHLog {.exported.} = ## Obtains an individual log by sequential index in a log sequence. ## ## * The returned value is allocated in the given log sequence. ## It must neither be released nor written to, and the log sequence ## must not be released while the returned value is in use. ## ## Parameters: ## * `logs` - Log sequence. ## * `logIndex` - Sequential log index. ## ## Returns: ## * Log. addr logs[][logIndex.int] func ETHLogGetAddress( log: ptr ETHLog): ptr ExecutionAddress {.exported.} = ## Obtains the address of a log. ## ## * The returned value is allocated in the given log. ## It must neither be released nor written to, and the log ## must not be released while the returned value is in use. ## ## Parameters: ## * `log` - Log. ## ## Returns: ## * Address. addr log[].address func ETHLogGetNumTopics( log: ptr ETHLog): cint {.exported.} = ## Indicates the total number of topics in a log. ## ## * Individual topics may be inspected using `ETHLogGetTopic`. ## ## Parameters: ## * `log` - Log. ## ## Returns: ## * Number of available topics. log[].topics.len.cint func ETHLogGetTopic( log: ptr ETHLog, topicIndex: cint): ptr Eth2Digest {.exported.} = ## Obtains an individual topic by sequential index in a log. ## ## * The returned value is allocated in the given log. ## It must neither be released nor written to, and the log ## must not be released while the returned value is in use. ## ## Parameters: ## * `log` - Log. ## * `topicIndex` - Sequential topic index. ## ## Returns: ## * Topic. addr log[].topics[topicIndex.int] func ETHLogGetDataBytes( log: ptr ETHLog, numBytes #[out]#: ptr cint): ptr UncheckedArray[byte] {.exported.} = ## Obtains the data of a log. ## ## * The returned value is allocated in the given log. ## It must neither be released nor written to, and the log ## must not be released while the returned value is in use. ## ## Parameters: ## * `log` - Log. ## * `numBytes` [out] - Length of buffer. ## ## Returns: ## * Buffer with data. numBytes[] = log[].data.len.cint if log[].data.len == 0: # https://github.com/nim-lang/Nim/issues/22389 const defaultData: cstring = "" return cast[ptr UncheckedArray[byte]](defaultData) cast[ptr UncheckedArray[byte]](addr log[].data[0]) func ETHReceiptGetBytes( receipt: ptr ETHReceipt, numBytes #[out]#: ptr cint): ptr UncheckedArray[byte] {.exported.} = ## Obtains the raw byte representation of a receipt. ## ## * The returned value is allocated in the given receipt. ## It must neither be released nor written to, and the receipt ## must not be released while the returned value is in use. ## ## Parameters: ## * `receipt` - Receipt. ## * `numBytes` [out] - Length of buffer. ## ## Returns: ## * Buffer with raw receipt data. numBytes[] = distinctBase(receipt[].bytes).len.cint if distinctBase(receipt[].bytes).len == 0: # https://github.com/nim-lang/Nim/issues/22389 const defaultBytes: cstring = "" return cast[ptr UncheckedArray[byte]](defaultBytes) cast[ptr UncheckedArray[byte]](addr distinctBase(receipt[].bytes)[0]) func ETHReceiptGetEip6493Bytes( receipt: ptr ETHReceipt, numBytes #[out]#: ptr cint): ptr UncheckedArray[byte] {.exported.} = ## Obtains the raw EIP-6493 byte representation of a receipt. ## ## * The returned value is allocated in the given receipt. ## It must neither be released nor written to, and the receipt ## must not be released while the returned value is in use. ## ## Parameters: ## * `receipt` - Receipt. ## * `numBytes` [out] - Length of buffer. ## ## Returns: ## * Buffer with raw EIP-6493 receipt data. ## ## See: ## * https://eips.ethereum.org/EIPS/eip-6493 numBytes[] = distinctBase(receipt[].eip6493Bytes).len.cint if distinctBase(receipt[].eip6493Bytes).len == 0: # https://github.com/nim-lang/Nim/issues/22389 const defaultBytes: cstring = "" return cast[ptr UncheckedArray[byte]](defaultBytes) cast[ptr UncheckedArray[byte]]( addr distinctBase(receipt[].eip6493Bytes)[0]) func ETHReceiptGetNumEip6493SnappyBytes( receipt: ptr ETHReceipt): cint {.exported.} = ## Obtains the length of the Snappy compressed EIP-6493 byte representation ## of a receipt. ## ## Parameters: ## * `receipt` - Receipt. ## ## Returns: ## * Length of Snappy compressed EIP-6493 receipt data. ## ## See: ## * https://eips.ethereum.org/EIPS/eip-6493 snappy.encodeFramed(receipt[].eip6493Bytes).len.cint func ETHWithdrawalsGetCount( withdrawals: ptr seq[ETHWithdrawal]): cint {.exported.} = ## Indicates the total number of withdrawals in a withdrawal sequence. ## ## * Individual withdrawals may be inspected using `ETHWithdrawalsGet`. ## ## Parameters: ## * `withdrawals` - Withdrawal sequence. ## ## Returns: ## * Number of available withdrawals. withdrawals[].len.cint func ETHWithdrawalsGet( withdrawals: ptr seq[ETHWithdrawal], withdrawalIndex: cint): ptr ETHWithdrawal {.exported.} = ## Obtains an individual withdrawal by sequential index ## in a withdrawal sequence. ## ## * The returned value is allocated in the given withdrawal sequence. ## It must neither be released nor written to, and the withdrawal ## sequence must not be released while the returned value is in use. ## ## Parameters: ## * `withdrawals` - Withdrawal sequence. ## * `withdrawalIndex` - Sequential withdrawal index. ## ## Returns: ## * Withdrawal. addr withdrawals[][withdrawalIndex.int] func ETHWithdrawalGetIndex( withdrawal: ptr ETHWithdrawal): ptr uint64 {.exported.} = ## Obtains the index of a withdrawal. ## ## * The returned value is allocated in the given withdrawal. ## It must neither be released nor written to, and the withdrawal ## must not be released while the returned value is in use. ## ## Parameters: ## * `withdrawal` - Withdrawal. ## ## Returns: ## * Index. addr withdrawal[].index func ETHWithdrawalGetValidatorIndex( withdrawal: ptr ETHWithdrawal): ptr uint64 {.exported.} = ## Obtains the validator index of a withdrawal. ## ## * The returned value is allocated in the given withdrawal. ## It must neither be released nor written to, and the withdrawal ## must not be released while the returned value is in use. ## ## Parameters: ## * `withdrawal` - Withdrawal. ## ## Returns: ## * Validator index. addr withdrawal[].validatorIndex func ETHWithdrawalGetAddress( withdrawal: ptr ETHWithdrawal): ptr ExecutionAddress {.exported.} = ## Obtains the address of a withdrawal. ## ## * The returned value is allocated in the given withdrawal. ## It must neither be released nor written to, and the withdrawal ## must not be released while the returned value is in use. ## ## Parameters: ## * `withdrawal` - Withdrawal. ## ## Returns: ## * Address. addr withdrawal[].address func ETHWithdrawalGetAmount( withdrawal: ptr ETHWithdrawal): ptr uint64 {.exported.} = ## Obtains the amount of a withdrawal. ## ## * The returned value is allocated in the given withdrawal. ## It must neither be released nor written to, and the withdrawal ## must not be released while the returned value is in use. ## ## Parameters: ## * `withdrawal` - Withdrawal. ## ## Returns: ## * Amount. addr withdrawal[].amount func ETHWithdrawalGetBytes( withdrawal: ptr ETHWithdrawal, numBytes #[out]#: ptr cint): ptr UncheckedArray[byte] {.exported.} = ## Obtains the raw byte representation of a withdrawal. ## ## * The returned value is allocated in the given withdrawal. ## It must neither be released nor written to, and the withdrawal ## must not be released while the returned value is in use. ## ## Parameters: ## * `withdrawal` - Withdrawal. ## * `numBytes` [out] - Length of buffer. ## ## Returns: ## * Buffer with raw withdrawal data. numBytes[] = distinctBase(withdrawal[].bytes).len.cint if distinctBase(withdrawal[].bytes).len == 0: # https://github.com/nim-lang/Nim/issues/22389 const defaultBytes: cstring = "" return cast[ptr UncheckedArray[byte]](defaultBytes) cast[ptr UncheckedArray[byte]](addr distinctBase(withdrawal[].bytes)[0])