Skip to content

Commit

Permalink
block: fix the block body parsing as well as save/load from blockchain (
Browse files Browse the repository at this point in the history
#3392)

* block: fix the block body parsing as well as save/load from blockchain

* debug and fix block references on header save/get

* fix build

* debug and fix the missing requests in body raw/serialization

* lint
  • Loading branch information
g11tech authored May 3, 2024
1 parent 36cd606 commit 63a530f
Show file tree
Hide file tree
Showing 7 changed files with 51 additions and 65 deletions.
40 changes: 38 additions & 2 deletions packages/block/src/block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,13 @@ import type {
BlockData,
BlockOptions,
ExecutionPayload,
ExecutionWitnessBytes,
HeaderData,
JsonBlock,
JsonRpcBlock,
RequestsBytes,
VerkleExecutionWitness,
WithdrawalsBytes,
} from './types.js'
import type { Common } from '@ethereumjs/common'
import type {
Expand Down Expand Up @@ -217,10 +220,22 @@ export class Block {

// First try to load header so that we can use its common (in case of setHardfork being activated)
// to correctly make checks on the hardforks
const [headerData, txsData, uhsData, withdrawalBytes, requestBytes, executionWitnessBytes] =
values
const [headerData, txsData, uhsData, ...valuesTail] = values
const header = BlockHeader.fromValuesArray(headerData, opts)

// conditional assignment of rest of values and splicing them out from the valuesTail
const withdrawalBytes = header.common.isActivatedEIP(4895)
? (valuesTail.splice(0, 1)[0] as WithdrawalsBytes)
: undefined
const requestBytes = header.common.isActivatedEIP(7685)
? (valuesTail.splice(0, 1)[0] as RequestsBytes)
: undefined
// if witness bytes are not present that we should assume that witness has not been provided
// in that scenario pass null as undefined is used for default witness assignment
const executionWitnessBytes = header.common.isActivatedEIP(6800)
? (valuesTail.splice(0, 1)[0] as ExecutionWitnessBytes)
: null

if (
header.common.isActivatedEIP(4895) &&
(withdrawalBytes === undefined || !Array.isArray(withdrawalBytes))
Expand All @@ -230,6 +245,21 @@ export class Block {
)
}

if (
header.common.isActivatedEIP(7685) &&
(requestBytes === undefined || !Array.isArray(requestBytes))
) {
throw new Error(
'Invalid serialized block input: EIP-7685 is active, and no requestBytes were provided as array'
)
}

if (header.common.isActivatedEIP(6800) && executionWitnessBytes === undefined) {
throw new Error(
'Invalid serialized block input: EIP-6800 is active, and execution witness is undefined'
)
}

// parse transactions
const transactions = []
for (const txData of txsData ?? []) {
Expand Down Expand Up @@ -567,6 +597,12 @@ export class Block {
if (withdrawalsRaw) {
bytesArray.push(withdrawalsRaw)
}

const requestsRaw = this.requests?.map((req) => req.serialize())
if (requestsRaw) {
bytesArray.push(requestsRaw)
}

if (this.executionWitness !== undefined && this.executionWitness !== null) {
const executionWitnessBytes = RLP.encode(JSON.stringify(this.executionWitness))
bytesArray.push(executionWitnessBytes as any)
Expand Down
14 changes: 7 additions & 7 deletions packages/blockchain/src/blockchain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -375,8 +375,8 @@ export class Blockchain implements BlockchainInterface {
async getCanonicalHeadHeader(): Promise<BlockHeader> {
return this.runWithLock<BlockHeader>(async () => {
if (!this._headHeaderHash) throw new Error('No head header set')
const block = await this.getBlock(this._headHeaderHash)
return block.header
const header = await this._getHeader(this._headHeaderHash)
return header
})
}

Expand Down Expand Up @@ -524,7 +524,7 @@ export class Blockchain implements BlockchainInterface {
)
}

if (this._validateBlocks && !isGenesis) {
if (this._validateBlocks && !isGenesis && item instanceof Block) {
// this calls into `getBlock`, which is why we cannot lock yet
await this.validateBlock(block)
}
Expand All @@ -550,8 +550,8 @@ export class Blockchain implements BlockchainInterface {
// save total difficulty to the database
dbOps = dbOps.concat(DBSetTD(td, blockNumber, blockHash))

// save header/block to the database
dbOps = dbOps.concat(DBSetBlockOrHeader(block))
// save header/block to the database, but save the input not our wrapper block
dbOps = dbOps.concat(DBSetBlockOrHeader(item))

let commonAncestor: undefined | BlockHeader
let ancestorHeaders: undefined | BlockHeader[]
Expand Down Expand Up @@ -628,7 +628,7 @@ export class Blockchain implements BlockchainInterface {
if (header.isGenesis()) {
return
}
const parentHeader = (await this.getBlock(header.parentHash)).header
const parentHeader = await this._getHeader(header.parentHash)

const { number } = header
if (number !== parentHeader.number + BIGINT_1) {
Expand Down Expand Up @@ -1138,7 +1138,7 @@ export class Blockchain implements BlockchainInterface {
if (!this._headHeaderHash) throw new Error('No head header set')
const ancestorHeaders = new Set<BlockHeader>()

let { header } = await this.getBlock(this._headHeaderHash)
let header = await this._getHeader(this._headHeaderHash)
if (header.number > newHeader.number) {
header = await this.getCanonicalHeader(newHeader.number)
ancestorHeaders.add(header)
Expand Down
2 changes: 1 addition & 1 deletion packages/blockchain/src/consensus/ethash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export class EthashConsensus implements Consensus {
if (!this.blockchain) {
throw new Error('blockchain not provided')
}
const parentHeader = (await this.blockchain.getBlock(header.parentHash)).header
const parentHeader = await this.blockchain['_getHeader'](header.parentHash)
if (header.ethashCanonicalDifficulty(parentHeader) !== header.difficulty) {
throw new Error(`invalid difficulty ${header.errorStr()}`)
}
Expand Down
9 changes: 1 addition & 8 deletions packages/blockchain/src/db/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,7 @@ function DBSetBlockOrHeader(blockBody: Block | BlockHeader): DBOp[] {

const isGenesis = header.number === BIGINT_0

if (
isGenesis ||
(blockBody instanceof Block &&
(blockBody.transactions.length ||
(blockBody.withdrawals?.length ?? 0) ||
blockBody.uncleHeaders.length ||
(blockBody.executionWitness !== null && blockBody.executionWitness !== undefined)))
) {
if (isGenesis || blockBody instanceof Block) {
const bodyValue = RLP.encode(blockBody.raw().slice(1))
dbOps.push(
DBOp.set(DBTarget.Body, bodyValue, {
Expand Down
39 changes: 1 addition & 38 deletions packages/blockchain/src/db/manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,8 @@ import { RLP } from '@ethereumjs/rlp'
import {
BIGINT_0,
BIGINT_1,
KECCAK256_RLP,
KECCAK256_RLP_ARRAY,
bytesToBigInt,
bytesToHex,
equalsBytes,
unprefixedHexToBytes,
} from '@ethereumjs/util'

Expand Down Expand Up @@ -107,40 +104,6 @@ export class DBManager {
if (hash === undefined || number === undefined) return undefined
const header = await this.getHeader(hash, number)
const body = await this.getBody(hash, number)
if (body[0].length === 0 && body[1].length === 0) {
// Do extra validations on the header since we are assuming empty transactions and uncles
if (!equalsBytes(header.transactionsTrie, KECCAK256_RLP)) {
throw new Error('transactionsTrie root should be equal to hash of null')
}
if (!equalsBytes(header.uncleHash, KECCAK256_RLP_ARRAY)) {
throw new Error('uncle hash should be equal to hash of empty array')
}
// If this block had empty withdrawals push an empty array in body
if (header.withdrawalsRoot !== undefined) {
// Do extra validations for withdrawal before assuming empty withdrawals
if (
!equalsBytes(header.withdrawalsRoot, KECCAK256_RLP) &&
(body.length < 3 || body[2]?.length === 0)
) {
throw new Error('withdrawals root shoot be equal to hash of null when no withdrawals')
}
if (body.length < 3) body.push([])
}
// If requests root exists, validate that requests array exists or insert it
if (header.requestsRoot !== undefined) {
if (
!equalsBytes(header.requestsRoot, KECCAK256_RLP) &&
(body.length < 4 || body[3]?.length === 0)
) {
throw new Error('requestsRoot should be equal to hash of null when no requests')
}
if (body.length < 4) {
for (let x = 0; x < 4 - body.length; x++) {
body.push([])
}
}
}
}

const blockData = [header.raw(), ...body] as BlockBytes
const opts: BlockOptions = { common: this.common }
Expand All @@ -158,7 +121,7 @@ export class DBManager {
async getBody(blockHash: Uint8Array, blockNumber: bigint): Promise<BlockBodyBytes> {
const body = await this.get(DBTarget.Body, { blockHash, blockNumber })
if (body === undefined) {
return [[], []]
throw Error('Body not found')
}
return RLP.decode(body) as BlockBodyBytes
}
Expand Down
1 change: 1 addition & 0 deletions packages/blockchain/test/blockValidation.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,7 @@ describe('EIP 7685: requests field validation tests', () => {
timestamp: blockchain.genesisBlock.header.timestamp + 1n,
gasLimit: 5000,
},
requests: [],
},
{ common }
)
Expand Down
11 changes: 2 additions & 9 deletions packages/blockchain/test/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -512,17 +512,14 @@ describe('blockchain test', () => {
await blockchain.putBlock(blocks[3])
})

it('should test nil bodies / throw', async () => {
it('should not reconstruct nil bodies / throw', async () => {
const blocks = generateBlocks(3)
const blockchain = await Blockchain.create({
validateBlocks: false,
validateConsensus: false,
genesisBlock: blocks[0],
})
await blockchain.putHeader(blocks[1].header)
// Should be able to get the block
await blockchain.getBlock(BigInt(1))

const block2HeaderValuesArray = blocks[2].header.raw()

block2HeaderValuesArray[1] = new Uint8Array(32)
Expand All @@ -534,11 +531,7 @@ describe('blockchain test', () => {
await blockchain.getBlock(BigInt(2))
assert.fail('block should not be constucted')
} catch (e: any) {
assert.equal(
e.message,
'uncle hash should be equal to hash of empty array',
'block not constructed from empty bodies'
)
assert.equal(e.message, 'Body not found', 'block not constructed from empty bodies')
}
})

Expand Down

0 comments on commit 63a530f

Please sign in to comment.