Skip to content

Commit

Permalink
Block consensus methods refactoring (#3571)
Browse files Browse the repository at this point in the history
* Move clique.ts into subdirectory

* Fix import

* Make ethashCanonicalDifficulty, _requireClique, and cliqueSigHash standalone functions

* Make cliqueIsEpochTransition a standalone function

* Remove unused import

* Make cliqueExtraVanity a standalone function and cleanup

* Make cliqueExtraSeal a standalone function

* Make cliqueEpochTransitionSigners a standalone function

* Make cliqueVerifySignature and cliqueSigner standalone functions

* Fix linting issues

* Fix test

* Remove unused import

* Update package and package-lock files

* Fix lint issues

* Fix linting issue

* Remove block dependency and pass in cliqueSigner as option to evm

* Add more typing for cliqueSigner option

* Move cliqueSealBlock functionality into standalone constructor function

* Fix imports

* fix header typing

* remove pointless underscore

* revise codedoc [no ci]

* Create and use block and header constructor for PoA/cliques

* Fix linting issue

* Fix tests

* Fix test

* Make constructor signatures more similar

* Fix read-only error

* fix extraData function reference

* fix the freeze

* Fix test

* Fix test

* Handle creating a sealed block for PoA blocks in vm block builder

* Fix tests

* Update docstring

* reorganize test with proper it-ing [no ci]

* handle clique consensus validation at correct point

* clean up code docs

* revert invalid check

* Fix lint issue

---------

Co-authored-by: acolytec3 <[email protected]>
  • Loading branch information
scorbajio and acolytec3 authored Aug 13, 2024
1 parent 0686310 commit 210b0fa
Show file tree
Hide file tree
Showing 24 changed files with 382 additions and 260 deletions.
9 changes: 0 additions & 9 deletions packages/block/src/block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -507,15 +507,6 @@ export class Block {
}
}

/**
* Returns the canonical difficulty for this block.
*
* @param parentBlock - the parent of this `Block`
*/
ethashCanonicalDifficulty(parentBlock: Block): bigint {
return this.header.ethashCanonicalDifficulty(parentBlock.header)
}

/**
* Validates if the block gasLimit remains in the boundaries set by the protocol.
* Throws if invalid
Expand Down
4 changes: 0 additions & 4 deletions packages/block/src/clique.ts

This file was deleted.

163 changes: 163 additions & 0 deletions packages/block/src/consensus/clique.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import { ConsensusAlgorithm } from '@ethereumjs/common'
import { RLP } from '@ethereumjs/rlp'
import {
Address,
BIGINT_0,
BIGINT_27,
bigIntToBytes,
bytesToBigInt,
concatBytes,
createAddressFromPublicKey,
createZeroAddress,
ecrecover,
ecsign,
equalsBytes,
} from '@ethereumjs/util'

import type { BlockHeader } from '../index.js'
import type { CliqueConfig } from '@ethereumjs/common'

// Fixed number of extra-data prefix bytes reserved for signer vanity
export const CLIQUE_EXTRA_VANITY = 32
// Fixed number of extra-data suffix bytes reserved for signer seal
export const CLIQUE_EXTRA_SEAL = 65

// This function is not exported in the index file to keep it internal
export function requireClique(header: BlockHeader, name: string) {
if (header.common.consensusAlgorithm() !== ConsensusAlgorithm.Clique) {
const msg = header['_errorMsg'](
`BlockHeader.${name}() call only supported for clique PoA networks`,
)
throw new Error(msg)
}
}

/**
* PoA clique signature hash without the seal.
*/
export function cliqueSigHash(header: BlockHeader) {
requireClique(header, 'cliqueSigHash')
const raw = header.raw()
raw[12] = header.extraData.subarray(0, header.extraData.length - CLIQUE_EXTRA_SEAL)
return header['keccakFunction'](RLP.encode(raw))
}

/**
* Checks if the block header is an epoch transition
* header (only clique PoA, throws otherwise)
*/
export function cliqueIsEpochTransition(header: BlockHeader): boolean {
requireClique(header, 'cliqueIsEpochTransition')
const epoch = BigInt((header.common.consensusConfig() as CliqueConfig).epoch)
// Epoch transition block if the block number has no
// remainder on the division by the epoch length
return header.number % epoch === BIGINT_0
}

/**
* Returns extra vanity data
* (only clique PoA, throws otherwise)
*/
export function cliqueExtraVanity(header: BlockHeader): Uint8Array {
requireClique(header, 'cliqueExtraVanity')
return header.extraData.subarray(0, CLIQUE_EXTRA_VANITY)
}

/**
* Returns extra seal data
* (only clique PoA, throws otherwise)
*/
export function cliqueExtraSeal(header: BlockHeader): Uint8Array {
requireClique(header, 'cliqueExtraSeal')
return header.extraData.subarray(-CLIQUE_EXTRA_SEAL)
}

/**
* Returns a list of signers
* (only clique PoA, throws otherwise)
*
* This function throws if not called on an epoch
* transition block and should therefore be used
* in conjunction with {@link BlockHeader.cliqueIsEpochTransition}
*/
export function cliqueEpochTransitionSigners(header: BlockHeader): Address[] {
requireClique(header, 'cliqueEpochTransitionSigners')
if (!cliqueIsEpochTransition(header)) {
const msg = header['_errorMsg']('Signers are only included in epoch transition blocks (clique)')
throw new Error(msg)
}

const start = CLIQUE_EXTRA_VANITY
const end = header.extraData.length - CLIQUE_EXTRA_SEAL
const signerBytes = header.extraData.subarray(start, end)

const signerList: Uint8Array[] = []
const signerLength = 20
for (let start = 0; start <= signerBytes.length - signerLength; start += signerLength) {
signerList.push(signerBytes.subarray(start, start + signerLength))
}
return signerList.map((buf) => new Address(buf))
}

