Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Block consensus methods refactoring #3571

Merged
merged 47 commits into from
Aug 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
8fd3556
Move clique.ts into subdirectory
scorbajio Aug 7, 2024
317ad18
Fix import
scorbajio Aug 7, 2024
84aece0
Make ethashCanonicalDifficulty, _requireClique, and cliqueSigHash sta…
scorbajio Aug 7, 2024
6e67e93
Make cliqueIsEpochTransition a standalone function
scorbajio Aug 7, 2024
22460a4
Remove unused import
scorbajio Aug 7, 2024
b73dfb5
Make cliqueExtraVanity a standalone function and cleanup
scorbajio Aug 7, 2024
5f4dfe6
Make cliqueExtraSeal a standalone function
scorbajio Aug 7, 2024
32dfad5
Make cliqueEpochTransitionSigners a standalone function
scorbajio Aug 7, 2024
ba0284a
Make cliqueVerifySignature and cliqueSigner standalone functions
scorbajio Aug 7, 2024
37fea1f
Fix linting issues
scorbajio Aug 7, 2024
1187d33
Merge branch 'master' into block-consensus-methods-refactoring
scorbajio Aug 7, 2024
eddfb1d
Fix test
scorbajio Aug 7, 2024
7dc74a0
Remove unused import
scorbajio Aug 7, 2024
ef51a55
Update package and package-lock files
scorbajio Aug 7, 2024
50ab7e4
Fix lint issues
scorbajio Aug 7, 2024
16241f6
Merge branch 'block-consensus-methods-refactoring' of github.com:ethe…
scorbajio Aug 7, 2024
c8f4fc1
Fix linting issue
scorbajio Aug 7, 2024
3c30ee3
Merge branch 'master' of github.com:ethereumjs/ethereumjs-monorepo in…
scorbajio Aug 8, 2024
41ffe66
Remove block dependency and pass in cliqueSigner as option to evm
scorbajio Aug 8, 2024
c7dea57
Add more typing for cliqueSigner option
scorbajio Aug 8, 2024
05a84b5
Move cliqueSealBlock functionality into standalone constructor function
scorbajio Aug 8, 2024
54114e7
Fix imports
scorbajio Aug 8, 2024
dd58b43
fix header typing
acolytec3 Aug 9, 2024
bf8ca68
Merge remote-tracking branch 'origin/master' into block-consensus-met…
acolytec3 Aug 12, 2024
c9204ab
remove pointless underscore
acolytec3 Aug 12, 2024
eb89c3c
revise codedoc [no ci]
acolytec3 Aug 12, 2024
a0bb705
Create and use block and header constructor for PoA/cliques
scorbajio Aug 12, 2024
9a02915
Merge branch 'block-consensus-methods-refactoring' of github.com:ethe…
scorbajio Aug 12, 2024
917d48f
Fix linting issue
scorbajio Aug 12, 2024
b95ae39
Fix tests
scorbajio Aug 12, 2024
aab9b6e
Fix test
scorbajio Aug 12, 2024
eec50fe
Make constructor signatures more similar
scorbajio Aug 12, 2024
598b922
Fix read-only error
scorbajio Aug 12, 2024
4818096
fix extraData function reference
acolytec3 Aug 13, 2024
4b7090a
fix the freeze
acolytec3 Aug 13, 2024
8d53ea0
Fix test
scorbajio Aug 13, 2024
6048d8f
Fix test
scorbajio Aug 13, 2024
c981daf
Handle creating a sealed block for PoA blocks in vm block builder
scorbajio Aug 13, 2024
f6a02d1
Fix tests
scorbajio Aug 13, 2024
aec9005
Update docstring
scorbajio Aug 13, 2024
fe00092
reorganize test with proper it-ing [no ci]
acolytec3 Aug 13, 2024
07342f8
handle clique consensus validation at correct point
acolytec3 Aug 13, 2024
be12a25
clean up code docs
acolytec3 Aug 13, 2024
ad688cd
Merge remote-tracking branch 'origin/master' into block-consensus-met…
acolytec3 Aug 13, 2024
831e1d6
revert invalid check
acolytec3 Aug 13, 2024
8a02fbe
Fix lint issue
scorbajio Aug 13, 2024
c029904
Merge branch 'master' into block-consensus-methods-refactoring
acolytec3 Aug 13, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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'
scorbajio marked this conversation as resolved.
Show resolved Hide resolved
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)
}
acolytec3 marked this conversation as resolved.
Show resolved Hide resolved
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
}
acolytec3 marked this conversation as resolved.
Show resolved Hide resolved
Loading
Loading