Skip to content

Commit

Permalink
client: Merge get blobs engine api into getPayloadV3 (#2650)
Browse files Browse the repository at this point in the history
* client: Merge get blobs engine api into getPayloadV3

* some renamings

* fix v3 spec

* add blobs bundle checks

* fix spec
  • Loading branch information
g11tech authored Apr 19, 2023
1 parent aa56f43 commit 13ceff9
Show file tree
Hide file tree
Showing 6 changed files with 73 additions and 90 deletions.
61 changes: 22 additions & 39 deletions packages/client/lib/miner/pendingBlock.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { BlockHeader } from '@ethereumjs/block'
import { BlobEIP4844Transaction } from '@ethereumjs/tx'
import {
TypeOutput,
Expand Down Expand Up @@ -33,10 +32,9 @@ interface PendingBlockOpts {
skipHardForkValidation?: boolean
}

interface BlobBundle {
blockHash: string
export interface BlobsBundle {
blobs: Uint8Array[]
kzgCommitments: Uint8Array[]
commitments: Uint8Array[]
proofs: Uint8Array[]
}
/**
Expand All @@ -55,7 +53,7 @@ export class PendingBlock {
txPool: TxPool

pendingPayloads: Map<string, BlockBuilder> = new Map()
blobBundles: Map<string, BlobBundle> = new Map()
blobsBundles: Map<string, BlobsBundle> = new Map()

private skipHardForkValidation?: boolean

Expand Down Expand Up @@ -114,7 +112,7 @@ export class PendingBlock {
return payloadIdBytes
}

// Prune the builders and blobbundles
// Prune the builders and blobsbundles
this.pruneSetToMax(MAX_PAYLOAD_CACHE)

if (typeof vm.blockchain.getTotalDifficulty !== 'function') {
Expand Down Expand Up @@ -191,20 +189,7 @@ export class PendingBlock {

// Construct initial blobs bundle when payload is constructed
if (vm._common.isActivatedEIP(4844)) {
const header = BlockHeader.fromHeaderData(
{
...headerData,
number,
gasLimit,
baseFeePerGas,
excessDataGas,
},
{
hardforkByTTD: td,
common: vm._common,
}
)
this.constructBlobsBundle(payloadId, blobTxs, header.hash())
this.constructBlobsBundle(payloadId, blobTxs)
}
return payloadIdBytes
}
Expand All @@ -221,15 +206,15 @@ export class PendingBlock {
void builder.revert()
// Remove from pendingPayloads
this.pendingPayloads.delete(payloadId)
this.blobBundles.delete(payloadId)
this.blobsBundles.delete(payloadId)
}

/**
* Returns the completed block
*/
async build(
payloadIdBytes: Uint8Array | string
): Promise<void | [block: Block, receipts: TxReceipt[], value: bigint]> {
): Promise<void | [block: Block, receipts: TxReceipt[], value: bigint, blobs?: BlobsBundle]> {
const payloadId =
typeof payloadIdBytes !== 'string' ? bytesToPrefixedHexString(payloadIdBytes) : payloadIdBytes
const builder = this.pendingPayloads.get(payloadId)
Expand Down Expand Up @@ -304,11 +289,11 @@ export class PendingBlock {
)

// Construct blobs bundle
if (block._common.isActivatedEIP(4844)) {
this.constructBlobsBundle(payloadId, blobTxs, block.header.hash())
}
const blobs = block._common.isActivatedEIP(4844)
? this.constructBlobsBundle(payloadId, blobTxs)
: undefined

return [block, builder.transactionReceipts, builder.minerValue]
return [block, builder.transactionReceipts, builder.minerValue, blobs]
}

/**
Expand All @@ -317,34 +302,32 @@ export class PendingBlock {
* @param txs an array of {@BlobEIP4844Transaction } transactions
* @param blockHash the blockhash of the pending block (computed from the header data provided)
*/
private constructBlobsBundle = (
payloadId: string,
txs: BlobEIP4844Transaction[],
blockHash: Uint8Array
) => {
private constructBlobsBundle = (payloadId: string, txs: BlobEIP4844Transaction[]) => {
let blobs: Uint8Array[] = []
let kzgCommitments: Uint8Array[] = []
let commitments: Uint8Array[] = []
let proofs: Uint8Array[] = []
const bundle = this.blobBundles.get(payloadId)
const bundle = this.blobsBundles.get(payloadId)
if (bundle !== undefined) {
blobs = bundle.blobs
kzgCommitments = bundle.kzgCommitments
commitments = bundle.commitments
proofs = bundle.proofs
}

for (let tx of txs) {
tx = tx as BlobEIP4844Transaction
if (tx.blobs !== undefined && tx.blobs.length > 0) {
blobs = blobs.concat(tx.blobs)
kzgCommitments = kzgCommitments.concat(tx.kzgCommitments!)
commitments = commitments.concat(tx.kzgCommitments!)
proofs = proofs.concat(tx.kzgProofs!)
}
}
this.blobBundles.set(payloadId, {
blockHash: bytesToPrefixedHexString(blockHash),

const blobsBundle = {
blobs,
kzgCommitments,
commitments,
proofs,
})
}
this.blobsBundles.set(payloadId, blobsBundle)
return blobsBundle
}
}
45 changes: 13 additions & 32 deletions packages/client/lib/rpc/modules/engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import type { Chain } from '../../blockchain'
import type { EthereumClient } from '../../client'
import type { Config } from '../../config'
import type { VMExecution } from '../../execution'
import type { BlobsBundle } from '../../miner'
import type { FullEthereumService } from '../../service'
import type { HeaderData } from '@ethereumjs/block'
import type { VM } from '@ethereumjs/vm'
Expand Down Expand Up @@ -109,8 +110,7 @@ type TransitionConfigurationV1 = {
}

type BlobsBundleV1 = {
blockHash: string
kzgs: Bytes48[]
commitments: Bytes48[]
blobs: Blob[]
proofs: Bytes48[]
}
Expand Down Expand Up @@ -170,12 +170,19 @@ const payloadAttributesFieldValidatorsV2 = {
/**
* Formats a block to {@link ExecutionPayloadV1}.
*/
export const blockToExecutionPayload = (block: Block, value: bigint) => {
export const blockToExecutionPayload = (block: Block, value: bigint, bundle?: BlobsBundle) => {
const blockJson = block.toJSON()
const header = blockJson.header!
const transactions =
block.transactions.map((tx) => bytesToPrefixedHexString(tx.serialize())) ?? []
const withdrawalsArr = blockJson.withdrawals ? { withdrawals: blockJson.withdrawals } : {}
const blobsBundle: BlobsBundleV1 | undefined = bundle
? {
commitments: bundle.commitments.map(bytesToPrefixedHexString),
blobs: bundle.blobs.map(bytesToPrefixedHexString),
proofs: bundle.proofs.map(bytesToPrefixedHexString),
}
: undefined

const executionPayload: ExecutionPayload = {
blockNumber: header.number!,
Expand All @@ -195,7 +202,7 @@ export const blockToExecutionPayload = (block: Block, value: bigint) => {
transactions,
...withdrawalsArr,
}
return { executionPayload, blockValue: bigIntToHex(value) }
return { executionPayload, blockValue: bigIntToHex(value), blobsBundle }
}

/**
Expand Down Expand Up @@ -475,11 +482,6 @@ export class Engine {
() => this.connectionManager.updateStatus()
)

this.getBlobsBundleV1 = cmMiddleware(
middleware(this.getBlobsBundleV1.bind(this), 1, [[validators.bytes8]]),
() => this.connectionManager.updateStatus()
)

this.exchangeCapabilities = cmMiddleware(
middleware(this.exchangeCapabilities.bind(this), 0, []),
() => this.connectionManager.updateStatus()
Expand Down Expand Up @@ -1003,9 +1005,9 @@ export class Engine {
}
// The third arg returned is the minerValue which we will use to
// value the block
const [block, receipts, value] = built
const [block, receipts, value, blobs] = built
await this.execution.runWithoutSetHead({ block }, receipts)
return blockToExecutionPayload(block, value)
return blockToExecutionPayload(block, value, blobs)
} catch (error: any) {
if (error === EngineError.UnknownPayload) throw error
throw {
Expand Down Expand Up @@ -1058,27 +1060,6 @@ export class Engine {
return { terminalTotalDifficulty, terminalBlockHash, terminalBlockNumber }
}

/**
*
* @param params a payloadId for a pending block
* @returns a BlobsBundle consisting of the blockhash, the blobs, and the corresponding kzg commitments
*/
private async getBlobsBundleV1(params: [Bytes8]): Promise<BlobsBundleV1> {
const payloadId = params[0]

const bundle = this.pendingBlock.blobBundles.get(payloadId)
if (bundle === undefined) {
throw EngineError.UnknownPayload
}

return {
blockHash: bundle.blockHash,
kzgs: bundle.kzgCommitments.map(bytesToPrefixedHexString),
blobs: bundle.blobs.map(bytesToPrefixedHexString),
proofs: bundle.proofs.map(bytesToPrefixedHexString),
}
}

/**
* Returns a list of engine API endpoints supported by the client
*/
Expand Down
8 changes: 4 additions & 4 deletions packages/client/test/miner/pendingBlock.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -302,11 +302,11 @@ tape('[PendingBlock]', async (t) => {
const payloadId = await pendingBlock.start(vm, parentBlock)
await pendingBlock.build(payloadId)

const blobBundle = pendingBlock.blobBundles.get(bytesToPrefixedHexString(payloadId))!
st.ok(blobBundle !== undefined)
const pendingBlob = blobBundle.blobs[0]
const blobsBundles = pendingBlock.blobsBundles.get(bytesToPrefixedHexString(payloadId))!
st.ok(blobsBundles !== undefined)
const pendingBlob = blobsBundles.blobs[0]
st.ok(pendingBlob !== undefined && equalsBytes(pendingBlob, blobs[0]))
const blobProof = blobBundle.proofs[0]
const blobProof = blobsBundles.proofs[0]
st.ok(blobProof !== undefined && equalsBytes(blobProof, proofs[0]))
st.end()
})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
import { Hardfork } from '@ethereumjs/common'
import { DefaultStateManager } from '@ethereumjs/statemanager'
import { TransactionFactory } from '@ethereumjs/tx'
import { Account, Address, hexStringToBytes, initKZG } from '@ethereumjs/util'
import {
Account,
Address,
blobsToCommitments,
blobsToProofs,
bytesToPrefixedHexString,
commitmentsToVersionedHashes,
getBlobs,
hexStringToBytes,
initKZG,
} from '@ethereumjs/util'
import * as kzg from 'c-kzg'
import * as tape from 'tape'

Expand All @@ -26,7 +36,7 @@ const validPayloadAttributes = {
const validPayload = [validForkChoiceState, { ...validPayloadAttributes, withdrawals: [] }]

initKZG(kzg, __dirname + '/../../../lib/trustedSetups/devnet4.txt')
const method = 'engine_getBlobsBundleV1'
const method = 'engine_getPayloadV3'

tape(`${method}: call with invalid payloadId`, async (t) => {
const { server } = baseSetup({ engine: true, includeVM: true })
Expand Down Expand Up @@ -74,10 +84,19 @@ tape(`${method}: call with known payload`, async (t) => {
payloadId = res.body.result.payloadId
}
await baseRequest(t, server, req, 200, expectRes, false)

const txBlobs = getBlobs('hello world')
const txCommitments = blobsToCommitments(txBlobs)
const txVersionedHashes = commitmentsToVersionedHashes(txCommitments)
const txProofs = blobsToProofs(txBlobs, txCommitments)

const tx = TransactionFactory.fromTxData(
{
type: 0x03,
versionedHashes: [],
versionedHashes: txVersionedHashes,
blobs: txBlobs,
kzgCommitments: txCommitments,
kzgProofs: txProofs,
maxFeePerDataGas: 1n,
maxFeePerGas: 10000000000n,
maxPriorityFeePerGas: 100000000n,
Expand All @@ -90,24 +109,24 @@ tape(`${method}: call with known payload`, async (t) => {
await service.txPool.add(tx, true)
req = params('engine_getPayloadV3', [payloadId])
expectRes = (res: any) => {
const { executionPayload, blobsBundle } = res.body.result
t.equal(
res.body.result.executionPayload.blockHash,
'0x4f3068842f4977e1358719f03868cae636e654eca60cf97a1b5619aa006b7185',
executionPayload.blockHash,
'0x3c599ece59439d2dc938e7a2b5e1c675cf8173b6be654f0a689b96936eba96e2',
'built expected block'
)
}

await baseRequest(t, server, req, 200, expectRes, false)
req = params(method, [payloadId])
expectRes = (res: any) => {
t.equal(
res.body.result.blockHash,
'0x4f3068842f4977e1358719f03868cae636e654eca60cf97a1b5619aa006b7185',
'got expected blockHash'
const { commitments, proofs, blobs } = blobsBundle
t.ok(
commitments.length === proofs.length && commitments.length === blobs.length,
'equal commitments, proofs and blobs'
)
t.equal(blobs.length, 1, '1 blob should be returned')
t.equal(proofs[0], bytesToPrefixedHexString(txProofs[0]), 'proof should match')
t.equal(commitments[0], bytesToPrefixedHexString(txCommitments[0]), 'commitment should match')
t.equal(blobs[0], bytesToPrefixedHexString(txBlobs[0]), 'blob should match')
}

await baseRequest(t, server, req, 200, expectRes, false)
// Restore setStateRoot
DefaultStateManager.prototype.setStateRoot = originalSetStateRoot
DefaultStateManager.prototype.copy = originalStateManagerCopy
})

0 comments on commit 13ceff9

Please sign in to comment.