Repositioning blob hash validation in newPayload of engine API (#2141)

This commit is contained in:
andri lim 2024-04-20 02:43:13 +07:00 committed by GitHub
parent 7d9e1d8607
commit 6694e240d7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 110 additions and 83 deletions

View File

@ -425,7 +425,7 @@ proc customizePayload*(cust: CustomPayloadData, data: ExecutableData): Executabl
if cust.versionedHashesCustomizer.isNil.not: if cust.versionedHashesCustomizer.isNil.not:
doAssert(data.versionedHashes.isSome) doAssert(data.versionedHashes.isSome)
result.versionedHashes = cust.versionedHashesCustomizer.getVersionedHashes(data.versionedHashes.get) result.versionedHashes = cust.versionedHashesCustomizer.getVersionedHashes(data.versionedHashes.get)
# Base new payload directive call cust. # Base new payload directive call cust.
# Used as base to other customizers. # Used as base to other customizers.
type type
@ -615,22 +615,22 @@ proc generateInvalidPayload*(sender: TxSender, data: ExecutableData, payloadFiel
excessBlobGas: some(modExcessBlobGas), excessBlobGas: some(modExcessBlobGas),
) )
of InvalidVersionedHashesVersion: of InvalidVersionedHashesVersion:
doAssert(data.versionedHashes.isNone, "no versioned hashes available for modification") doAssert(data.versionedHashes.isSome, "no versioned hashes available for modification")
customPayloadMod = CustomPayloadData( customPayloadMod = CustomPayloadData(
versionedHashesCustomizer: IncreaseVersionVersionedHashes(), versionedHashesCustomizer: IncreaseVersionVersionedHashes(),
) )
of InvalidVersionedHashes: of InvalidVersionedHashes:
doAssert(data.versionedHashes.isNone, "no versioned hashes available for modification") doAssert(data.versionedHashes.isSome, "no versioned hashes available for modification")
customPayloadMod = CustomPayloadData( customPayloadMod = CustomPayloadData(
versionedHashesCustomizer: CorruptVersionedHashes(), versionedHashesCustomizer: CorruptVersionedHashes(),
) )
of IncompleteVersionedHashes: of IncompleteVersionedHashes:
doAssert(data.versionedHashes.isNone, "no versioned hashes available for modification") doAssert(data.versionedHashes.isSome, "no versioned hashes available for modification")
customPayloadMod = CustomPayloadData( customPayloadMod = CustomPayloadData(
versionedHashesCustomizer: RemoveVersionedHash(), versionedHashesCustomizer: RemoveVersionedHash(),
) )
of ExtraVersionedHashes: of ExtraVersionedHashes:
doAssert(data.versionedHashes.isNone, "no versioned hashes available for modification") doAssert(data.versionedHashes.isSome, "no versioned hashes available for modification")
customPayloadMod = CustomPayloadData( customPayloadMod = CustomPayloadData(
versionedHashesCustomizer: ExtraVersionedHash(), versionedHashesCustomizer: ExtraVersionedHash(),
) )

View File

@ -1,5 +1,5 @@
# Nimbus # Nimbus
# Copyright (c) 2023 Status Research & Development GmbH # Copyright (c) 2023-2024 Status Research & Development GmbH
# Licensed under either of # Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
# http://www.apache.org/licenses/LICENSE-2.0) # http://www.apache.org/licenses/LICENSE-2.0)

View File

