# beacon_chain # Copyright (c) 2019-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. # Required for deserialisation of ValidatorSig in Attestation due to # https://github.com/nim-lang/Nim/issues/11225 {.push raises: [].} import stew/ptrops, chronicles, ../beacon_chain/networking/network_metadata, ../beacon_chain/spec/datatypes/phase0, ../beacon_chain/spec/[ beaconstate, eth2_ssz_serialization, forks, validator, state_transition, state_transition_block] type AttestationInput = object state: phase0.BeaconState attestation: phase0.Attestation AttesterSlashingInput = object state: phase0.BeaconState attesterSlashing: phase0.AttesterSlashing BlockInput = object state: phase0.BeaconState beaconBlock: phase0.SignedBeaconBlock BlockHeaderInput = BlockInput DepositInput = object state: phase0.BeaconState deposit: Deposit ProposerSlashingInput = object state: phase0.BeaconState proposerSlashing: ProposerSlashing VoluntaryExitInput = object state: phase0.BeaconState exit: SignedVoluntaryExit # This and AssertionError are raised to indicate programming bugs # A wrapper to allow exception tracking to identify unexpected exceptions FuzzCrashError = object of CatchableError # TODO: change ptr uint to ptr csize_t when available in newer Nim version. func copyState(state: phase0.BeaconState, xoutput: ptr byte, xoutput_size: ptr uint): bool {.raises: [FuzzCrashError].} = var resultState = try: SSZ.encode(state) except IOError as e: # Shouldn't occur as the writer isn't a file raise newException(FuzzCrashError, "Unexpected failure to serialize.", e) if unlikely(resultState.len.uint > xoutput_size[]): let msg = ( "Not enough xoutput buffer provided to nimbus harness. Provided: " & $(xoutput_size[]) & "Required: " & $resultState.len.uint ) raise newException(FuzzCrashError, msg) xoutput_size[] = resultState.len.uint # TODO: improvement might be to write directly to buffer with xoutputStream # and SszWriter (but then need to ensure length doesn't overflow) copyMem(xoutput, unsafeAddr resultState[0], xoutput_size[]) result = true template decodeAndProcess(typ, process: untyped): bool = let flags {.inject.} = if disable_bls: {skipBlsValidation} else: {} var cache {.used, inject.} = StateCache() data {.inject.} = newClone( try: SSZ.decode(input, typ) except SerializationError as e: raise newException( FuzzCrashError, "Malformed SSZ, likely bug in preprocessing.", e) ) let processOk = try: process except IOError as e: raise newException( FuzzCrashError, "Unexpected (logging?) IOError in state transition", e, ) except ValueError as e: raise newException( FuzzCrashError, "Unexpected (logging?) IOError in state transition", e) except Exception as e: # TODO why an Exception? # Lots of vendor code looks like it might raise a bare exception type raise newException(FuzzCrashError, "Unexpected Exception in state transition", e) if processOk: copyState(data.state, xoutput, xoutput_size) else: false proc nfuzz_attestation(input: openArray[byte], xoutput: ptr byte, xoutput_size: ptr uint, disable_bls: bool): bool {.exportc, raises: [FuzzCrashError].} = decodeAndProcess(AttestationInput): process_attestation(data.state, data.attestation, flags, 0.Gwei, cache).isOk proc nfuzz_attester_slashing(input: openArray[byte], xoutput: ptr byte, xoutput_size: ptr uint, disable_bls: bool): bool {.exportc, raises: [FuzzCrashError].} = decodeAndProcess(AttesterSlashingInput): process_attester_slashing(getRuntimeConfig(some "mainnet"), data.state, data.attesterSlashing, flags, get_state_exit_queue_info(data.state), cache).isOk proc nfuzz_block(input: openArray[byte], xoutput: ptr byte, xoutput_size: ptr uint, disable_bls: bool): bool {.exportc, raises: [FuzzCrashError].} = # There's not a perfect approach here, but it's not worth switching the rest # and requiring HashedBeaconState (yet). So to keep consistent, puts wrapper # only in one function. proc state_transition( cfg: RuntimeConfig, data: auto, blck: auto, flags: auto, rollback: RollbackForkedHashedProc): auto = var fhState = (ref ForkedHashedBeaconState)( phase0Data: phase0.HashedBeaconState( data: data.state, root: hash_tree_root(data.state)), kind: ConsensusFork.Phase0) cache = StateCache() info = ForkedEpochInfo() result = state_transition( cfg, fhState[], blck, cache, info, flags, rollback) data.state = fhState.phase0Data.data decodeAndProcess(BlockInput): state_transition( getRuntimeConfig(some "mainnet"), data, data.beaconBlock, flags, noRollback).isOk func nfuzz_block_header(input: openArray[byte], xoutput: ptr byte, xoutput_size: ptr uint, disable_bls: bool): bool {.exportc, raises: [FuzzCrashError].} = decodeAndProcess(BlockHeaderInput): process_block_header(data.state, data.beaconBlock.message, flags, cache).isOk from ".."/beacon_chain/bloomfilter import constructBloomFilter proc nfuzz_deposit(input: openArray[byte], xoutput: ptr byte, xoutput_size: ptr uint, disable_bls: bool): bool {.exportc, raises: [FuzzCrashError].} = decodeAndProcess(DepositInput): process_deposit( getRuntimeConfig(some "mainnet"), data.state, constructBloomFilter(data.state.validators.asSeq)[], data.deposit, flags).isOk proc nfuzz_proposer_slashing(input: openArray[byte], xoutput: ptr byte, xoutput_size: ptr uint, disable_bls: bool): bool {.exportc, raises: [FuzzCrashError].} = decodeAndProcess(ProposerSlashingInput): process_proposer_slashing(getRuntimeConfig(some "mainnet"), data.state, data.proposerSlashing, flags, get_state_exit_queue_info(data.state), cache).isOk proc nfuzz_voluntary_exit(input: openArray[byte], xoutput: ptr byte, xoutput_size: ptr uint, disable_bls: bool): bool {.exportc, raises: [FuzzCrashError].} = decodeAndProcess(VoluntaryExitInput): process_voluntary_exit(getRuntimeConfig(some "mainnet"), data.state, data.exit, flags, get_state_exit_queue_info(data.state), cache).isOk # Note: Could also accept raw input pointer and access list_size + seed here. # However, list_size needs to be known also outside this proc to allocate xoutput. # TODO: rework to copy immediatly in an uint8 openArray, considering we have to # go over the list anyhow? func nfuzz_shuffle(input_seed: ptr byte, xoutput: var openArray[uint64]): bool {.exportc, raises: [].} = var seed: Eth2Digest # Should be OK as max 2 bytes are passed by the framework. let list_size = xoutput.len copyMem(addr(seed.data), input_seed, sizeof(seed.data)) var shuffled_seq: seq[ValidatorIndex] for i in 0..