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

Blockchain/Ethash/Trie: remove level dependency from blockchain #2669

Merged
merged 33 commits into from
May 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
7c444cb
blockchain: remove level dependency from blockchain
gabrocheleau Apr 26, 2023
ec3a913
util: refactor DB types from trie to util
gabrocheleau Apr 27, 2023
510b0c6
util: make db interface generic
gabrocheleau Apr 27, 2023
89f4741
blockchain: remove abstract-level usage in favor of DB interface and …
gabrocheleau Apr 27, 2023
6c9d0b1
WIP removal of level DB stuff (#2673)
acolytec3 May 4, 2023
98dd481
Add correct value encoding for keys in level
acolytec3 May 4, 2023
9c2e40d
Apply correct db interface typing
acolytec3 May 4, 2023
29579c6
Fix level encoding for strings
acolytec3 May 4, 2023
9810b92
More typing fixes
acolytec3 May 4, 2023
a752c60
Merge remote-tracking branch 'origin/develop-v7' into blockchain/leve…
acolytec3 May 4, 2023
ab7fc78
Merge remote-tracking branch 'origin/develop-v7' into blockchain/leve…
acolytec3 May 8, 2023
c7ea77a
Fix batch value encoding
acolytec3 May 8, 2023
ccd8be3
fix deprecated property usage
acolytec3 May 8, 2023
abcd64d
Replace level with mapdb in VM
acolytec3 May 9, 2023
d35960e
move mapDB to util
acolytec3 May 9, 2023
86cea58
Cleanup
acolytec3 May 9, 2023
521d164
Merge remote-tracking branch 'origin/develop-v7' into blockchain/leve…
acolytec3 May 10, 2023
2b1e081
Add key/value encodings back to interface
acolytec3 May 10, 2023
238626d
Remove level dependency from ethash
acolytec3 May 10, 2023
e1faad9
Add db ops to blockchain
acolytec3 May 10, 2023
c8d95df
remove unused imports
acolytec3 May 10, 2023
aca0e1d
remove leveldb
acolytec3 May 10, 2023
ef57f9b
update trie to use strings
acolytec3 May 11, 2023
b5fa536
Slight fixes
acolytec3 May 11, 2023
7f1d7e0
lint
acolytec3 May 11, 2023
7d807cf
fix trie tests
acolytec3 May 11, 2023
cd0ab87
cast trie db as any
acolytec3 May 11, 2023
ac004af
Merge remote-tracking branch 'origin/develop-v7' into blockchain/leve…
acolytec3 May 11, 2023
b1346f2
client: improve levelDB types and remove unnecessary typecasting
gabrocheleau May 11, 2023
d6d5387
Merge branch 'blockchain/level-refactor' of https://github.com/ethere…
gabrocheleau May 11, 2023
16a5ab9
client: remove additional unnecessary typecasting
gabrocheleau May 11, 2023
9d3e04b
client: type issue
gabrocheleau May 11, 2023
1b5af37
Merge branch 'develop-v7' into blockchain/level-refactor
gabrocheleau May 11, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
20 changes: 4 additions & 16 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 1 addition & 3 deletions packages/blockchain/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,10 @@
"@ethereumjs/trie": "^5.0.4",
"@ethereumjs/tx": "^4.1.1",
"@ethereumjs/util": "^8.0.5",
"abstract-level": "^1.0.3",
"debug": "^4.3.3",
"ethereum-cryptography": "^1.1.2",
"level": "^8.0.0",
"lru-cache": "^7.18.3",
"memory-level": "^1.0.0"
"lru-cache": "^7.18.3"
},
"devDependencies": {
"@types/async": "^2.4.1",
Expand Down
152 changes: 66 additions & 86 deletions packages/blockchain/src/blockchain.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import { Block, BlockHeader } from '@ethereumjs/block'
import { Chain, Common, ConsensusAlgorithm, ConsensusType, Hardfork } from '@ethereumjs/common'
import { KECCAK256_RLP, Lock, concatBytesNoTypeCheck } from '@ethereumjs/util'
import {
KECCAK256_RLP,
Lock,
MapDB,
bytesToPrefixedHexString,
concatBytesNoTypeCheck,
} from '@ethereumjs/util'
import { bytesToHex, equalsBytes, hexToBytes } from 'ethereum-cryptography/utils'
import { MemoryLevel } from 'memory-level'

import { CasperConsensus, CliqueConsensus, EthashConsensus } from './consensus'
import { DBOp, DBSaveLookups, DBSetBlockOrHeader, DBSetHashToNumber, DBSetTD } from './db/helpers'
Expand All @@ -16,15 +21,14 @@ import type { GenesisState } from './genesisStates'
import type { BlockchainInterface, BlockchainOptions, OnBlock } from './types'
import type { BlockData } from '@ethereumjs/block'
import type { CliqueConfig } from '@ethereumjs/common'
import type { BigIntLike } from '@ethereumjs/util'
import type { AbstractLevel } from 'abstract-level'
import type { BigIntLike, DB, DBObject } from '@ethereumjs/util'

/**
* This class stores and interacts with blocks.
*/
export class Blockchain implements BlockchainInterface {
consensus: Consensus
db: AbstractLevel<string | Uint8Array | Uint8Array, string | Uint8Array, string | Uint8Array>
db: DB<Uint8Array | string, Uint8Array | string | DBObject>
dbManager: DBManager

private _genesisBlock?: Block /** The genesis block of this blockchain */
Expand Down Expand Up @@ -115,7 +119,8 @@ export class Blockchain implements BlockchainInterface {
this._validateBlocks = opts.validateBlocks ?? true
this._customGenesisState = opts.genesisState

this.db = opts.db ? opts.db : new MemoryLevel()
this.db = opts.db !== undefined ? opts.db : new MapDB()

this.dbManager = new DBManager(this.db, this._common)

if (opts.consensus) {
Expand Down Expand Up @@ -191,17 +196,13 @@ export class Blockchain implements BlockchainInterface {
await this.consensus.setup({ blockchain: this })

if (this._isInitialized) return
let dbGenesisBlock
try {
const genesisHash = await this.dbManager.numberToHash(BigInt(0))
dbGenesisBlock = await this.dbManager.getBlock(genesisHash)
} catch (error: any) {
if (error.code !== 'LEVEL_NOT_FOUND') {
throw error
}
}

if (!genesisBlock) {
let genesisHash = await this.dbManager.numberToHash(BigInt(0))

const dbGenesisBlock =
genesisHash !== undefined ? await this.dbManager.getBlock(genesisHash) : undefined

if (genesisBlock === undefined) {
let stateRoot
if (this._common.chainId() === BigInt(1) && this._customGenesisState === undefined) {
// For mainnet use the known genesis stateRoot to quicken setup
Expand All @@ -214,13 +215,13 @@ export class Blockchain implements BlockchainInterface {

// If the DB has a genesis block, then verify that the genesis block in the
// DB is indeed the Genesis block generated or assigned.
if (dbGenesisBlock && !equalsBytes(genesisBlock.hash(), dbGenesisBlock.hash())) {
if (dbGenesisBlock !== undefined && !equalsBytes(genesisBlock.hash(), dbGenesisBlock.hash())) {
throw new Error(
'The genesis block in the DB has a different hash than the provided genesis block.'
)
}

const genesisHash = genesisBlock.hash()
genesisHash = genesisBlock.hash()

if (!dbGenesisBlock) {
// If there is no genesis block put the genesis block in the DB.
Expand All @@ -239,37 +240,16 @@ export class Blockchain implements BlockchainInterface {
this._genesisBlock = genesisBlock

// load verified iterator heads
try {
const heads = await this.dbManager.getHeads()
this._heads = heads
} catch (error: any) {
if (error.code !== 'LEVEL_NOT_FOUND') {
throw error
}
this._heads = {}
}
const heads = await this.dbManager.getHeads()
this._heads = heads !== undefined ? heads : {}

// load headerchain head
try {
const hash = await this.dbManager.getHeadHeader()
this._headHeaderHash = hash
} catch (error: any) {
if (error.code !== 'LEVEL_NOT_FOUND') {
throw error
}
this._headHeaderHash = genesisHash
}
let hash = await this.dbManager.getHeadHeader()
this._headHeaderHash = hash !== undefined ? hash : genesisHash

// load blockchain head
try {
const hash = await this.dbManager.getHeadBlock()
this._headBlockHash = hash
} catch (error: any) {
if (error.code !== 'LEVEL_NOT_FOUND') {
throw error
}
this._headBlockHash = genesisHash
}
hash = await this.dbManager.getHeadBlock()
this._headBlockHash = hash !== undefined ? hash : genesisHash

if (this._hardforkByHeadBlockNumber) {
const latestHeader = await this._getHeader(this._headHeaderHash)
Expand Down Expand Up @@ -423,6 +403,9 @@ export class Blockchain implements BlockchainInterface {
async resetCanonicalHead(canonicalHead: bigint) {
await this.runWithLock<void>(async () => {
const hash = await this.dbManager.numberToHash(canonicalHead)
if (hash === undefined) {
throw new Error(`no block for ${canonicalHead} found in DB`)
}
const header = await this._getHeader(hash, canonicalHead)
const td = await this.getParentTD(header)

Expand Down Expand Up @@ -738,18 +721,16 @@ export class Blockchain implements BlockchainInterface {
// in the `VM` if we encounter a `BLOCKHASH` opcode: then a bigint is used we
// need to then read the block from the canonical chain Q: is this safe? We
// know it is OK if we call it from the iterator... (runBlock)
try {
return await this.dbManager.getBlock(blockId)
} catch (error: any) {
if (error.code === 'LEVEL_NOT_FOUND') {
if (typeof blockId === 'object') {
error.message = `Block with hash ${bytesToHex(blockId)} not found in DB (NotFound)`
} else {
error.message = `Block number ${blockId} not found in DB (NotFound)`
}
const block = await this.dbManager.getBlock(blockId)

if (block === undefined) {
if (typeof blockId === 'object') {
throw new Error(`Block with hash ${bytesToHex(blockId)} not found in DB`)
} else {
throw new Error(`Block number ${blockId} not found in DB`)
}
throw error
}
return block
}

/**
Expand All @@ -758,6 +739,9 @@ export class Blockchain implements BlockchainInterface {
public async getTotalDifficulty(hash: Uint8Array, number?: bigint): Promise<bigint> {
if (number === undefined) {
number = await this.dbManager.hashToNumber(hash)
if (number === undefined) {
throw new Error(`Block with hash ${bytesToPrefixedHexString(hash)} not found in DB`)
}
}
return this.dbManager.getTotalDifficulty(hash, number)
}
Expand Down Expand Up @@ -794,12 +778,12 @@ export class Blockchain implements BlockchainInterface {
let block
try {
block = await this.getBlock(blockId)
} catch (error: any) {
if (error.code !== 'LEVEL_NOT_FOUND') {
throw error
}
return
} catch (err: any) {
if (err.message.includes('not found in DB') === true) {
return
} else throw err
}

i++
const nextBlockNumber = block.header.number + BigInt(reverse ? -1 : 1)
if (i !== 0 && skip && i % (skip + 1) !== 0) {
Expand Down Expand Up @@ -835,11 +819,12 @@ export class Blockchain implements BlockchainInterface {
let number
try {
number = await this.dbManager.hashToNumber(hashes[mid])
} catch (error: any) {
if (error.code !== 'LEVEL_NOT_FOUND') {
throw error
}
} catch (err: any) {
if (err.message.includes('not found in DB') === true) {
number = undefined
} else throw err
}

if (number !== undefined) {
min = mid + 1
} else {
Expand Down Expand Up @@ -945,9 +930,9 @@ export class Blockchain implements BlockchainInterface {
try {
const childHeader = await this.getCanonicalHeader(blockNumber + BigInt(1))
await this._delChild(childHeader.hash(), childHeader.number, headHash, ops)
} catch (error: any) {
if (error.code !== 'LEVEL_NOT_FOUND') {
throw error
} catch (err: any) {
if (err.message.includes('not found in canonical chain') !== true) {
throw err
}
}
}
Expand Down Expand Up @@ -977,7 +962,8 @@ export class Blockchain implements BlockchainInterface {
}

let headBlockNumber = await this.dbManager.hashToNumber(headHash)
let nextBlockNumber = headBlockNumber + BigInt(1)
// `headBlockNumber` should always exist since it defaults to the genesis block
let nextBlockNumber = headBlockNumber! + BigInt(1)
let blocksRanCounter = 0
let lastBlock: Block | undefined

Expand All @@ -992,7 +978,7 @@ export class Blockchain implements BlockchainInterface {
// If reorg has happened, the _heads must have been updated so lets reload the counters
headHash = this._heads[name] ?? this.genesisBlock.hash()
headBlockNumber = await this.dbManager.hashToNumber(headHash)
nextBlockNumber = headBlockNumber + BigInt(1)
nextBlockNumber = headBlockNumber! + BigInt(1)
nextBlock = await this.getBlock(nextBlockNumber)
}

Expand Down Expand Up @@ -1025,7 +1011,7 @@ export class Blockchain implements BlockchainInterface {
// Successful execution of onBlock, move the head pointer
blocksRanCounter++
} catch (error: any) {
if (error.code === 'LEVEL_NOT_FOUND') {
if ((error.message as string).includes('not found in DB')) {
break
} else {
throw error
Expand Down Expand Up @@ -1199,13 +1185,9 @@ export class Blockchain implements BlockchainInterface {
staleHeadBlock = true
}

try {
header = await this._getHeader(header.parentHash, --currentNumber)
} catch (error: any) {
header = await this._getHeader(header.parentHash, --currentNumber)
if (header === undefined) {
staleHeads = []
if (error.code !== 'LEVEL_NOT_FOUND') {
throw error
}
break
}
}
Expand Down Expand Up @@ -1259,6 +1241,8 @@ export class Blockchain implements BlockchainInterface {
private async _getHeader(hash: Uint8Array, number?: bigint) {
if (number === undefined) {
number = await this.dbManager.hashToNumber(hash)
if (number === undefined)
throw new Error(`no header for ${bytesToPrefixedHexString(hash)} found in DB`)
}
return this.dbManager.getHeader(hash, number)
}
Expand Down Expand Up @@ -1302,25 +1286,21 @@ export class Blockchain implements BlockchainInterface {
*/
async getCanonicalHeader(number: bigint) {
const hash = await this.dbManager.numberToHash(number)
if (hash === undefined) {
throw new Error(`header with number ${number} not found in canonical chain`)
}
return this._getHeader(hash, number)
}

/**
* This method either returns a Uint8Array if there exists one in the DB or if it
* does not exist (DB throws a `NotFoundError`) then return false If DB throws
* does not exist then return false If DB throws
* any other error, this function throws.
* @param number
*/
async safeNumberToHash(number: bigint): Promise<Uint8Array | false> {
try {
const hash = await this.dbManager.numberToHash(number)
return hash
} catch (error: any) {
if (error.code !== 'LEVEL_NOT_FOUND') {
throw error
}
return false
}
const hash = await this.dbManager.numberToHash(number)
return hash !== undefined ? hash : false
}

/**
Expand Down