mirror of
https://github.com/status-im/nimbus-eth1.git
synced 2025-02-12 03:57:05 +00:00
It is common for many accounts to share the same code - at the database level, code is stored by hash meaning only one copy exists per unique program but when loaded in memory, a copy is made for each account. Further, every time we execute the code, it must be scanned for invalid jump destinations which slows down EVM exeuction. Finally, the extcodesize call causes code to be loaded even if only the size is needed. This PR improves on all these points by introducing a shared CodeBytesRef type whose code section is immutable and that can be shared between accounts. Further, a dedicated `len` API call is added so that the EXTCODESIZE opcode can operate without polluting the GC and code cache, for cases where only the size is requested - rocksdb will in this case cache the code itself in the row cache meaning that lookup of the code itself remains fast when length is asked for first. With 16k code entries, there's a 90% hit rate which goes up to 99% during the 2.3M attack - the cache significantly lowers memory consumption and execution time not only during this event but across the board.
212 lines
7.1 KiB
Nim
212 lines
7.1 KiB
Nim
# Nimbus
|
|
# Copyright (c) 2023-2024 Status Research & Development GmbH
|
|
# Licensed under either of
|
|
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
|
|
# http://www.apache.org/licenses/LICENSE-2.0)
|
|
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or
|
|
# http://opensource.org/licenses/MIT)
|
|
# at your option. This file may not be copied, modified, or distributed except
|
|
# according to those terms.
|
|
|
|
import
|
|
std/[os, strutils],
|
|
stew/arrayops,
|
|
nimcrypto/sha2,
|
|
kzg4844/kzg_ex as kzg,
|
|
results,
|
|
stint,
|
|
../constants,
|
|
../common/common
|
|
|
|
{.push raises: [].}
|
|
|
|
type
|
|
Bytes64 = array[64, byte]
|
|
|
|
const
|
|
BLS_MODULUS_STR = "52435875175126190479447740508185965837690552500527637822603658699938581184513"
|
|
BLS_MODULUS* = parse(BLS_MODULUS_STR, UInt256, 10).toBytesBE
|
|
PrecompileInputLength = 192
|
|
|
|
proc pointEvaluationResult(): Bytes64 {.compileTime.} =
|
|
result[0..<32] = FIELD_ELEMENTS_PER_BLOB.u256.toBytesBE[0..^1]
|
|
result[32..^1] = BLS_MODULUS[0..^1]
|
|
|
|
const
|
|
PointEvaluationResult* = pointEvaluationResult()
|
|
POINT_EVALUATION_PRECOMPILE_GAS* = 50000.GasInt
|
|
|
|
|
|
# kzgToVersionedHash implements kzg_to_versioned_hash from EIP-4844
|
|
proc kzgToVersionedHash*(kzg: kzg.KzgCommitment): VersionedHash =
|
|
result = sha256.digest(kzg)
|
|
result.data[0] = VERSIONED_HASH_VERSION_KZG
|
|
|
|
# pointEvaluation implements point_evaluation_precompile from EIP-4844
|
|
# return value and gas consumption is handled by pointEvaluation in
|
|
# precompiles.nim
|
|
proc pointEvaluation*(input: openArray[byte]): Result[void, string] =
|
|
# Verify p(z) = y given commitment that corresponds to the polynomial p(x) and a KZG proof.
|
|
# Also verify that the provided commitment matches the provided versioned_hash.
|
|
# The data is encoded as follows: versioned_hash | z | y | commitment | proof |
|
|
|
|
if input.len != PrecompileInputLength:
|
|
return err("invalid input length")
|
|
|
|
let
|
|
versionedHash = KzgBytes32.initCopyFrom(input.toOpenArray(0, 31))
|
|
z = KzgBytes32.initCopyFrom(input.toOpenArray(32, 63))
|
|
y = KzgBytes32.initCopyFrom(input.toOpenArray(64, 95))
|
|
commitment = KzgBytes48.initCopyFrom(input.toOpenArray(96, 143))
|
|
kzgProof = KzgBytes48.initCopyFrom(input.toOpenArray(144, 191))
|
|
|
|
if kzgToVersionedHash(commitment).data != versionedHash:
|
|
return err("versionedHash should equal to kzgToVersionedHash(commitment)")
|
|
|
|
# Verify KZG proof
|
|
let res = kzg.verifyKzgProof(commitment, z, y, kzgProof)
|
|
if res.isErr:
|
|
return err(res.error)
|
|
|
|
# The actual verify result
|
|
if not res.get():
|
|
return err("Failed to verify KZG proof")
|
|
|
|
ok()
|
|
|
|
# calcExcessBlobGas implements calc_excess_data_gas from EIP-4844
|
|
proc calcExcessBlobGas*(parent: BlockHeader): uint64 =
|
|
let
|
|
excessBlobGas = parent.excessBlobGas.get(0'u64)
|
|
blobGasUsed = parent.blobGasUsed.get(0'u64)
|
|
|
|
if excessBlobGas + blobGasUsed < TARGET_BLOB_GAS_PER_BLOCK:
|
|
0'u64
|
|
else:
|
|
excessBlobGas + blobGasUsed - TARGET_BLOB_GAS_PER_BLOCK
|
|
|
|
# fakeExponential approximates factor * e ** (num / denom) using a taylor expansion
|
|
# as described in the EIP-4844 spec.
|
|
func fakeExponential*(factor, numerator, denominator: UInt256): UInt256 =
|
|
var
|
|
i = 1.u256
|
|
output = 0.u256
|
|
numeratorAccum = factor * denominator
|
|
|
|
while numeratorAccum > 0.u256:
|
|
output += numeratorAccum
|
|
numeratorAccum = (numeratorAccum * numerator) div (denominator * i)
|
|
i = i + 1.u256
|
|
|
|
output div denominator
|
|
|
|
proc getTotalBlobGas*(tx: Transaction): uint64 =
|
|
GAS_PER_BLOB * tx.versionedHashes.len.uint64
|
|
|
|
proc getTotalBlobGas*(versionedHashesLen: int): uint64 =
|
|
GAS_PER_BLOB * versionedHashesLen.uint64
|
|
|
|
# getBlobBaseFee implements get_data_gas_price from EIP-4844
|
|
func getBlobBaseFee*(excessBlobGas: uint64): UInt256 =
|
|
fakeExponential(
|
|
MIN_BLOB_GASPRICE.u256,
|
|
excessBlobGas.u256,
|
|
BLOB_GASPRICE_UPDATE_FRACTION.u256
|
|
)
|
|
|
|
proc calcDataFee*(versionedHashesLen: int,
|
|
excessBlobGas: uint64): UInt256 =
|
|
getTotalBlobGas(versionedHashesLen).u256 *
|
|
getBlobBaseFee(excessBlobGas)
|
|
|
|
func blobGasUsed(txs: openArray[Transaction]): uint64 =
|
|
for tx in txs:
|
|
result += tx.getTotalBlobGas
|
|
|
|
# https://eips.ethereum.org/EIPS/eip-4844
|
|
func validateEip4844Header*(
|
|
com: CommonRef, header, parentHeader: BlockHeader,
|
|
txs: openArray[Transaction]): Result[void, string] {.raises: [].} =
|
|
|
|
if not com.forkGTE(Cancun):
|
|
if header.blobGasUsed.isSome:
|
|
return err("unexpected EIP-4844 blobGasUsed in block header")
|
|
|
|
if header.excessBlobGas.isSome:
|
|
return err("unexpected EIP-4844 excessBlobGas in block header")
|
|
|
|
return ok()
|
|
|
|
if header.blobGasUsed.isNone:
|
|
return err("expect EIP-4844 blobGasUsed in block header")
|
|
|
|
if header.excessBlobGas.isNone:
|
|
return err("expect EIP-4844 excessBlobGas in block header")
|
|
|
|
let
|
|
headerBlobGasUsed = header.blobGasUsed.get()
|
|
blobGasUsed = blobGasUsed(txs)
|
|
headerExcessBlobGas = header.excessBlobGas.get
|
|
excessBlobGas = calcExcessBlobGas(parentHeader)
|
|
|
|
if blobGasUsed > MAX_BLOB_GAS_PER_BLOCK:
|
|
return err("blobGasUsed " & $blobGasUsed & " exceeds maximum allowance " & $MAX_BLOB_GAS_PER_BLOCK)
|
|
|
|
if headerBlobGasUsed != blobGasUsed:
|
|
return err("calculated blobGas not equal header.blobGasUsed")
|
|
|
|
if headerExcessBlobGas != excessBlobGas:
|
|
return err("calculated excessBlobGas not equal header.excessBlobGas")
|
|
|
|
return ok()
|
|
|
|
proc validateBlobTransactionWrapper*(tx: PooledTransaction):
|
|
Result[void, string] {.raises: [].} =
|
|
if tx.networkPayload.isNil:
|
|
return err("tx wrapper is none")
|
|
|
|
# note: assert blobs are not malformatted
|
|
let goodFormatted = tx.tx.versionedHashes.len ==
|
|
tx.networkPayload.commitments.len and
|
|
tx.tx.versionedHashes.len ==
|
|
tx.networkPayload.blobs.len and
|
|
tx.tx.versionedHashes.len ==
|
|
tx.networkPayload.proofs.len
|
|
|
|
if not goodFormatted:
|
|
return err("tx wrapper is ill formatted")
|
|
|
|
let commitments = tx.networkPayload.commitments
|
|
|
|
# Verify that commitments match the blobs by checking the KZG proof
|
|
let res = kzg.verifyBlobKzgProofBatch(
|
|
tx.networkPayload.blobs,
|
|
commitments,
|
|
tx.networkPayload.proofs)
|
|
|
|
if res.isErr:
|
|
return err(res.error)
|
|
|
|
# Actual verification result
|
|
if not res.get():
|
|
return err("Failed to verify network payload of a transaction")
|
|
|
|
# Now that all commitments have been verified, check that versionedHashes matches the commitments
|
|
for i in 0 ..< tx.tx.versionedHashes.len:
|
|
# this additional check also done in tx validation
|
|
if tx.tx.versionedHashes[i].data[0] != VERSIONED_HASH_VERSION_KZG:
|
|
return err("wrong kzg version in versioned hash at index " & $i)
|
|
|
|
if tx.tx.versionedHashes[i] != kzgToVersionedHash(commitments[i]):
|
|
return err("tx versioned hash not match commitments at index " & $i)
|
|
|
|
ok()
|
|
|
|
proc loadKzgTrustedSetup*(): Result[void, string] =
|
|
const
|
|
vendorDir = currentSourcePath.parentDir.replace('\\', '/') & "/../../vendor"
|
|
trustedSetupDir = vendorDir & "/nim-kzg4844/kzg4844/csources/src"
|
|
trustedSetup = staticRead trustedSetupDir & "/trusted_setup.txt"
|
|
|
|
Kzg.loadTrustedSetupFromString(trustedSetup)
|