@ -1865,7 +1865,7 @@ proc makeCancunTest(): seq[EngineSpec] =
mainFork: ForkCancun, mainFork: ForkCancun,
fieldModification: t, fieldModification: t,
) )
#[
# Invalid Payload Tests # Invalid Payload Tests
const const
invalidFields = [ invalidFields = [
@ -1911,7 +1911,6 @@ proc makeCancunTest(): seq[EngineSpec] =
invalidDetectedOnSync: invalidDetectedOnSync, invalidDetectedOnSync: invalidDetectedOnSync,
nilLatestValidHash : nilLatestValidHash, nilLatestValidHash : nilLatestValidHash,
) )
]#
# Invalid Transaction ChainID Tests # Invalid Transaction ChainID Tests
result.add InvalidTxChainIDTest( result.add InvalidTxChainIDTest(

View File

@ -22,11 +22,16 @@ import
type type
BaseTx* = object of RootObj BaseTx* = object of RootObj
recipient*: Option[EthAddress] recipient* : Option[EthAddress]
gasLimit* : GasInt gasLimit* : GasInt
amount* : UInt256 amount* : UInt256
payload* : seq[byte] payload* : seq[byte]
txType* : Option[TxType] txType* : Option[TxType]
gasTip* : GasInt
gasFee* : GasInt
blobGasFee*: UInt256
blobCount* : int
blobID* : BlobID
BigInitcodeTx* = object of BaseTx BigInitcodeTx* = object of BaseTx
initcodeLength*: int initcodeLength*: int
@ -35,11 +40,6 @@ type
# Blob transaction creator # Blob transaction creator
BlobTx* = object of BaseTx BlobTx* = object of BaseTx
gasFee* : GasInt
gasTip* : GasInt
blobGasFee*: UInt256
blobID* : BlobID
blobCount* : int
TestAccount* = object TestAccount* = object
key* : PrivateKey key* : PrivateKey
@ -77,6 +77,7 @@ const
TestAccountCount = 1000 TestAccountCount = 1000
gasPrice* = 30.gwei gasPrice* = 30.gwei
gasTipPrice* = 1.gwei gasTipPrice* = 1.gwei
blobGasPrice* = 1.gwei
func toAddress(key: PrivateKey): EthAddress = func toAddress(key: PrivateKey): EthAddress =
toKeyPair(key).pubkey.toCanonicalAddress() toKeyPair(key).pubkey.toCanonicalAddress()
@ -129,37 +130,75 @@ proc getTxType(tc: BaseTx, nonce: uint64): TxType =
else: else:
tc.txType.get tc.txType.get
proc makeTx(params: MakeTxParams, tc: BaseTx): Transaction = proc makeTxOfType(params: MakeTxParams, tc: BaseTx): Transaction =
const let
gasFeeCap = gasPrice gasFeeCap = if tc.gasFee != 0.GasInt: tc.gasFee
gasTipCap = gasTipPrice else: gasPrice
gasTipCap = if tc.gasTip != 0.GasInt: tc.gasTip
else: gasTipPrice
let txType = tc.getTxType(params.nonce) let txType = tc.getTxType(params.nonce)
case txType
of TxLegacy:
Transaction(
txType : TxLegacy,
nonce : params.nonce,
to : tc.recipient,
value : tc.amount,
gasLimit: tc.gasLimit,
gasPrice: gasPrice,
payload : tc.payload
)
of TxEip1559:
Transaction(
txType : TxEIP1559,
nonce : params.nonce,
gasLimit: tc.gasLimit,
maxFee : gasFeeCap,
maxPriorityFee: gasTipCap,
to : tc.recipient,
value : tc.amount,
payload : tc.payload,
chainId : params.chainId
)
of TxEip4844:
doAssert(tc.recipient.isSome, "recipient must be some")
let
blobCount = if tc.blobCount != 0: tc.blobCount
else: MAX_BLOBS_PER_BLOCK
blobFeeCap = if tc.blobGasFee != 0.u256: tc.blobGasFee
else: blobGasPrice.u256
# Need tx wrap data that will pass blob verification
var blobData = blobDataGenerator(tc.blobID, blobCount)
#tc.blobID += BlobID(blobCount)
Transaction(
txType : TxEIP4844,
nonce : params.nonce,
chainId : params.chainId,
maxFee : gasFeeCap,
maxPriorityFee: gasTipCap,
gasLimit: tc.gasLimit,
to : tc.recipient,
value : tc.amount,
payload : tc.payload,
#AccessList: tc.AccessList,
maxFeePerBlobGas: blobFeeCap,
versionedHashes: system.move(blobData.hashes),
networkPayload: NetworkPayload(
blobs: system.move(blobData.blobs),
commitments: system.move(blobData.commitments),
proofs: system.move(blobData.proofs),
)
)
else:
doAssert(false, "unsupported tx type")
Transaction()
proc makeTx(params: MakeTxParams, tc: BaseTx): Transaction =
# Build the transaction depending on the specified type # Build the transaction depending on the specified type
let tx = if txType == TxLegacy: let tx = makeTxOfType(params, tc)
Transaction(
txType : TxLegacy,
nonce : params.nonce,
to : tc.recipient,
value : tc.amount,
gasLimit: tc.gasLimit,
gasPrice: gasPrice,
payload : tc.payload
)
else:
Transaction(
txType : TxEIP1559,
nonce : params.nonce,
gasLimit: tc.gasLimit,
maxFee : gasFeeCap,
maxPriorityFee: gasTipCap,
to : tc.recipient,
value : tc.amount,
payload : tc.payload,
chainId : params.chainId
)
signTransaction(tx, params.key, params.chainId, eip155 = true) signTransaction(tx, params.key, params.chainId, eip155 = true)
proc makeTx(params: MakeTxParams, tc: BigInitcodeTx): Transaction = proc makeTx(params: MakeTxParams, tc: BigInitcodeTx): Transaction =
@ -263,13 +302,13 @@ proc makeTx*(params: MakeTxParams, tc: BlobTx): Transaction =
let data = blobDataGenerator(tc.blobID, tc.blobCount) let data = blobDataGenerator(tc.blobID, tc.blobCount)
doAssert(tc.recipient.isSome, "nil recipient address") doAssert(tc.recipient.isSome, "nil recipient address")
# Collect fields for transaction
let let
gasFeeCap = if tc.gasFee != 0.GasInt: tc.gasFee gasFeeCap = if tc.gasFee != 0.GasInt: tc.gasFee
else: gasPrice else: gasPrice
gasTipCap = if tc.gasTip != 0.GasInt: tc.gasTip gasTipCap = if tc.gasTip != 0.GasInt: tc.gasTip
else: gasTipPrice else: gasTipPrice
# Collect fields for transaction
let unsignedTx = Transaction( let unsignedTx = Transaction(
txType : TxEip4844, txType : TxEip4844,
chainId : params.chainId, chainId : params.chainId,

View File

@ -8,11 +8,6 @@
# those terms. # those terms.
import import
std/[options, typetraits],
eth/common,
./web3_eth_conv,
./beacon_engine,
web3/execution_types,
./api_handler/api_utils, ./api_handler/api_utils,
./api_handler/api_getpayload, ./api_handler/api_getpayload,
./api_handler/api_getbodies, ./api_handler/api_getbodies,
@ -24,25 +19,6 @@ import
# Public functions # Public functions
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
{.push gcsafe, raises:[CatchableError].}
func validateVersionedHashed*(payload: ExecutionPayload,
expected: openArray[Web3Hash]): bool =
var versionedHashes: seq[common.Hash256]
for x in payload.transactions:
let tx = rlp.decode(distinctBase(x), Transaction)
versionedHashes.add tx.versionedHashes
if versionedHashes.len != expected.len:
return false
for i, x in expected:
if distinctBase(x) != versionedHashes[i].data:
return false
true
{.pop.}
export export
invalidStatus, invalidStatus,
getPayload, getPayload,

View File

@ -19,6 +19,21 @@ import
{.push gcsafe, raises:[CatchableError].} {.push gcsafe, raises:[CatchableError].}
func validateVersionedHashed(payload: ExecutionPayload,
expected: openArray[Web3Hash]): bool =
var versionedHashes: seq[common.Hash256]
for x in payload.transactions:
let tx = rlp.decode(distinctBase(x), Transaction)
versionedHashes.add tx.versionedHashes
if versionedHashes.len != expected.len:
return false
for i, x in expected:
if distinctBase(x) != versionedHashes[i].data:
return false
true
template validateVersion(com, timestamp, version, apiVersion) = template validateVersion(com, timestamp, version, apiVersion) =
if apiVersion == Version.V4: if apiVersion == Version.V4:
if not com.isPragueOrLater(timestamp): if not com.isPragueOrLater(timestamp):
@ -83,6 +98,7 @@ template validatePayload(apiVersion, version, payload) =
proc newPayload*(ben: BeaconEngineRef, proc newPayload*(ben: BeaconEngineRef,
apiVersion: Version, apiVersion: Version,
payload: ExecutionPayload, payload: ExecutionPayload,
versionedHashes = none(seq[Web3Hash]),
beaconRoot = none(Web3Hash)): PayloadStatusV1 = beaconRoot = none(Web3Hash)): PayloadStatusV1 =
trace "Engine API request received", trace "Engine API request received",
@ -90,7 +106,7 @@ proc newPayload*(ben: BeaconEngineRef,
number = payload.blockNumber, number = payload.blockNumber,
hash = payload.blockHash hash = payload.blockHash
if apiVersion == Version.V3: if apiVersion >= Version.V3:
if beaconRoot.isNone: if beaconRoot.isNone:
raise invalidParams("newPayloadV3 expect beaconRoot but got none") raise invalidParams("newPayloadV3 expect beaconRoot but got none")
@ -108,6 +124,12 @@ proc newPayload*(ben: BeaconEngineRef,
header.validateBlockHash(blockHash, version).isOkOr: header.validateBlockHash(blockHash, version).isOkOr:
return error return error
if apiVersion >= Version.V3:
if versionedHashes.isNone:
raise invalidParams("newPayload" & $apiVersion &
" expect blobVersionedHashes but got none")
if not validateVersionedHashed(payload, versionedHashes.get):
return invalidStatus(header.parentHash, "invalid blob versionedHashes")
# If we already have the block locally, ignore the entire execution and just # If we already have the block locally, ignore the entire execution and just
# return a fake success. # return a fake success.

View File

@ -13,8 +13,7 @@ import
web3/[conversions, execution_types], web3/[conversions, execution_types],
../beacon/api_handler, ../beacon/api_handler,
../beacon/beacon_engine, ../beacon/beacon_engine,
../beacon/web3_eth_conv, ../beacon/web3_eth_conv
../beacon/api_handler/api_utils
{.push raises: [].} {.push raises: [].}
@ -53,20 +52,12 @@ proc setupEngineAPI*(engine: BeaconEngineRef, server: RpcServer) =
server.rpc("engine_newPayloadV3") do(payload: ExecutionPayload, server.rpc("engine_newPayloadV3") do(payload: ExecutionPayload,
expectedBlobVersionedHashes: Option[seq[Web3Hash]], expectedBlobVersionedHashes: Option[seq[Web3Hash]],
parentBeaconBlockRoot: Option[Web3Hash]) -> PayloadStatusV1: parentBeaconBlockRoot: Option[Web3Hash]) -> PayloadStatusV1:
if expectedBlobVersionedHashes.isNone: return engine.newPayload(Version.V3, payload, expectedBlobVersionedHashes, parentBeaconBlockRoot)
raise invalidParams("newPayloadV3 expect blobVersionedHashes but got none")
if not validateVersionedHashed(payload, expectedBlobVersionedHashes.get):
return invalidStatus()
return engine.newPayload(Version.V3, payload, parentBeaconBlockRoot)
server.rpc("engine_newPayloadV4") do(payload: ExecutionPayload, server.rpc("engine_newPayloadV4") do(payload: ExecutionPayload,
expectedBlobVersionedHashes: Option[seq[Web3Hash]], expectedBlobVersionedHashes: Option[seq[Web3Hash]],
parentBeaconBlockRoot: Option[Web3Hash]) -> PayloadStatusV1: parentBeaconBlockRoot: Option[Web3Hash]) -> PayloadStatusV1:
if expectedBlobVersionedHashes.isNone: return engine.newPayload(Version.V4, payload, expectedBlobVersionedHashes, parentBeaconBlockRoot)
raise invalidParams("newPayloadV4 expect blobVersionedHashes but got none")
if not validateVersionedHashed(payload, expectedBlobVersionedHashes.get):
return invalidStatus()
return engine.newPayload(Version.V4, payload, parentBeaconBlockRoot)
server.rpc("engine_getPayloadV1") do(payloadId: PayloadID) -> ExecutionPayloadV1: server.rpc("engine_getPayloadV1") do(payloadId: PayloadID) -> ExecutionPayloadV1:
return engine.getPayload(Version.V1, payloadId).executionPayload.V1 return engine.getPayload(Version.V1, payloadId).executionPayload.V1