/**
* Returns the signer address
*/
export function cliqueSigner(header: BlockHeader): Address {
requireClique(header, 'cliqueSigner')
const extraSeal = cliqueExtraSeal(header)
// Reasonable default for default blocks
if (extraSeal.length === 0 || equalsBytes(extraSeal, new Uint8Array(65))) {
return createZeroAddress()
}
const r = extraSeal.subarray(0, 32)
const s = extraSeal.subarray(32, 64)
const v = bytesToBigInt(extraSeal.subarray(64, 65)) + BIGINT_27
const pubKey = ecrecover(cliqueSigHash(header), v, r, s)
return createAddressFromPublicKey(pubKey)
}

/**
* Verifies the signature of the block (last 65 bytes of extraData field)
* (only clique PoA, throws otherwise)
*
* Method throws if signature is invalid
*/
export function cliqueVerifySignature(header: BlockHeader, signerList: Address[]): boolean {
requireClique(header, 'cliqueVerifySignature')
const signerAddress = cliqueSigner(header)
const signerFound = signerList.find((signer) => {
return signer.equals(signerAddress)
})
return !!signerFound
}

/**
* Generates the extraData from a sealed block header
* @param header block header from which to retrieve extraData
* @param cliqueSigner clique signer key used for creating sealed block
* @returns clique seal (i.e. extradata) for the block
*/
export function generateCliqueBlockExtraData(
header: BlockHeader,
cliqueSigner: Uint8Array,
): Uint8Array {
// Ensure extraData is at least length CLIQUE_EXTRA_VANITY + CLIQUE_EXTRA_SEAL
const minExtraDataLength = CLIQUE_EXTRA_VANITY + CLIQUE_EXTRA_SEAL
if (header.extraData.length < minExtraDataLength) {
const remainingLength = minExtraDataLength - header.extraData.length
;(header.extraData as any) = concatBytes(header.extraData, new Uint8Array(remainingLength))
}

requireClique(header, 'generateCliqueBlockExtraData')

const ecSignFunction = header.common.customCrypto?.ecsign ?? ecsign
const signature = ecSignFunction(cliqueSigHash(header), cliqueSigner)
const signatureB = concatBytes(signature.r, signature.s, bigIntToBytes(signature.v - BIGINT_27))

const extraDataWithoutSeal = header.extraData.subarray(
0,
header.extraData.length - CLIQUE_EXTRA_SEAL,
)
const extraData = concatBytes(extraDataWithoutSeal, signatureB)
return extraData
}
10 changes: 10 additions & 0 deletions packages/block/src/consensus/ethash.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type { Block } from '../index.js'

/**
* Returns the canonical difficulty for this block.
*
* @param parentBlock - the parent of this `Block`
*/
export function ethashCanonicalDifficulty(block: Block, parentBlock: Block): bigint {
return block.header.ethashCanonicalDifficulty(parentBlock.header)
}
12 changes: 12 additions & 0 deletions packages/block/src/consensus/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export {
CLIQUE_EXTRA_SEAL,
CLIQUE_EXTRA_VANITY,
cliqueEpochTransitionSigners,
cliqueExtraSeal,
cliqueExtraVanity,
cliqueIsEpochTransition,
cliqueSigHash,
cliqueSigner,
cliqueVerifySignature,
} from './clique.js'
export * from './ethash.js'
44 changes: 44 additions & 0 deletions packages/block/src/constructors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
isHexString,
} from '@ethereumjs/util'

import { generateCliqueBlockExtraData } from './consensus/clique.js'
import { createBlockFromRpc } from './from-rpc.js'
import {
genRequestsTrieRoot,
Expand Down Expand Up @@ -515,3 +516,46 @@ export async function createBlockFromBeaconPayloadJson(
const executionPayload = executionPayloadFromBeaconPayload(payload)
return createBlockFromExecutionPayload(executionPayload, opts)
}

export function createSealedCliqueBlock(
blockData: BlockData = {},
cliqueSigner: Uint8Array,
opts: BlockOptions = {},
): Block {
const sealedCliqueBlock = createBlock(blockData, {
...opts,
...{ freeze: false, skipConsensusFormatValidation: true },
})
;(sealedCliqueBlock.header.extraData as any) = generateCliqueBlockExtraData(
sealedCliqueBlock.header,
cliqueSigner,
)
if (opts?.freeze === true) {
// We have to freeze here since we can't freeze the block when constructing it since we are overwriting `extraData`
Object.freeze(sealedCliqueBlock)
}
if (opts?.skipConsensusFormatValidation === false) {
// We need to validate the consensus format here since we skipped it when constructing the block
sealedCliqueBlock.header['_consensusFormatValidation']()
}
return sealedCliqueBlock
}

export function createSealedCliqueBlockHeader(
headerData: HeaderData = {},
cliqueSigner: Uint8Array,
opts: BlockOptions = {},
): BlockHeader {
const sealedCliqueBlockHeader = new BlockHeader(headerData, {
...opts,
...{ skipConsensusFormatValidation: true },
})
;(sealedCliqueBlockHeader.extraData as any) = generateCliqueBlockExtraData(
sealedCliqueBlockHeader,
cliqueSigner,
)
if (opts.skipConsensusFormatValidation === false)
// We need to validate the consensus format here since we skipped it when constructing the block header
sealedCliqueBlockHeader['_consensusFormatValidation']()
return sealedCliqueBlockHeader
}
Loading

0 comments on commit 210b0fa

Please sign in to comment.