@ethereumjs/blockchain
Advanced tools
Comparing version 6.3.0 to 7.0.0-rc.1
{ | ||
"name": "@ethereumjs/blockchain", | ||
"version": "6.3.0", | ||
"version": "7.0.0-rc.1", | ||
"description": "A module to store and interact with blocks", | ||
@@ -19,4 +19,11 @@ "keywords": [ | ||
"author": "mjbecze <mjbecze@gmail.com>", | ||
"main": "dist/index.js", | ||
"types": "dist/index.d.ts", | ||
"type": "commonjs", | ||
"main": "dist/cjs/index.js", | ||
"module": "dist/esm/index.js", | ||
"exports": { | ||
".": { | ||
"import": "./dist/esm/index.js", | ||
"require": "./dist/cjs/index.js" | ||
} | ||
}, | ||
"files": [ | ||
@@ -29,4 +36,5 @@ "dist", | ||
"clean": "../../config/cli/clean-package.sh", | ||
"coverage": "../../config/cli/coverage.sh", | ||
"docs:build": "typedoc --options typedoc.js", | ||
"coverage": "npx vitest run --coverage.enabled --coverage.reporter=lcov", | ||
"docs:build": "typedoc --options typedoc.cjs", | ||
"examples": "ts-node ../../scripts/examples-runner.ts -- blockchain", | ||
"lint": "../../config/cli/lint.sh", | ||
@@ -36,31 +44,23 @@ "lint:diff": "../../config/cli/lint-diff.sh", | ||
"prepublishOnly": "../../config/cli/prepublish.sh", | ||
"tape": "tape -r ts-node/register", | ||
"test": "npm run test:node && npm run test:browser", | ||
"test:browser": "karma start karma.conf.js", | ||
"test:node": "npm run tape -- ./test/*.spec.ts", | ||
"test:browser": "npx vitest run --browser.name=webkit --browser.provider=playwright --browser.headless", | ||
"test:node": "npx vitest run", | ||
"tsc": "../../config/cli/ts-compile.sh" | ||
}, | ||
"dependencies": { | ||
"@ethereumjs/block": "^4.3.0", | ||
"@ethereumjs/common": "^3.2.0", | ||
"@ethereumjs/ethash": "^2.1.0", | ||
"@ethereumjs/rlp": "^4.0.1", | ||
"@ethereumjs/trie": "^5.1.0", | ||
"@ethereumjs/tx": "^4.2.0", | ||
"@ethereumjs/util": "^8.1.0", | ||
"abstract-level": "^1.0.3", | ||
"@ethereumjs/block": "5.0.0-rc.1", | ||
"@ethereumjs/common": "4.0.0-rc.1", | ||
"@ethereumjs/ethash": "3.0.0-rc.1", | ||
"@ethereumjs/rlp": "5.0.0-rc.1", | ||
"@ethereumjs/trie": "6.0.0-rc.1", | ||
"@ethereumjs/tx": "5.0.0-rc.1", | ||
"@ethereumjs/util": "9.0.0-rc.1", | ||
"debug": "^4.3.3", | ||
"ethereum-cryptography": "^2.0.0", | ||
"level": "^8.0.0", | ||
"lru-cache": "^5.1.1", | ||
"memory-level": "^1.0.0" | ||
"ethereum-cryptography": "^2.1.2", | ||
"lru-cache": "^10.0.0" | ||
}, | ||
"devDependencies": { | ||
"@types/async": "^2.4.1", | ||
"@types/level-errors": "^3.0.0", | ||
"@types/lru-cache": "^5.1.0" | ||
}, | ||
"devDependencies": {}, | ||
"engines": { | ||
"node": ">=14" | ||
"node": ">=18" | ||
} | ||
} |
@@ -9,2 +9,4 @@ # @ethereumjs/blockchain | ||
Note: this README has been updated containing the changes from our next breaking release round [UNRELEASED] targeted for Summer 2023. See the README files from the [maintenance-v6](https://github.com/ethereumjs/ethereumjs-monorepo/tree/maintenance-v6/) branch for documentation matching our latest releases. | ||
| A module to store and interact with blocks. | | ||
@@ -37,22 +39,18 @@ | ------------------------------------------- | | ||
The following is an example to iterate through an existing Geth DB (needs `level` to be installed separately). | ||
The following is an example to instantiate a simple Blockchain object, put blocks into the blockchain and then iterate through the blocks added: | ||
This module performs write operations. Making a backup of your data before trying it is recommended. Otherwise, you can end up with a compromised DB state. | ||
```typescript | ||
import { Blockchain } from '@ethereumjs/blockchain' | ||
import { Chain, Common } from '@ethereumjs/common' | ||
import { bytesToHex } from '@ethereumjs/util' | ||
const { Level } = require('level') | ||
const gethDbPath = './chaindata' // Add your own path here. It will get modified, see remarks. | ||
const common = new Common({ chain: Chain.Ropsten }) | ||
const db = new Level(gethDbPath) | ||
// Use the safe static constructor which awaits the init method | ||
const blockchain = Blockchain.create({ common, db }) | ||
// See @ethereumjs/block on how to create a block | ||
await blockchain.putBlock(block1) | ||
await blockchain.putBlock(block2) | ||
blockchain.iterator('i', (block) => { | ||
const blockNumber = block.header.number.toString() | ||
const blockHash = block.hash().toString('hex') | ||
const blockHash = bytesToHex(block.hash()) | ||
console.log(`Block ${blockNumber}: ${blockHash}`) | ||
@@ -62,4 +60,10 @@ }) | ||
**WARNING**: Since `@ethereumjs/blockchain` is also doing write operations on the DB for safety reasons only run this on a copy of your database, otherwise this might lead to a compromised DB state. | ||
### Database Abstraction / Removed LevelDB Dependency | ||
With the v7 release the Blockchain library database has gotten an additional abstraction layer which allows to switch the backend to whatever is fitting the best for a use case, see PR [#2669](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2669) and PR [#2673](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2673). The database just needs to conform to the new [DB](https://github.com/ethereumjs/ethereumjs-monorepo/blob/master/packages/util/src/db.ts) interface provided in the `@ethereumjs/util` package (since this is used in other places as well). | ||
By default the blockchain package is now using a [MapDB](https://github.com/ethereumjs/ethereumjs-monorepo/blob/master/packages/util/src/mapDB.ts) non-persistent data storage which is also generically provided in the `@ethereumjs/util` package. | ||
If you need a persistent data store for your use case you can consider using the wrapper we have written within our [client](https://github.com/ethereumjs/ethereumjs-monorepo/blob/master/packages/client/src/execution/level.ts) library. | ||
### Consensus | ||
@@ -75,3 +79,3 @@ | ||
#### Custom Conensus Algorithms | ||
#### Custom Consensus Algorithms | ||
@@ -84,4 +88,18 @@ Also part of V6, you can also create a custom consensus class implementing the above interface and pass it into the `Blockchain` constructor using the `consensus` option at instantiation. See [this test script](https://github.com/ethereumjs/ethereumjs-monorepo/blob/master/packages/blockchain/test/customConsensus.spec.ts) for a complete example of how write and use a custom consensus implementation. | ||
Starting with v6 responsibility for setting up a custom genesis state moved from the [Common](../common/) library to the `Blockchain` package, see PR [#1924](https://github.com/ethereumjs/ethereumjs-monorepo/pull/1924) for some work context. | ||
### Genesis in v7 (removed genesis dependency) | ||
Genesis state was huge and had previously been bundled with the `Blockchain` package with the burden going over to the VM, since `Blockchain` is a dependency. | ||
Starting with the v7 release genesis state has been removed from `blockchain` and moved into its own auxiliary package [@ethereumjs/genesis](https://github.com/ethereumjs/ethereumjs-monorepo/tree/master/packages/genesis), from which it can be included if needed (for most - especially VM - use cases it is not necessary), see PR [#2844](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2844). | ||
This goes along with some changes in Blockchain and VM API: | ||
- Blockchain: There is a new constructor option `genesisStateRoot` beside `genesisBlock` and `genesisState` for an alternative condensed way to provide the genesis state root directly | ||
- Blockchain: `genesisState(): GenesisState` method has been replaced by the async `getGenesisStateRoot(chainId: Chain): Promise<Uint8Array>` method | ||
- VM: `activateGenesisState?: boolean` constructor option has been replaced with a `genesisState?: GenesisState` option | ||
### Genesis in v6 | ||
For the v6 release responsibility for setting up a custom genesis state moved from the [Common](../common/) library to the `Blockchain` package, see PR [#1924](https://github.com/ethereumjs/ethereumjs-monorepo/pull/1924) for some work context. | ||
A genesis state can be set along `Blockchain` creation by passing in a custom `genesisBlock` and `genesisState`. For `mainnet` and the official test networks like `sepolia` or `goerli` genesis is already provided with the block data coming from `@ethereumjs/common`. The genesis state is being integrated in the `Blockchain` library (see `genesisStates` folder). | ||
@@ -110,12 +128,22 @@ | ||
## EIP-1559 Support | ||
## Supported Blocks and Tx Types | ||
### EIP-1559 Support | ||
This library supports the handling of `EIP-1559` blocks and transactions starting with the `v5.3.0` release. | ||
### EIP-4844 Shard Blob Transactions Support (experimental) | ||
### EIP-4844 Shard Blob Transactions Support | ||
This library supports an experimental version of the blob transaction type introduced with [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844) as being specified in the [01d3209](https://github.com/ethereum/EIPs/commit/01d320998d1d53d95f347b5f43feaf606f230703) EIP version from February 8, 2023 and deployed along `eip4844-devnet-4` (January 2023) starting with `v6.2.0`. | ||
This library supports the blob transaction type introduced with [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844) as being specified in the [b9a5a11](https://github.com/ethereum/EIPs/commit/b9a5a117ab7e1dc18f937841d00598b527c306e7) EIP version from July 2023 deployed along [4844-devnet-7](https://github.com/ethpandaops/4844-testnet) (July 2023), see PR [#2349](https://github.com/ethereumjs/ethereumjs-monorepo/pull/2349) and following. | ||
**Note:** 4844 support is not yet completely stable and there will still be (4844-)breaking changes along all types of library releases. | ||
The blockchain library now allows for blob transactions to be validated and included in a chain where EIP-4844 activated either by hardfork or standalone EIP (see latest tx library release for additional details). | ||
## Browser | ||
With the breaking release round in Summer 2023 we have added hybrid ESM/CJS builds for all our libraries (see section below) and have eliminated many of the caveats which had previously prevented a frictionless browser usage. | ||
It is now easily possible to run a browser build of one of the EthereumJS libraries within a modern browser using the provided ESM build. For a setup example see [./examples/browser.html](./examples/browser.html). | ||
## API | ||
@@ -127,2 +155,26 @@ | ||
### Hybrid CJS/ESM Builds | ||
With the breaking releases from Summer 2023 we have started to ship our libraries with both CommonJS (`cjs` folder) and ESM builds (`esm` folder), see `package.json` for the detailed setup. | ||
If you use an ES6-style `import` in your code files from the ESM build will be used: | ||
```typescript | ||
import { EthereumJSClass } from '@ethereumjs/[PACKAGE_NAME]' | ||
``` | ||
If you use Node.js specific `require` the CJS build will be used: | ||
```typescript | ||
const { EthereumJSClass } = require('@ethereumjs/[PACKAGE_NAME]') | ||
``` | ||
Using ESM will give you additional advantages over CJS beyond browser usage like static code analysis / Tree Shaking which CJS can not provide. | ||
### Buffer -> Uint8Array | ||
With the breaking releases from Summer 2023 we have removed all Node.js specific `Buffer` usages from our libraries and replace these with [Uint8Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array) representations, which are available both in Node.js and the browser (`Buffer` is a subclass of `Uint8Array`). | ||
We have converted existing Buffer conversion methods to Uint8Array conversion methods in the [@ethereumjs/util](https://github.com/ethereumjs/ethereumjs-monorepo/tree/master/packages/util) `bytes` module, see the respective README section for guidance. | ||
### BigInt Support | ||
@@ -149,3 +201,3 @@ | ||
```shell | ||
DEBUG=blockchain:clique ts-node test.ts | ||
DEBUG=ethjs,blockchain:clique ts-node test.ts | ||
``` | ||
@@ -152,0 +204,0 @@ |
import { Block, BlockHeader } from '@ethereumjs/block' | ||
import { Chain, Common, ConsensusAlgorithm, ConsensusType, Hardfork } from '@ethereumjs/common' | ||
import { KECCAK256_RLP, Lock } from '@ethereumjs/util' | ||
import { MemoryLevel } from 'memory-level' | ||
import { | ||
Chain, | ||
ChainGenesis, | ||
Common, | ||
ConsensusAlgorithm, | ||
ConsensusType, | ||
Hardfork, | ||
} from '@ethereumjs/common' | ||
import { genesisStateRoot as genGenesisStateRoot } from '@ethereumjs/trie' | ||
import { | ||
KECCAK256_RLP, | ||
Lock, | ||
MapDB, | ||
bytesToHex, | ||
bytesToUnprefixedHex, | ||
concatBytes, | ||
equalsBytes, | ||
} from '@ethereumjs/util' | ||
import { CasperConsensus, CliqueConsensus, EthashConsensus } from './consensus' | ||
import { DBOp, DBSaveLookups, DBSetBlockOrHeader, DBSetHashToNumber, DBSetTD } from './db/helpers' | ||
import { DBManager } from './db/manager' | ||
import { DBTarget } from './db/operation' | ||
import { genesisStateRoot } from './genesisStates' | ||
import {} from './utils' | ||
import { CasperConsensus, CliqueConsensus, EthashConsensus } from './consensus/index.js' | ||
import { | ||
DBOp, | ||
DBSaveLookups, | ||
DBSetBlockOrHeader, | ||
DBSetHashToNumber, | ||
DBSetTD, | ||
} from './db/helpers.js' | ||
import { DBManager } from './db/manager.js' | ||
import { DBTarget } from './db/operation.js' | ||
import type { Consensus } from './consensus' | ||
import type { GenesisState } from './genesisStates' | ||
import type { BlockchainInterface, BlockchainOptions, OnBlock } from './types' | ||
import type { | ||
BlockchainInterface, | ||
BlockchainOptions, | ||
Consensus, | ||
GenesisOptions, | ||
OnBlock, | ||
} from './types.js' | ||
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, GenesisState } from '@ethereumjs/util' | ||
@@ -26,3 +48,3 @@ /** | ||
consensus: Consensus | ||
db: AbstractLevel<string | Buffer | Uint8Array, string | Buffer, string | Buffer> | ||
db: DB<Uint8Array | string, Uint8Array | string | DBObject> | ||
dbManager: DBManager | ||
@@ -40,5 +62,5 @@ | ||
/** The hash of the current head block */ | ||
private _headBlockHash?: Buffer | ||
private _headBlockHash?: Uint8Array | ||
/** The hash of the current head header */ | ||
private _headHeaderHash?: Buffer | ||
private _headHeaderHash?: Uint8Array | ||
@@ -50,3 +72,3 @@ /** | ||
*/ | ||
private _heads: { [key: string]: Buffer } | ||
private _heads: { [key: string]: Uint8Array } | ||
@@ -56,3 +78,3 @@ protected _isInitialized = false | ||
_common: Common | ||
public readonly common: Common | ||
private _hardforkByHeadBlockNumber: boolean | ||
@@ -71,3 +93,4 @@ private readonly _validateConsensus: boolean | ||
const blockchain = new Blockchain(opts) | ||
await blockchain._init(opts.genesisBlock) | ||
await blockchain._init(opts) | ||
return blockchain | ||
@@ -87,4 +110,4 @@ } | ||
const block = Block.fromBlockData(blockData, { | ||
common: blockchain._common, | ||
hardforkByBlockNumber: true, | ||
common: blockchain.common, | ||
setHardfork: true, | ||
}) | ||
@@ -108,7 +131,7 @@ await blockchain.putBlock(block) | ||
if (opts.common) { | ||
this._common = opts.common | ||
this.common = opts.common | ||
} else { | ||
const DEFAULT_CHAIN = Chain.Mainnet | ||
const DEFAULT_HARDFORK = Hardfork.Chainstart | ||
this._common = new Common({ | ||
this.common = new Common({ | ||
chain: DEFAULT_CHAIN, | ||
@@ -124,9 +147,10 @@ hardfork: DEFAULT_HARDFORK, | ||
this.db = opts.db ? opts.db : new MemoryLevel() | ||
this.dbManager = new DBManager(this.db, this._common) | ||
this.db = opts.db !== undefined ? opts.db : new MapDB() | ||
this.dbManager = new DBManager(this.db, this.common) | ||
if (opts.consensus) { | ||
this.consensus = opts.consensus | ||
} else { | ||
switch (this._common.consensusAlgorithm()) { | ||
switch (this.common.consensusAlgorithm()) { | ||
case ConsensusAlgorithm.Casper: | ||
@@ -142,3 +166,3 @@ this.consensus = new CasperConsensus() | ||
default: | ||
throw new Error(`consensus algorithm ${this._common.consensusAlgorithm()} not supported`) | ||
throw new Error(`consensus algorithm ${this.common.consensusAlgorithm()} not supported`) | ||
} | ||
@@ -148,9 +172,9 @@ } | ||
if (this._validateConsensus) { | ||
if (this._common.consensusType() === ConsensusType.ProofOfWork) { | ||
if (this._common.consensusAlgorithm() !== ConsensusAlgorithm.Ethash) { | ||
if (this.common.consensusType() === ConsensusType.ProofOfWork) { | ||
if (this.common.consensusAlgorithm() !== ConsensusAlgorithm.Ethash) { | ||
throw new Error('consensus validation only supported for pow ethash algorithm') | ||
} | ||
} | ||
if (this._common.consensusType() === ConsensusType.ProofOfAuthority) { | ||
if (this._common.consensusAlgorithm() !== ConsensusAlgorithm.Clique) { | ||
if (this.common.consensusType() === ConsensusType.ProofOfAuthority) { | ||
if (this.common.consensusAlgorithm() !== ConsensusAlgorithm.Clique) { | ||
throw new Error( | ||
@@ -183,3 +207,3 @@ 'consensus (signature) validation only supported for poa clique algorithm' | ||
*/ | ||
copy(): Blockchain { | ||
shallowCopy(): Blockchain { | ||
const copiedBlockchain = Object.create( | ||
@@ -189,3 +213,3 @@ Object.getPrototypeOf(this), | ||
) | ||
copiedBlockchain._common = this._common.copy() | ||
copiedBlockchain.common = this.common.copy() | ||
return copiedBlockchain | ||
@@ -199,35 +223,30 @@ } | ||
* | ||
* @param opts An options object to provide genesisBlock or ways to contruct it | ||
* | ||
* @hidden | ||
*/ | ||
private async _init(genesisBlock?: Block): Promise<void> { | ||
private async _init(opts: GenesisOptions = {}): Promise<void> { | ||
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 stateRoot | ||
if (this._common.chainId() === BigInt(1) && this._customGenesisState === undefined) { | ||
// For mainnet use the known genesis stateRoot to quicken setup | ||
stateRoot = Buffer.from( | ||
'd7f8974fb5ac78d9ac099b9ad5018bedc2ce0a72dad1827a1709da30580f0544', | ||
'hex' | ||
) | ||
let stateRoot = opts.genesisBlock?.header.stateRoot ?? opts.genesisStateRoot | ||
if (stateRoot === undefined) { | ||
if (this._customGenesisState !== undefined) { | ||
stateRoot = await genGenesisStateRoot(this._customGenesisState) | ||
} else { | ||
stateRoot = await genesisStateRoot(this.genesisState()) | ||
// eslint-disable-next-line @typescript-eslint/no-use-before-define | ||
stateRoot = await getGenesisStateRoot(Number(this.common.chainId()) as Chain) | ||
} | ||
genesisBlock = this.createGenesisBlock(stateRoot) | ||
} | ||
const genesisBlock = opts.genesisBlock ?? this.createGenesisBlock(stateRoot) | ||
let genesisHash = await this.dbManager.numberToHash(BigInt(0)) | ||
const dbGenesisBlock = | ||
genesisHash !== undefined ? await this.dbManager.getBlock(genesisHash) : undefined | ||
// 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 && !genesisBlock.hash().equals(dbGenesisBlock.hash())) { | ||
if (dbGenesisBlock !== undefined && !equalsBytes(genesisBlock.hash(), dbGenesisBlock.hash())) { | ||
throw new Error( | ||
@@ -238,3 +257,3 @@ 'The genesis block in the DB has a different hash than the provided genesis block.' | ||
const genesisHash = genesisBlock.hash() | ||
genesisHash = genesisBlock.hash() | ||
@@ -258,33 +277,12 @@ if (!dbGenesisBlock) { | ||
// 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 | ||
@@ -320,3 +318,3 @@ if (this._hardforkByHeadBlockNumber) { | ||
* | ||
* This function replaces the old {@link Blockchain.getHead} method. Note that | ||
* This function replaces the old Blockchain.getHead() method. Note that | ||
* the function deviates from the old behavior and returns the | ||
@@ -338,23 +336,2 @@ * genesis hash instead of the current head block if an iterator | ||
/** | ||
* Returns the specified iterator head. | ||
* | ||
* @param name - Optional name of the iterator head (default: 'vm') | ||
* | ||
* @deprecated use {@link Blockchain.getIteratorHead} instead. | ||
* Note that {@link Blockchain.getIteratorHead} doesn't return | ||
* the `headHeader` but the genesis hash as an initial iterator | ||
* head value (now matching the behavior of {@link Blockchain.iterator} | ||
* on a first run) | ||
*/ | ||
async getHead(name = 'vm'): Promise<Block> { | ||
return this.runWithLock<Block>(async () => { | ||
// if the head is not found return the headHeader | ||
const hash = this._heads[name] ?? this._headBlockHash | ||
if (hash === undefined) throw new Error('No head found.') | ||
const block = await this.getBlock(hash) | ||
return block | ||
}) | ||
} | ||
/** | ||
* Returns the latest header in the canonical chain. | ||
@@ -445,2 +422,5 @@ */ | ||
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) | ||
@@ -480,4 +460,4 @@ const td = await this.getParentTD(header) | ||
item instanceof BlockHeader | ||
? new Block(item, undefined, undefined, { | ||
common: item._common, | ||
? new Block(item, undefined, undefined, undefined, { | ||
common: item.common, | ||
}) | ||
@@ -499,3 +479,3 @@ : item | ||
if (block._common.chainId() !== this._common.chainId()) { | ||
if (block.common.chainId() !== this.common.chainId()) { | ||
throw new Error('Chain mismatch while trying to put block or header') | ||
@@ -539,3 +519,3 @@ } | ||
td > currentTd.header || | ||
block._common.consensusType() === ConsensusType.ProofOfStake | ||
block.common.consensusType() === ConsensusType.ProofOfStake | ||
) { | ||
@@ -616,6 +596,6 @@ const foundCommon = await this.findCommonAncestor(header) | ||
if (!(header._common.consensusType() === 'pos')) await this.consensus.validateDifficulty(header) | ||
if (!(header.common.consensusType() === 'pos')) await this.consensus.validateDifficulty(header) | ||
if (this._common.consensusAlgorithm() === ConsensusAlgorithm.Clique) { | ||
const period = (this._common.consensusConfig() as CliqueConfig).period | ||
if (this.common.consensusAlgorithm() === ConsensusAlgorithm.Clique) { | ||
const period = (this.common.consensusConfig() as CliqueConfig).period | ||
// Timestamp diff between blocks is lower than PERIOD (clique) | ||
@@ -640,9 +620,9 @@ if (parentHeader.timestamp + BigInt(period) > header.timestamp) { | ||
// check blockchain dependent EIP1559 values | ||
if (header._common.isActivatedEIP(1559) === true) { | ||
if (header.common.isActivatedEIP(1559) === true) { | ||
// check if the base fee is correct | ||
let expectedBaseFee | ||
const londonHfBlock = this._common.hardforkBlock(Hardfork.London) | ||
const londonHfBlock = this.common.hardforkBlock(Hardfork.London) | ||
const isInitialEIP1559Block = number === londonHfBlock | ||
if (isInitialEIP1559Block) { | ||
expectedBaseFee = header._common.param('gasConfig', 'initialBaseFee') | ||
expectedBaseFee = header.common.param('gasConfig', 'initialBaseFee') | ||
} else { | ||
@@ -656,2 +636,9 @@ expectedBaseFee = parentHeader.calcNextBaseFee() | ||
} | ||
if (header.common.isActivatedEIP(4844) === true) { | ||
const expectedExcessDataGas = parentHeader.calcNextExcessDataGas() | ||
if (header.excessDataGas !== expectedExcessDataGas) { | ||
throw new Error(`expected data gas: ${expectedExcessDataGas}, got: ${header.excessDataGas}`) | ||
} | ||
} | ||
} | ||
@@ -668,2 +655,6 @@ | ||
await block.validateData(false) | ||
// TODO: Rethink how validateHeader vs validateBlobTransactions works since the parentHeader is retrieved multiple times | ||
// (one for each uncle header and then for validateBlobTxs). | ||
const parentBlock = await this.getBlock(block.header.parentHash) | ||
block.validateBlobTransactions(parentBlock.header) | ||
} | ||
@@ -715,7 +706,7 @@ /** | ||
// mark block hash as part of the canonical chain | ||
canonicalChainHashes[parentBlock.hash().toString('hex')] = true | ||
canonicalChainHashes[bytesToUnprefixedHex(parentBlock.hash())] = true | ||
// for each of the uncles, mark the uncle as included | ||
parentBlock.uncleHeaders.map((uh) => { | ||
includedUncles[uh.hash().toString('hex')] = true | ||
includedUncles[bytesToUnprefixedHex(uh.hash())] = true | ||
}) | ||
@@ -732,4 +723,4 @@ | ||
uncleHeaders.map((uh) => { | ||
const uncleHash = uh.hash().toString('hex') | ||
const parentHash = uh.parentHash.toString('hex') | ||
const uncleHash = bytesToUnprefixedHex(uh.hash()) | ||
const parentHash = bytesToUnprefixedHex(uh.parentHash) | ||
@@ -760,3 +751,3 @@ if (!canonicalChainHashes[parentHash]) { | ||
*/ | ||
async getBlock(blockId: Buffer | number | bigint): Promise<Block> { | ||
async getBlock(blockId: Uint8Array | number | bigint): Promise<Block> { | ||
// cannot wait for a lock here: it is used both in `validate` of `Block` | ||
@@ -767,14 +758,12 @@ // (calls `getBlock` to get `parentHash`) it is also called from `runBlock` | ||
// 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 ${blockId.toString('hex')} 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 | ||
} | ||
@@ -785,5 +774,8 @@ | ||
*/ | ||
public async getTotalDifficulty(hash: Buffer, number?: bigint): Promise<bigint> { | ||
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 ${bytesToHex(hash)} not found in DB`) | ||
} | ||
} | ||
@@ -812,3 +804,3 @@ return this.dbManager.getTotalDifficulty(hash, number) | ||
async getBlocks( | ||
blockId: Buffer | bigint | number, | ||
blockId: Uint8Array | bigint | number, | ||
maxBlocks: number, | ||
@@ -822,12 +814,12 @@ skip: number, | ||
const nextBlock = async (blockId: Buffer | bigint | number): Promise<any> => { | ||
const nextBlock = async (blockId: Uint8Array | bigint | number): Promise<any> => { | ||
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++ | ||
@@ -855,4 +847,4 @@ const nextBlockNumber = block.header.number + BigInt(reverse ? -1 : 1) | ||
*/ | ||
async selectNeededHashes(hashes: Array<Buffer>): Promise<Buffer[]> { | ||
return this.runWithLock<Buffer[]>(async () => { | ||
async selectNeededHashes(hashes: Array<Uint8Array>): Promise<Uint8Array[]> { | ||
return this.runWithLock<Uint8Array[]>(async () => { | ||
let max: number | ||
@@ -869,7 +861,8 @@ let mid: number | ||
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) { | ||
@@ -897,3 +890,3 @@ min = mid + 1 | ||
*/ | ||
async delBlock(blockHash: Buffer) { | ||
async delBlock(blockHash: Uint8Array) { | ||
// Q: is it safe to make this not wait for a lock? this is called from | ||
@@ -910,3 +903,3 @@ // `BlockchainTestsRunner` in case `runBlock` throws (i.e. the block is invalid). | ||
*/ | ||
private async _delBlock(blockHash: Buffer) { | ||
private async _delBlock(blockHash: Uint8Array) { | ||
const dbOps: DBOp[] = [] | ||
@@ -923,3 +916,3 @@ | ||
const inCanonical = canonicalHash !== false && canonicalHash.equals(blockHash) | ||
const inCanonical = canonicalHash !== false && equalsBytes(canonicalHash, blockHash) | ||
@@ -954,5 +947,5 @@ // delete the block, and if block is in the canonical chain, delete all | ||
private async _delChild( | ||
blockHash: Buffer, | ||
blockHash: Uint8Array, | ||
blockNumber: bigint, | ||
headHash: Buffer | null, | ||
headHash: Uint8Array | null, | ||
ops: DBOp[] | ||
@@ -970,7 +963,10 @@ ) { | ||
if (this._headHeaderHash?.equals(blockHash) === true) { | ||
if ( | ||
this._headHeaderHash !== undefined && | ||
equalsBytes(this._headHeaderHash, blockHash) === true | ||
) { | ||
this._headHeaderHash = headHash | ||
} | ||
if (this._headBlockHash?.equals(blockHash) === true) { | ||
if (this._headBlockHash !== undefined && equalsBytes(this._headBlockHash, blockHash)) { | ||
this._headBlockHash = headHash | ||
@@ -982,5 +978,5 @@ } | ||
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 | ||
} | ||
@@ -1015,42 +1011,62 @@ } | ||
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 | ||
while (maxBlocks !== blocksRanCounter) { | ||
try { | ||
let nextBlock = await this.getBlock(nextBlockNumber) | ||
const reorg = lastBlock ? !lastBlock.hash().equals(nextBlock.header.parentHash) : false | ||
if (reorg) { | ||
// 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) | ||
nextBlock = await this.getBlock(nextBlockNumber) | ||
} | ||
this._heads[name] = nextBlock.hash() | ||
lastBlock = nextBlock | ||
if (releaseLockOnCallback === true) { | ||
this._lock.release() | ||
} | ||
try { | ||
while (maxBlocks !== blocksRanCounter) { | ||
try { | ||
await onBlock(nextBlock, reorg) | ||
} finally { | ||
let nextBlock = await this.getBlock(nextBlockNumber) | ||
const reorg = lastBlock | ||
? !equalsBytes(lastBlock.hash(), nextBlock.header.parentHash) | ||
: false | ||
if (reorg) { | ||
// 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) | ||
nextBlock = await this.getBlock(nextBlockNumber) | ||
} | ||
// While running onBlock with released lock, reorgs can happen via putBlocks | ||
let reorgWhileOnBlock = false | ||
if (releaseLockOnCallback === true) { | ||
await this._lock.acquire() | ||
this._lock.release() | ||
} | ||
try { | ||
await onBlock(nextBlock, reorg) | ||
} finally { | ||
if (releaseLockOnCallback === true) { | ||
await this._lock.acquire() | ||
// If lock was released check if reorg occured | ||
const nextBlockMayBeReorged = await this.getBlock(nextBlockNumber).catch( | ||
(_e) => null | ||
) | ||
reorgWhileOnBlock = nextBlockMayBeReorged | ||
? !equalsBytes(nextBlockMayBeReorged.hash(), nextBlock.hash()) | ||
: true | ||
} | ||
} | ||
// if there was no reorg, update head | ||
if (!reorgWhileOnBlock) { | ||
this._heads[name] = nextBlock.hash() | ||
lastBlock = nextBlock | ||
nextBlockNumber++ | ||
} | ||
// Successful execution of onBlock, move the head pointer | ||
blocksRanCounter++ | ||
} catch (error: any) { | ||
if ((error.message as string).includes('not found in DB')) { | ||
break | ||
} else { | ||
throw error | ||
} | ||
} | ||
nextBlockNumber++ | ||
blocksRanCounter++ | ||
} catch (error: any) { | ||
if (error.code === 'LEVEL_NOT_FOUND') { | ||
break | ||
} else { | ||
throw error | ||
} | ||
} | ||
return blocksRanCounter | ||
} finally { | ||
await this._saveHeads() | ||
} | ||
await this._saveHeads() | ||
return blocksRanCounter | ||
}) | ||
@@ -1065,3 +1081,3 @@ } | ||
*/ | ||
async setIteratorHead(tag: string, headHash: Buffer) { | ||
async setIteratorHead(tag: string, headHash: Uint8Array) { | ||
await this.runWithLock<void>(async () => { | ||
@@ -1096,3 +1112,3 @@ this._heads[tag] = headHash | ||
} | ||
while (!header.hash().equals(newHeader.hash()) && header.number > BigInt(0)) { | ||
while (!equalsBytes(header.hash(), newHeader.hash()) && header.number > BigInt(0)) { | ||
header = await this.getCanonicalHeader(header.number - BigInt(1)) | ||
@@ -1103,3 +1119,3 @@ ancestorHeaders.add(header) | ||
} | ||
if (!header.hash().equals(newHeader.hash())) { | ||
if (!equalsBytes(header.hash(), newHeader.hash())) { | ||
throw new Error('Failed to find ancient header') | ||
@@ -1128,6 +1144,6 @@ } | ||
blockNumber: bigint, | ||
headHash: Buffer, | ||
headHash: Uint8Array, | ||
ops: DBOp[] | ||
) { | ||
let hash: Buffer | false | ||
let hash: Uint8Array | false | ||
@@ -1143,3 +1159,3 @@ hash = await this.safeNumberToHash(blockNumber) | ||
for (const name of Object.keys(this._heads)) { | ||
if (this._heads[name].equals(hash)) { | ||
if (equalsBytes(this._heads[name], hash)) { | ||
this._heads[name] = headHash | ||
@@ -1149,10 +1165,10 @@ } | ||
// reset stale headHeader to current canonical | ||
if (this._headHeaderHash !== undefined && equalsBytes(this._headHeaderHash, hash) === true) { | ||
this._headHeaderHash = headHash | ||
} | ||
// reset stale headBlock to current canonical | ||
if (this._headBlockHash?.equals(hash) === true) { | ||
if (this._headBlockHash !== undefined && equalsBytes(this._headBlockHash, hash) === true) { | ||
this._headBlockHash = headHash | ||
} | ||
// reset stale headBlock to current canonical | ||
if (this._headHeaderHash?.equals(hash) === true) { | ||
this._headHeaderHash = headHash | ||
} | ||
@@ -1182,7 +1198,7 @@ blockNumber++ | ||
let currentNumber = header.number | ||
let currentCanonicalHash: Buffer = header.hash() | ||
let currentCanonicalHash: Uint8Array = header.hash() | ||
// track the staleHash: this is the hash currently in the DB which matches | ||
// the block number of the provided header. | ||
let staleHash: Buffer | false = false | ||
let staleHash: Uint8Array | false = false | ||
let staleHeads: string[] = [] | ||
@@ -1194,3 +1210,3 @@ let staleHeadBlock = false | ||
currentCanonicalHash = header.hash() | ||
return staleHash === false || !currentCanonicalHash.equals(staleHash) | ||
return staleHash === false || !equalsBytes(currentCanonicalHash, staleHash) | ||
} | ||
@@ -1214,3 +1230,3 @@ | ||
for (const name of Object.keys(this._heads)) { | ||
if (staleHash && this._heads[name].equals(staleHash)) { | ||
if (staleHash && equalsBytes(this._heads[name], staleHash)) { | ||
staleHeads.push(name) | ||
@@ -1220,13 +1236,13 @@ } | ||
// flag stale headBlock for reset | ||
if (staleHash && this._headBlockHash?.equals(staleHash) === true) { | ||
if ( | ||
staleHash && | ||
this._headBlockHash !== undefined && | ||
equalsBytes(this._headBlockHash, staleHash) === true | ||
) { | ||
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 | ||
@@ -1254,4 +1270,10 @@ } | ||
private _saveHeadOps(): DBOp[] { | ||
// Convert DB heads to hex strings for efficient storage in DB | ||
// LevelDB doesn't handle Uint8Arrays properly when they are part | ||
// of a JSON object being stored as a value in the DB | ||
const hexHeads = Object.fromEntries( | ||
Object.entries(this._heads).map((entry) => [entry[0], bytesToUnprefixedHex(entry[1])]) | ||
) | ||
return [ | ||
DBOp.set(DBTarget.Heads, this._heads), | ||
DBOp.set(DBTarget.Heads, hexHeads), | ||
DBOp.set(DBTarget.HeadHeader, this._headHeaderHash!), | ||
@@ -1277,5 +1299,6 @@ DBOp.set(DBTarget.HeadBlock, this._headBlockHash!), | ||
*/ | ||
private async _getHeader(hash: Buffer, number?: bigint) { | ||
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 ${bytesToHex(hash)} found in DB`) | ||
} | ||
@@ -1286,7 +1309,11 @@ return this.dbManager.getHeader(hash, number) | ||
async checkAndTransitionHardForkByNumber( | ||
number: bigint, | ||
number: BigIntLike, | ||
td?: BigIntLike, | ||
timestamp?: BigIntLike | ||
): Promise<void> { | ||
this._common.setHardforkByBlockNumber(number, td, timestamp) | ||
this.common.setHardforkBy({ | ||
blockNumber: number, | ||
td, | ||
timestamp, | ||
}) | ||
@@ -1297,3 +1324,3 @@ // If custom consensus algorithm is used, skip merge hardfork consensus checks | ||
switch (this._common.consensusAlgorithm()) { | ||
switch (this.common.consensusAlgorithm()) { | ||
case ConsensusAlgorithm.Casper: | ||
@@ -1315,3 +1342,3 @@ if (!(this.consensus instanceof CasperConsensus)) { | ||
default: | ||
throw new Error(`consensus algorithm ${this._common.consensusAlgorithm()} not supported`) | ||
throw new Error(`consensus algorithm ${this.common.consensusAlgorithm()} not supported`) | ||
} | ||
@@ -1327,2 +1354,5 @@ await this.consensus.setup({ blockchain: this }) | ||
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) | ||
@@ -1332,17 +1362,10 @@ } | ||
/** | ||
* This method either returns a Buffer if there exists one in the DB or if it | ||
* does not exist (DB throws a `NotFoundError`) then return false If DB throws | ||
* This method either returns a Uint8Array if there exists one in the DB or if it | ||
* does not exist then return false If DB throws | ||
* any other error, this function throws. | ||
* @param number | ||
*/ | ||
async safeNumberToHash(number: bigint): Promise<Buffer | false> { | ||
try { | ||
const hash = await this.dbManager.numberToHash(number) | ||
return hash | ||
} catch (error: any) { | ||
if (error.code !== 'LEVEL_NOT_FOUND') { | ||
throw error | ||
} | ||
return false | ||
} | ||
async safeNumberToHash(number: bigint): Promise<Uint8Array | false> { | ||
const hash = await this.dbManager.numberToHash(number) | ||
return hash !== undefined ? hash : false | ||
} | ||
@@ -1362,9 +1385,9 @@ | ||
*/ | ||
createGenesisBlock(stateRoot: Buffer): Block { | ||
const common = this._common.copy() | ||
common.setHardforkByBlockNumber( | ||
0, | ||
BigInt(common.genesis().difficulty), | ||
common.genesis().timestamp | ||
) | ||
createGenesisBlock(stateRoot: Uint8Array): Block { | ||
const common = this.common.copy() | ||
common.setHardforkBy({ | ||
blockNumber: 0, | ||
td: BigInt(common.genesis().difficulty), | ||
timestamp: common.genesis().timestamp, | ||
}) | ||
@@ -1376,3 +1399,2 @@ const header: BlockData['header'] = { | ||
withdrawalsRoot: common.isActivatedEIP(4895) ? KECCAK256_RLP : undefined, | ||
excessDataGas: common.isActivatedEIP(4844) ? BigInt(0) : undefined, | ||
} | ||
@@ -1385,3 +1407,3 @@ if (common.consensusType() === 'poa') { | ||
// Add required extraData (32 bytes vanity + 65 bytes filled with zeroes | ||
header.extraData = Buffer.concat([Buffer.alloc(32), Buffer.alloc(65).fill(0)]) | ||
header.extraData = concatBytes(new Uint8Array(32), new Uint8Array(65)) | ||
} | ||
@@ -1394,29 +1416,10 @@ } | ||
} | ||
} | ||
/** | ||
* Returns the genesis state of the blockchain. | ||
* All values are provided as hex-prefixed strings. | ||
*/ | ||
genesisState(): GenesisState { | ||
if (this._customGenesisState) { | ||
return this._customGenesisState | ||
} | ||
// Use require statements here in favor of import statements | ||
// to load json files on demand | ||
// (high memory usage by large mainnet.json genesis state file) | ||
switch (this._common.chainName()) { | ||
case 'mainnet': | ||
return require('./genesisStates/mainnet.json') | ||
case 'ropsten': | ||
return require('./genesisStates/ropsten.json') | ||
case 'rinkeby': | ||
return require('./genesisStates/rinkeby.json') | ||
case 'goerli': | ||
return require('./genesisStates/goerli.json') | ||
case 'sepolia': | ||
return require('./genesisStates/sepolia.json') | ||
} | ||
return {} | ||
} | ||
/** | ||
* Returns the genesis state root if chain is well known or an empty state's root otherwise | ||
*/ | ||
async function getGenesisStateRoot(chainId: Chain): Promise<Uint8Array> { | ||
const chainGenesis = ChainGenesis[chainId] | ||
return chainGenesis !== undefined ? chainGenesis.stateRoot : genGenesisStateRoot({}) | ||
} |
import { ConsensusAlgorithm } from '@ethereumjs/common' | ||
import type { Consensus } from './interface' | ||
import type { Consensus } from '../types.js' | ||
import type { BlockHeader } from '@ethereumjs/block' | ||
@@ -5,0 +5,0 @@ |
import { ConsensusAlgorithm } from '@ethereumjs/common' | ||
import { RLP } from '@ethereumjs/rlp' | ||
import { Address, arrToBufArr, bigIntToBuffer, bufArrToArr, bufferToBigInt } from '@ethereumjs/util' | ||
import { debug as createDebugLogger } from 'debug' | ||
import { | ||
Address, | ||
TypeOutput, | ||
bigIntToBytes, | ||
bytesToBigInt, | ||
equalsBytes, | ||
hexToBytes, | ||
toType, | ||
} from '@ethereumjs/util' | ||
import debugDefault from 'debug' | ||
import type { Blockchain } from '..' | ||
import type { Consensus, ConsensusOptions } from './interface' | ||
import type { Blockchain } from '../index.js' | ||
import type { Consensus, ConsensusOptions } from '../types.js' | ||
import type { Block, BlockHeader } from '@ethereumjs/block' | ||
import type { CliqueConfig } from '@ethereumjs/common' | ||
const { debug: createDebugLogger } = debugDefault | ||
@@ -14,5 +23,5 @@ const debug = createDebugLogger('blockchain:clique') | ||
// Magic nonce number to vote on adding a new signer | ||
export const CLIQUE_NONCE_AUTH = Buffer.from('ffffffffffffffff', 'hex') | ||
export const CLIQUE_NONCE_AUTH = hexToBytes('0xffffffffffffffff') | ||
// Magic nonce number to vote on removing a signer. | ||
export const CLIQUE_NONCE_DROP = Buffer.alloc(8) | ||
export const CLIQUE_NONCE_DROP = new Uint8Array(8) | ||
@@ -28,7 +37,2 @@ const CLIQUE_SIGNERS_KEY = 'CliqueSigners' | ||
const DB_OPTS = { | ||
keyEncoding: 'buffer', | ||
valueEncoding: 'buffer', | ||
} | ||
// Clique Signer State | ||
@@ -41,3 +45,3 @@ type CliqueSignerState = [blockNumber: bigint, signers: Address[]] | ||
blockNumber: bigint, | ||
vote: [signer: Address, beneficiary: Address, cliqueNonce: Buffer] | ||
vote: [signer: Address, beneficiary: Address, cliqueNonce: Uint8Array] | ||
] | ||
@@ -52,2 +56,6 @@ type CliqueLatestVotes = CliqueVote[] | ||
* This class encapsulates Clique-related consensus functionality when used with the Blockchain class. | ||
* Note: reorgs which happen between epoch transitions, which change the internal voting state over the reorg | ||
* will result in failure and is currently not supported. | ||
* The hotfix for this could be: re-load the latest epoch block (this has the clique state in the extraData of the header) | ||
* Now replay all blocks on top of it. This should validate the chain up to the new/reorged tip which previously threw. | ||
*/ | ||
@@ -64,3 +72,3 @@ export class CliqueConsensus implements Consensus { | ||
*/ | ||
private CLIQUE_SIGNER_HISTORY_BLOCK_LIMIT = 100 | ||
private CLIQUE_SIGNER_HISTORY_BLOCK_LIMIT = 200 | ||
@@ -119,2 +127,3 @@ /** | ||
this._cliqueLatestSignerStates = await this.getCliqueLatestSignerStates() | ||
this._cliqueLatestSignerStates.sort((a, b) => (a[0] > b[0] ? 1 : -1)) | ||
this._cliqueLatestVotes = await this.getCliqueLatestVotes() | ||
@@ -134,3 +143,3 @@ this._cliqueLatestBlockSigners = await this.getCliqueLatestBlockSigners() | ||
const { header } = block | ||
const valid = header.cliqueVerifySignature(this.cliqueActiveSigners()) | ||
const valid = header.cliqueVerifySignature(this.cliqueActiveSigners(header.number)) | ||
if (!valid) { | ||
@@ -149,3 +158,3 @@ throw new Error('invalid PoA block signature (clique)') | ||
const checkpointSigners = header.cliqueEpochTransitionSigners() | ||
const activeSigners = this.cliqueActiveSigners() | ||
const activeSigners = this.cliqueActiveSigners(header.number) | ||
for (const [i, cSigner] of checkpointSigners.entries()) { | ||
@@ -171,3 +180,3 @@ if (activeSigners[i]?.equals(cSigner) !== true) { | ||
const signers = this.cliqueActiveSigners() | ||
const signers = this.cliqueActiveSigners(header.number) | ||
if (signers.length === 0) { | ||
@@ -226,3 +235,13 @@ // abort if signers are unavailable | ||
if (signerState) { | ||
const blockNumber = signerState[0] | ||
const known = this._cliqueLatestSignerStates.find((value) => { | ||
if (value[0] === blockNumber) { | ||
return true | ||
} | ||
}) | ||
if (known !== undefined) { | ||
return | ||
} | ||
this._cliqueLatestSignerStates.push(signerState) | ||
this._cliqueLatestSignerStates.sort((a, b) => (a[0] > b[0] ? 1 : -1)) | ||
} | ||
@@ -247,15 +266,16 @@ | ||
const formatted = this._cliqueLatestSignerStates.map((state) => [ | ||
bigIntToBuffer(state[0]), | ||
state[1].map((a) => a.toBuffer()), | ||
bigIntToBytes(state[0]), | ||
state[1].map((a) => a.toBytes()), | ||
]) | ||
await this.blockchain!.db.put( | ||
CLIQUE_SIGNERS_KEY, | ||
Buffer.from(RLP.encode(bufArrToArr(formatted))), | ||
DB_OPTS | ||
) | ||
await this.blockchain!.db.put(CLIQUE_SIGNERS_KEY, RLP.encode(formatted)) | ||
// Output active signers for debugging purposes | ||
let i = 0 | ||
for (const signer of this.cliqueActiveSigners()) { | ||
debug(`Clique signer [${i}]: ${signer}`) | ||
i++ | ||
if (signerState !== undefined) { | ||
let i = 0 | ||
try { | ||
for (const signer of this.cliqueActiveSigners(signerState[0])) { | ||
debug(`Clique signer [${i}]: ${signer} (block: ${signerState[0]})`) | ||
i++ | ||
} | ||
// eslint-disable-next-line no-empty | ||
} catch (e) {} | ||
} | ||
@@ -284,5 +304,5 @@ } | ||
(header.number % | ||
BigInt((this.blockchain!._common.consensusConfig() as CliqueConfig).epoch)) | ||
const limit = this.cliqueSignerLimit() | ||
let activeSigners = this.cliqueActiveSigners() | ||
BigInt((this.blockchain!.common.consensusConfig() as CliqueConfig).epoch)) | ||
const limit = this.cliqueSignerLimit(header.number) | ||
let activeSigners = [...this.cliqueActiveSigners(header.number)] | ||
let consensus = false | ||
@@ -296,3 +316,3 @@ | ||
vote[1][1].equals(beneficiary) && | ||
vote[1][2].equals(CLIQUE_NONCE_AUTH) | ||
equalsBytes(vote[1][2], CLIQUE_NONCE_AUTH) | ||
) | ||
@@ -310,3 +330,3 @@ }) | ||
let numBeneficiaryVotesAUTH = beneficiaryVotesAUTH.length | ||
if (round === 2 && nonce.equals(CLIQUE_NONCE_AUTH)) { | ||
if (round === 2 && equalsBytes(nonce, CLIQUE_NONCE_AUTH)) { | ||
numBeneficiaryVotesAUTH += 1 | ||
@@ -320,4 +340,10 @@ } | ||
activeSigners.sort((a, b) => { | ||
// Sort by buffer size | ||
return a.toBuffer().compare(b.toBuffer()) | ||
// Sort by array size | ||
const result = | ||
toType(a.toString(), TypeOutput.BigInt) < toType(b.toString(), TypeOutput.BigInt) | ||
if (result) { | ||
return -1 | ||
} else { | ||
return 1 | ||
} | ||
}) | ||
@@ -336,3 +362,3 @@ // Discard votes for added signer | ||
vote[1][1].equals(beneficiary) && | ||
vote[1][2].equals(CLIQUE_NONCE_DROP) | ||
equalsBytes(vote[1][2], CLIQUE_NONCE_DROP) | ||
) | ||
@@ -351,3 +377,3 @@ }) | ||
if (round === 2 && nonce.equals(CLIQUE_NONCE_DROP)) { | ||
if (round === 2 && equalsBytes(nonce, CLIQUE_NONCE_DROP)) { | ||
numBeneficiaryVotesDROP += 1 | ||
@@ -372,3 +398,3 @@ } | ||
`[Block ${header.number}] New clique vote: ${signer} -> ${beneficiary} ${ | ||
nonce.equals(CLIQUE_NONCE_AUTH) ? 'AUTH' : 'DROP' | ||
equalsBytes(nonce, CLIQUE_NONCE_AUTH) ? 'AUTH' : 'DROP' | ||
}` | ||
@@ -402,3 +428,3 @@ ) | ||
(lastBlockNumber % | ||
BigInt((this.blockchain!._common.consensusConfig() as CliqueConfig).epoch)) | ||
BigInt((this.blockchain!.common.consensusConfig() as CliqueConfig).epoch)) | ||
const blockLimit = lastEpochBlockNumber - BigInt(limit) | ||
@@ -410,10 +436,6 @@ this._cliqueLatestVotes = this._cliqueLatestVotes.filter((state) => state[0] >= blockLimit) | ||
const formatted = this._cliqueLatestVotes.map((v) => [ | ||
bigIntToBuffer(v[0]), | ||
[v[1][0].toBuffer(), v[1][1].toBuffer(), v[1][2]], | ||
bigIntToBytes(v[0]), | ||
[v[1][0].toBytes(), v[1][1].toBytes(), v[1][2]], | ||
]) | ||
await this.blockchain!.db.put( | ||
CLIQUE_VOTES_KEY, | ||
Buffer.from(RLP.encode(bufArrToArr(formatted))), | ||
DB_OPTS | ||
) | ||
await this.blockchain!.db.put(CLIQUE_VOTES_KEY, RLP.encode(formatted)) | ||
} | ||
@@ -424,3 +446,3 @@ | ||
*/ | ||
cliqueActiveSigners(): Address[] { | ||
cliqueActiveSigners(blockNum: bigint): Address[] { | ||
const signers = this._cliqueLatestSignerStates | ||
@@ -430,3 +452,8 @@ if (signers.length === 0) { | ||
} | ||
return [...signers[signers.length - 1][1]] | ||
for (let i = signers.length - 1; i >= 0; i--) { | ||
if (signers[i][0] < blockNum) { | ||
return signers[i][1] | ||
} | ||
} | ||
throw new Error(`Could not load signers for block ${blockNum}`) | ||
} | ||
@@ -441,4 +468,4 @@ | ||
*/ | ||
private cliqueSignerLimit() { | ||
return Math.floor(this.cliqueActiveSigners().length / 2) + 1 | ||
private cliqueSignerLimit(blockNum: bigint) { | ||
return Math.floor(this.cliqueActiveSigners(blockNum).length / 2) + 1 | ||
} | ||
@@ -457,3 +484,3 @@ | ||
} | ||
const limit = this.cliqueSignerLimit() | ||
const limit = this.cliqueSignerLimit(header.number) | ||
// construct recent block signers list with this block | ||
@@ -512,3 +539,3 @@ let signers = this._cliqueLatestBlockSigners | ||
const length = this._cliqueLatestBlockSigners.length | ||
const limit = this.cliqueSignerLimit() | ||
const limit = this.cliqueSignerLimit(header.number) | ||
if (length > limit) { | ||
@@ -524,10 +551,6 @@ this._cliqueLatestBlockSigners = this._cliqueLatestBlockSigners.slice( | ||
const formatted = this._cliqueLatestBlockSigners.map((b) => [ | ||
bigIntToBuffer(b[0]), | ||
b[1].toBuffer(), | ||
bigIntToBytes(b[0]), | ||
b[1].toBytes(), | ||
]) | ||
await this.blockchain!.db.put( | ||
CLIQUE_BLOCK_SIGNERS_SNAPSHOT_KEY, | ||
Buffer.from(RLP.encode(bufArrToArr(formatted))), | ||
DB_OPTS | ||
) | ||
await this.blockchain!.db.put(CLIQUE_BLOCK_SIGNERS_SNAPSHOT_KEY, RLP.encode(formatted)) | ||
} | ||
@@ -540,19 +563,10 @@ | ||
private async getCliqueLatestSignerStates(): Promise<CliqueLatestSignerStates> { | ||
try { | ||
const signerStates = await this.blockchain!.db.get<string, Buffer>( | ||
CLIQUE_SIGNERS_KEY, | ||
DB_OPTS | ||
) | ||
const states = arrToBufArr(RLP.decode(Uint8Array.from(signerStates))) as [Buffer, Buffer[]] | ||
return states.map((state) => { | ||
const blockNum = bufferToBigInt(state[0] as Buffer) | ||
const addrs = (<any>state[1]).map((buf: Buffer) => new Address(buf)) | ||
return [blockNum, addrs] | ||
}) as CliqueLatestSignerStates | ||
} catch (error: any) { | ||
if (error.code === 'LEVEL_NOT_FOUND') { | ||
return [] | ||
} | ||
throw error | ||
} | ||
const signerStates = await this.blockchain!.db.get(CLIQUE_SIGNERS_KEY) | ||
if (signerStates === undefined) return [] | ||
const states = RLP.decode(signerStates as Uint8Array) as [Uint8Array, Uint8Array[]] | ||
return states.map((state) => { | ||
const blockNum = bytesToBigInt(state[0] as Uint8Array) | ||
const addrs = (<any>state[1]).map((bytes: Uint8Array) => new Address(bytes)) | ||
return [blockNum, addrs] | ||
}) as CliqueLatestSignerStates | ||
} | ||
@@ -565,21 +579,15 @@ | ||
private async getCliqueLatestVotes(): Promise<CliqueLatestVotes> { | ||
try { | ||
const signerVotes = await this.blockchain!.db.get<string, Buffer>(CLIQUE_VOTES_KEY, DB_OPTS) | ||
const votes = arrToBufArr(RLP.decode(Uint8Array.from(signerVotes))) as [ | ||
Buffer, | ||
[Buffer, Buffer, Buffer] | ||
] | ||
return votes.map((vote) => { | ||
const blockNum = bufferToBigInt(vote[0] as Buffer) | ||
const signer = new Address((vote[1] as any)[0]) | ||
const beneficiary = new Address((vote[1] as any)[1]) | ||
const nonce = (vote[1] as any)[2] | ||
return [blockNum, [signer, beneficiary, nonce]] | ||
}) as CliqueLatestVotes | ||
} catch (error: any) { | ||
if (error.code === 'LEVEL_NOT_FOUND') { | ||
return [] | ||
} | ||
throw error | ||
} | ||
const signerVotes = await this.blockchain!.db.get(CLIQUE_VOTES_KEY) | ||
if (signerVotes === undefined) return [] | ||
const votes = RLP.decode(signerVotes as Uint8Array) as [ | ||
Uint8Array, | ||
[Uint8Array, Uint8Array, Uint8Array] | ||
] | ||
return votes.map((vote) => { | ||
const blockNum = bytesToBigInt(vote[0] as Uint8Array) | ||
const signer = new Address((vote[1] as any)[0]) | ||
const beneficiary = new Address((vote[1] as any)[1]) | ||
const nonce = (vote[1] as any)[2] | ||
return [blockNum, [signer, beneficiary, nonce]] | ||
}) as CliqueLatestVotes | ||
} | ||
@@ -592,19 +600,10 @@ | ||
private async getCliqueLatestBlockSigners(): Promise<CliqueLatestBlockSigners> { | ||
try { | ||
const blockSigners = await this.blockchain!.db.get<string, Buffer>( | ||
CLIQUE_BLOCK_SIGNERS_SNAPSHOT_KEY, | ||
DB_OPTS | ||
) | ||
const signers = arrToBufArr(RLP.decode(Uint8Array.from(blockSigners))) as [Buffer, Buffer][] | ||
return signers.map((s) => { | ||
const blockNum = bufferToBigInt(s[0] as Buffer) | ||
const signer = new Address(s[1] as any) | ||
return [blockNum, signer] | ||
}) as CliqueLatestBlockSigners | ||
} catch (error: any) { | ||
if (error.code === 'LEVEL_NOT_FOUND') { | ||
return [] | ||
} | ||
throw error | ||
} | ||
const blockSigners = await this.blockchain!.db.get(CLIQUE_BLOCK_SIGNERS_SNAPSHOT_KEY) | ||
if (blockSigners === undefined) return [] | ||
const signers = RLP.decode(blockSigners as Uint8Array) as [Uint8Array, Uint8Array][] | ||
return signers.map((s) => { | ||
const blockNum = bytesToBigInt(s[0] as Uint8Array) | ||
const signer = new Address(s[1]) | ||
return [blockNum, signer] | ||
}) as CliqueLatestBlockSigners | ||
} | ||
@@ -628,4 +627,4 @@ | ||
*/ | ||
async cliqueSignerInTurn(signer: Address): Promise<boolean> { | ||
const signers = this.cliqueActiveSigners() | ||
async cliqueSignerInTurn(signer: Address, blockNum: bigint): Promise<boolean> { | ||
const signers = this.cliqueActiveSigners(blockNum) | ||
const signerIndex = signers.findIndex((address) => address.equals(signer)) | ||
@@ -632,0 +631,0 @@ if (signerIndex === -1) { |
import { ConsensusAlgorithm } from '@ethereumjs/common' | ||
import { Ethash } from '@ethereumjs/ethash' | ||
import type { Blockchain } from '..' | ||
import type { Consensus, ConsensusOptions } from './interface' | ||
import type { Blockchain } from '../index.js' | ||
import type { Consensus, ConsensusOptions } from '../types.js' | ||
import type { Block, BlockHeader } from '@ethereumjs/block' | ||
import type { EthashCacheDB } from '@ethereumjs/ethash' | ||
@@ -48,5 +47,5 @@ /** | ||
this.blockchain = blockchain | ||
this._ethash = new Ethash(this.blockchain.db as unknown as EthashCacheDB) | ||
this._ethash = new Ethash(this.blockchain.db as any) | ||
} | ||
public async newBlock(): Promise<void> {} | ||
} |
@@ -1,6 +0,5 @@ | ||
import { CasperConsensus } from './casper' | ||
import { CliqueConsensus } from './clique' | ||
import { EthashConsensus } from './ethash' | ||
import { Consensus } from './interface' | ||
import { CasperConsensus } from './casper.js' | ||
import { CliqueConsensus } from './clique.js' | ||
import { EthashConsensus } from './ethash.js' | ||
export { CasperConsensus, CliqueConsensus, Consensus, EthashConsensus } | ||
export { CasperConsensus, CliqueConsensus, EthashConsensus } |
@@ -1,34 +0,36 @@ | ||
import * as LRUCache from 'lru-cache' | ||
import { bytesToUnprefixedHex } from '@ethereumjs/util' | ||
import { LRUCache } from 'lru-cache' | ||
/** | ||
* Simple LRU Cache that allows for keys of type Buffer | ||
* Simple LRU Cache that allows for keys of type Uint8Array | ||
* @hidden | ||
*/ | ||
export class Cache<V> { | ||
_cache: LRUCache<string, V> | ||
_cache: LRUCache<string, { value: V }, void> | ||
constructor(opts: LRUCache.Options<string, V>) { | ||
constructor(opts: LRUCache.Options<string, { value: V }, void>) { | ||
this._cache = new LRUCache(opts) | ||
} | ||
set(key: string | Buffer, value: V): void { | ||
if (key instanceof Buffer) { | ||
key = key.toString('hex') | ||
set(key: string | Uint8Array, value: V): void { | ||
if (key instanceof Uint8Array) { | ||
key = bytesToUnprefixedHex(key) | ||
} | ||
this._cache.set(key, value) | ||
this._cache.set(key, { value }) | ||
} | ||
get(key: string | Buffer): V | undefined { | ||
if (key instanceof Buffer) { | ||
key = key.toString('hex') | ||
get(key: string | Uint8Array): V | undefined { | ||
if (key instanceof Uint8Array) { | ||
key = bytesToUnprefixedHex(key) | ||
} | ||
return this._cache.get(key) | ||
const elem = this._cache.get(key) | ||
return elem !== undefined ? elem.value : undefined | ||
} | ||
del(key: string | Buffer): void { | ||
if (key instanceof Buffer) { | ||
key = key.toString('hex') | ||
del(key: string | Uint8Array): void { | ||
if (key instanceof Uint8Array) { | ||
key = bytesToUnprefixedHex(key) | ||
} | ||
this._cache.del(key) | ||
this._cache.delete(key) | ||
} | ||
} |
@@ -1,2 +0,2 @@ | ||
import { bigIntToBuffer } from '@ethereumjs/util' | ||
import { bigIntToBytes, concatBytes, utf8ToBytes } from '@ethereumjs/util' | ||
@@ -20,3 +20,3 @@ // Geth compatible DB keys | ||
*/ | ||
const HEADER_PREFIX = Buffer.from('h') | ||
const HEADER_PREFIX = utf8ToBytes('h') | ||
@@ -26,3 +26,3 @@ /** | ||
*/ | ||
const TD_SUFFIX = Buffer.from('t') | ||
const TD_SUFFIX = utf8ToBytes('t') | ||
@@ -32,3 +32,3 @@ /** | ||
*/ | ||
const NUM_SUFFIX = Buffer.from('n') | ||
const NUM_SUFFIX = utf8ToBytes('n') | ||
@@ -38,3 +38,3 @@ /** | ||
*/ | ||
const BLOCK_HASH_PEFIX = Buffer.from('H') | ||
const BLOCK_HASH_PEFIX = utf8ToBytes('H') | ||
@@ -44,3 +44,3 @@ /** | ||
*/ | ||
const BODY_PREFIX = Buffer.from('b') | ||
const BODY_PREFIX = utf8ToBytes('b') | ||
@@ -50,16 +50,16 @@ // Utility functions | ||
/** | ||
* Convert bigint to big endian Buffer | ||
* Convert bigint to big endian Uint8Array | ||
*/ | ||
const bufBE8 = (n: bigint) => bigIntToBuffer(BigInt.asUintN(64, n)) | ||
const bytesBE8 = (n: bigint) => bigIntToBytes(BigInt.asUintN(64, n)) | ||
const tdKey = (n: bigint, hash: Buffer) => | ||
Buffer.concat([HEADER_PREFIX, bufBE8(n), hash, TD_SUFFIX]) | ||
const tdKey = (n: bigint, hash: Uint8Array) => | ||
concatBytes(HEADER_PREFIX, bytesBE8(n), hash, TD_SUFFIX) | ||
const headerKey = (n: bigint, hash: Buffer) => Buffer.concat([HEADER_PREFIX, bufBE8(n), hash]) | ||
const headerKey = (n: bigint, hash: Uint8Array) => concatBytes(HEADER_PREFIX, bytesBE8(n), hash) | ||
const bodyKey = (n: bigint, hash: Buffer) => Buffer.concat([BODY_PREFIX, bufBE8(n), hash]) | ||
const bodyKey = (n: bigint, hash: Uint8Array) => concatBytes(BODY_PREFIX, bytesBE8(n), hash) | ||
const numberToHashKey = (n: bigint) => Buffer.concat([HEADER_PREFIX, bufBE8(n), NUM_SUFFIX]) | ||
const numberToHashKey = (n: bigint) => concatBytes(HEADER_PREFIX, bytesBE8(n), NUM_SUFFIX) | ||
const hashToNumberKey = (hash: Buffer) => Buffer.concat([BLOCK_HASH_PEFIX, hash]) | ||
const hashToNumberKey = (hash: Uint8Array) => concatBytes(BLOCK_HASH_PEFIX, hash) | ||
@@ -71,3 +71,3 @@ /** | ||
bodyKey, | ||
bufBE8, | ||
bytesBE8, | ||
hashToNumberKey, | ||
@@ -74,0 +74,0 @@ HEAD_BLOCK_KEY, |
import { Block } from '@ethereumjs/block' | ||
import { RLP } from '@ethereumjs/rlp' | ||
import { bufArrToArr } from '@ethereumjs/util' | ||
import { bufBE8 } from './constants' | ||
import { DBOp, DBTarget } from './operation' | ||
import { bytesBE8 } from './constants.js' | ||
import { DBOp, DBTarget } from './operation.js' | ||
@@ -15,4 +14,4 @@ import type { BlockHeader } from '@ethereumjs/block' | ||
function DBSetTD(TD: bigint, blockNumber: bigint, blockHash: Buffer): DBOp { | ||
return DBOp.set(DBTarget.TotalDifficulty, Buffer.from(RLP.encode(TD)), { | ||
function DBSetTD(TD: bigint, blockNumber: bigint, blockHash: Uint8Array): DBOp { | ||
return DBOp.set(DBTarget.TotalDifficulty, RLP.encode(TD), { | ||
blockNumber, | ||
@@ -54,3 +53,3 @@ blockHash, | ||
) { | ||
const bodyValue = Buffer.from(RLP.encode(bufArrToArr(blockBody.raw()).slice(1))) | ||
const bodyValue = RLP.encode(blockBody.raw().slice(1)) | ||
dbOps.push( | ||
@@ -67,4 +66,4 @@ DBOp.set(DBTarget.Body, bodyValue, { | ||
function DBSetHashToNumber(blockHash: Buffer, blockNumber: bigint): DBOp { | ||
const blockNumber8Byte = bufBE8(blockNumber) | ||
function DBSetHashToNumber(blockHash: Uint8Array, blockNumber: bigint): DBOp { | ||
const blockNumber8Byte = bytesBE8(blockNumber) | ||
return DBOp.set(DBTarget.HashToNumber, blockNumber8Byte, { | ||
@@ -75,3 +74,3 @@ blockHash, | ||
function DBSaveLookups(blockHash: Buffer, blockNumber: bigint, skipNumIndex?: boolean): DBOp[] { | ||
function DBSaveLookups(blockHash: Uint8Array, blockNumber: bigint, skipNumIndex?: boolean): DBOp[] { | ||
const ops = [] | ||
@@ -82,3 +81,3 @@ if (skipNumIndex !== true) { | ||
const blockNumber8Bytes = bufBE8(blockNumber) | ||
const blockNumber8Bytes = bytesBE8(blockNumber) | ||
ops.push( | ||
@@ -85,0 +84,0 @@ DBOp.set(DBTarget.HashToNumber, blockNumber8Bytes, { |
import { Block, BlockHeader, valuesArrayToHeaderData } from '@ethereumjs/block' | ||
import { RLP } from '@ethereumjs/rlp' | ||
import { KECCAK256_RLP, KECCAK256_RLP_ARRAY, arrToBufArr, bufferToBigInt } from '@ethereumjs/util' | ||
import { | ||
KECCAK256_RLP, | ||
KECCAK256_RLP_ARRAY, | ||
bytesToBigInt, | ||
bytesToHex, | ||
equalsBytes, | ||
unprefixedHexToBytes, | ||
} from '@ethereumjs/util' | ||
import { Cache } from './cache' | ||
import { DBOp, DBTarget } from './operation' | ||
import { Cache } from './cache.js' | ||
import { DBOp, DBTarget } from './operation.js' | ||
import type { DBOpData, DatabaseKey } from './operation' | ||
import type { BlockBodyBuffer, BlockBuffer, BlockOptions } from '@ethereumjs/block' | ||
import type { DatabaseKey } from './operation.js' | ||
import type { BlockBodyBytes, BlockBytes, BlockOptions } from '@ethereumjs/block' | ||
import type { Common } from '@ethereumjs/common' | ||
import type { AbstractLevel } from 'abstract-level' | ||
import type { BatchDBOp, DB, DBObject, DelBatch, PutBatch } from '@ethereumjs/util' | ||
class NotFoundError extends Error { | ||
public code: string = 'LEVEL_NOT_FOUND' | ||
constructor(blockNumber: bigint) { | ||
super(`Key ${blockNumber.toString()} was not found`) | ||
// `Error.captureStackTrace` is not defined in some browser contexts | ||
if (typeof Error.captureStackTrace !== 'undefined') { | ||
Error.captureStackTrace(this, this.constructor) | ||
} | ||
} | ||
} | ||
/** | ||
@@ -35,3 +29,3 @@ * @hidden | ||
export type CacheMap = { [key: string]: Cache<Buffer> } | ||
export type CacheMap = { [key: string]: Cache<Uint8Array> } | ||
@@ -45,11 +39,8 @@ /** | ||
private _cache: CacheMap | ||
private _common: Common | ||
private _db: AbstractLevel<string | Buffer | Uint8Array, string | Buffer, string | Buffer> | ||
public readonly common: Common | ||
private _db: DB<Uint8Array | string, Uint8Array | string | DBObject> | ||
constructor( | ||
db: AbstractLevel<string | Buffer | Uint8Array, string | Buffer, string | Buffer>, | ||
common: Common | ||
) { | ||
constructor(db: DB<Uint8Array | string, Uint8Array | string | DBObject>, common: Common) { | ||
this._db = db | ||
this._common = common | ||
this.common = common | ||
this._cache = { | ||
@@ -67,8 +58,13 @@ td: new Cache({ max: 1024 }), | ||
*/ | ||
async getHeads(): Promise<{ [key: string]: Buffer }> { | ||
const heads = await this.get(DBTarget.Heads) | ||
async getHeads(): Promise<{ [key: string]: Uint8Array }> { | ||
const heads = (await this.get(DBTarget.Heads)) as DBObject | ||
if (heads === undefined) return heads | ||
const decodedHeads: { [key: string]: Uint8Array } = {} | ||
for (const key of Object.keys(heads)) { | ||
heads[key] = Buffer.from(heads[key]) | ||
// Heads are stored in DB as hex strings since Level converts Uint8Arrays | ||
// to nested JSON objects when they are included in a value being stored | ||
// in the DB | ||
decodedHeads[key] = unprefixedHexToBytes(heads[key] as string) | ||
} | ||
return heads | ||
return decodedHeads | ||
} | ||
@@ -79,3 +75,3 @@ | ||
*/ | ||
async getHeadHeader(): Promise<Buffer> { | ||
async getHeadHeader(): Promise<Uint8Array | undefined> { | ||
return this.get(DBTarget.HeadHeader) | ||
@@ -87,3 +83,3 @@ } | ||
*/ | ||
async getHeadBlock(): Promise<Buffer> { | ||
async getHeadBlock(): Promise<Uint8Array | undefined> { | ||
return this.get(DBTarget.HeadBlock) | ||
@@ -96,3 +92,3 @@ } | ||
*/ | ||
async getBlock(blockId: Buffer | bigint | number): Promise<Block> { | ||
async getBlock(blockId: Uint8Array | bigint | number): Promise<Block | undefined> { | ||
if (typeof blockId === 'number' && Number.isInteger(blockId)) { | ||
@@ -104,3 +100,4 @@ blockId = BigInt(blockId) | ||
let hash | ||
if (Buffer.isBuffer(blockId)) { | ||
if (blockId === undefined) return undefined | ||
if (blockId instanceof Uint8Array) { | ||
hash = blockId | ||
@@ -115,35 +112,32 @@ number = await this.hashToNumber(blockId) | ||
if (hash === undefined || number === undefined) return undefined | ||
const header = await this.getHeader(hash, number) | ||
let body: BlockBodyBuffer | ||
try { | ||
body = await this.getBody(hash, number) | ||
} catch (error: any) { | ||
if (error.code !== 'LEVEL_NOT_FOUND') { | ||
throw error | ||
} | ||
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 ( | ||
!header.transactionsTrie.equals(KECCAK256_RLP) || | ||
!header.uncleHash.equals(KECCAK256_RLP_ARRAY) | ||
) { | ||
throw error | ||
if (!equalsBytes(header.transactionsTrie, KECCAK256_RLP)) { | ||
throw new Error('transactionsTrie root should be equal to hash of null') | ||
} | ||
body = [[], []] | ||
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 (!header.withdrawalsRoot.equals(KECCAK256_RLP)) { | ||
throw error | ||
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') | ||
} | ||
body.push([]) | ||
if (body.length !== 3) body.push([]) | ||
} | ||
} | ||
const blockData = [header.raw(), ...body] as BlockBuffer | ||
const opts: BlockOptions = { common: this._common } | ||
const blockData = [header.raw(), ...body] as BlockBytes | ||
const opts: BlockOptions = { common: this.common } | ||
if (number === BigInt(0)) { | ||
opts.hardforkByTTD = await this.getTotalDifficulty(hash, BigInt(0)) | ||
opts.setHardfork = await this.getTotalDifficulty(hash, BigInt(0)) | ||
} else { | ||
opts.hardforkByTTD = await this.getTotalDifficulty(header.parentHash, number - BigInt(1)) | ||
opts.setHardfork = await this.getTotalDifficulty(header.parentHash, number - BigInt(1)) | ||
} | ||
@@ -156,5 +150,8 @@ return Block.fromValuesArray(blockData, opts) | ||
*/ | ||
async getBody(blockHash: Buffer, blockNumber: bigint): Promise<BlockBodyBuffer> { | ||
async getBody(blockHash: Uint8Array, blockNumber: bigint): Promise<BlockBodyBytes> { | ||
const body = await this.get(DBTarget.Body, { blockHash, blockNumber }) | ||
return arrToBufArr(RLP.decode(Uint8Array.from(body))) as BlockBodyBuffer | ||
if (body === undefined) { | ||
return [[], []] | ||
} | ||
return RLP.decode(body) as BlockBodyBytes | ||
} | ||
@@ -165,17 +162,17 @@ | ||
*/ | ||
async getHeader(blockHash: Buffer, blockNumber: bigint) { | ||
async getHeader(blockHash: Uint8Array, blockNumber: bigint) { | ||
const encodedHeader = await this.get(DBTarget.Header, { blockHash, blockNumber }) | ||
const headerValues = arrToBufArr(RLP.decode(Uint8Array.from(encodedHeader))) | ||
const headerValues = RLP.decode(encodedHeader) | ||
const opts: BlockOptions = { common: this._common } | ||
const opts: BlockOptions = { common: this.common } | ||
if (blockNumber === BigInt(0)) { | ||
opts.hardforkByTTD = await this.getTotalDifficulty(blockHash, BigInt(0)) | ||
opts.setHardfork = await this.getTotalDifficulty(blockHash, BigInt(0)) | ||
} else { | ||
// Lets fetch the parent hash but not by number since this block might not | ||
// be in canonical chain | ||
const headerData = valuesArrayToHeaderData(headerValues as Buffer[]) | ||
const parentHash = headerData.parentHash as Buffer | ||
opts.hardforkByTTD = await this.getTotalDifficulty(parentHash, blockNumber - BigInt(1)) | ||
const headerData = valuesArrayToHeaderData(headerValues as Uint8Array[]) | ||
const parentHash = headerData.parentHash as Uint8Array | ||
opts.setHardfork = await this.getTotalDifficulty(parentHash, blockNumber - BigInt(1)) | ||
} | ||
return BlockHeader.fromValuesArray(headerValues as Buffer[], opts) | ||
return BlockHeader.fromValuesArray(headerValues as Uint8Array[], opts) | ||
} | ||
@@ -186,5 +183,5 @@ | ||
*/ | ||
async getTotalDifficulty(blockHash: Buffer, blockNumber: bigint): Promise<bigint> { | ||
async getTotalDifficulty(blockHash: Uint8Array, blockNumber: bigint): Promise<bigint> { | ||
const td = await this.get(DBTarget.TotalDifficulty, { blockHash, blockNumber }) | ||
return bufferToBigInt(Buffer.from(RLP.decode(Uint8Array.from(td)) as Uint8Array)) | ||
return bytesToBigInt(RLP.decode(td) as Uint8Array) | ||
} | ||
@@ -195,5 +192,8 @@ | ||
*/ | ||
async hashToNumber(blockHash: Buffer): Promise<bigint> { | ||
async hashToNumber(blockHash: Uint8Array): Promise<bigint | undefined> { | ||
const value = await this.get(DBTarget.HashToNumber, { blockHash }) | ||
return bufferToBigInt(value) | ||
if (value === undefined) { | ||
throw new Error(`value for ${bytesToHex(blockHash)} not found in DB`) | ||
} | ||
return value !== undefined ? bytesToBigInt(value) : undefined | ||
} | ||
@@ -204,8 +204,5 @@ | ||
*/ | ||
async numberToHash(blockNumber: bigint): Promise<Buffer> { | ||
if (blockNumber < BigInt(0)) { | ||
throw new NotFoundError(blockNumber) | ||
} | ||
return this.get(DBTarget.NumberToHash, { blockNumber }) | ||
async numberToHash(blockNumber: bigint): Promise<Uint8Array | undefined> { | ||
const value = await this.get(DBTarget.NumberToHash, { blockNumber }) | ||
return value | ||
} | ||
@@ -223,3 +220,2 @@ | ||
const dbKey = dbGetOperation.baseDBOp.key | ||
const dbOpts = dbGetOperation.baseDBOp | ||
@@ -230,8 +226,9 @@ if (cacheString !== undefined) { | ||
} | ||
let value = this._cache[cacheString].get(dbKey) | ||
if (!value) { | ||
value = await this._db.get(dbKey, dbOpts) | ||
if (value) { | ||
if (value === undefined) { | ||
value = (await this._db.get(dbKey, { | ||
keyEncoding: dbGetOperation.baseDBOp.keyEncoding, | ||
valueEncoding: dbGetOperation.baseDBOp.valueEncoding, | ||
})) as Uint8Array | undefined | ||
if (value !== undefined) { | ||
this._cache[cacheString].set(dbKey, value) | ||
@@ -243,4 +240,6 @@ } | ||
} | ||
return this._db.get(dbKey, dbOpts) | ||
return this._db.get(dbKey, { | ||
keyEncoding: dbGetOperation.baseDBOp.keyEncoding, | ||
valueEncoding: dbGetOperation.baseDBOp.valueEncoding, | ||
}) | ||
} | ||
@@ -252,8 +251,25 @@ | ||
async batch(ops: DBOp[]) { | ||
const convertedOps: DBOpData[] = ops.map((op) => op.baseDBOp) | ||
const convertedOps: BatchDBOp[] = ops.map((op) => { | ||
const type = | ||
op.baseDBOp.type !== undefined | ||
? op.baseDBOp.type | ||
: op.baseDBOp.value !== undefined | ||
? 'put' | ||
: 'del' | ||
const convertedOp = { | ||
key: op.baseDBOp.key, | ||
value: op.baseDBOp.value, | ||
type, | ||
opts: { | ||
keyEncoding: op.baseDBOp.keyEncoding, | ||
valueEncoding: op.baseDBOp.valueEncoding, | ||
}, | ||
} | ||
if (type === 'put') return convertedOp as PutBatch | ||
else return convertedOp as DelBatch | ||
}) | ||
// update the current cache for each operation | ||
ops.map((op) => op.updateCache(this._cache)) | ||
return this._db.batch(convertedOps as any) | ||
return this._db.batch(convertedOps) | ||
} | ||
} |
@@ -0,1 +1,3 @@ | ||
import { KeyEncoding, ValueEncoding } from '@ethereumjs/util' | ||
import { | ||
@@ -10,5 +12,5 @@ HEADS_KEY, | ||
tdKey, | ||
} from './constants' | ||
} from './constants.js' | ||
import type { CacheMap } from './manager' | ||
import type { CacheMap } from './manager.js' | ||
@@ -34,7 +36,7 @@ export enum DBTarget { | ||
export interface DBOpData { | ||
type?: string | ||
key: Buffer | string | ||
keyEncoding: string | ||
valueEncoding?: string | ||
value?: Buffer | object | ||
type?: 'put' | 'del' | ||
key: Uint8Array | string | ||
keyEncoding: KeyEncoding | ||
valueEncoding?: ValueEncoding | ||
value?: Uint8Array | object | ||
} | ||
@@ -45,3 +47,3 @@ | ||
blockNumber?: bigint | ||
blockHash?: Buffer | ||
blockHash?: Uint8Array | ||
} | ||
@@ -62,4 +64,4 @@ | ||
key: '', | ||
keyEncoding: 'buffer', | ||
valueEncoding: 'buffer', | ||
keyEncoding: KeyEncoding.Bytes, | ||
valueEncoding: ValueEncoding.Bytes, | ||
} | ||
@@ -70,3 +72,3 @@ | ||
this.baseDBOp.key = HEADS_KEY | ||
this.baseDBOp.valueEncoding = 'json' | ||
this.baseDBOp.valueEncoding = ValueEncoding.JSON | ||
break | ||
@@ -76,2 +78,3 @@ } | ||
this.baseDBOp.key = HEAD_HEADER_KEY | ||
this.baseDBOp.keyEncoding = KeyEncoding.String | ||
break | ||
@@ -81,2 +84,3 @@ } | ||
this.baseDBOp.key = HEAD_BLOCK_KEY | ||
this.baseDBOp.keyEncoding = KeyEncoding.String | ||
break | ||
@@ -117,3 +121,7 @@ } | ||
// set operation: note: value/key is not in default order | ||
public static set(operationTarget: DBTarget, value: Buffer | object, key?: DatabaseKey): DBOp { | ||
public static set( | ||
operationTarget: DBTarget, | ||
value: Uint8Array | object, | ||
key?: DatabaseKey | ||
): DBOp { | ||
const dbOperation = new DBOp(operationTarget, key) | ||
@@ -124,5 +132,5 @@ dbOperation.baseDBOp.value = value | ||
if (operationTarget === DBTarget.Heads) { | ||
dbOperation.baseDBOp.valueEncoding = 'json' | ||
dbOperation.baseDBOp.valueEncoding = ValueEncoding.JSON | ||
} else { | ||
dbOperation.baseDBOp.valueEncoding = 'binary' | ||
dbOperation.baseDBOp.valueEncoding = ValueEncoding.Bytes | ||
} | ||
@@ -142,3 +150,3 @@ | ||
if (this.baseDBOp.type === 'put') { | ||
Buffer.isBuffer(this.baseDBOp.value) && | ||
this.baseDBOp.value instanceof Uint8Array && | ||
cacheMap[this.cacheString].set(this.baseDBOp.key, this.baseDBOp.value) | ||
@@ -145,0 +153,0 @@ } else if (this.baseDBOp.type === 'del') { |
@@ -1,4 +0,10 @@ | ||
export { Blockchain } from './blockchain' | ||
export { CasperConsensus, CliqueConsensus, Consensus, EthashConsensus } from './consensus' | ||
export { BlockchainInterface, BlockchainOptions } from './types' | ||
export * from './utils' | ||
export { Blockchain } from './blockchain.js' | ||
export { CasperConsensus, CliqueConsensus, EthashConsensus } from './consensus/index.js' | ||
export { | ||
DBOp, | ||
DBSaveLookups, | ||
DBSetBlockOrHeader, | ||
DBSetHashToNumber, | ||
DBSetTD, | ||
} from './db/helpers.js' | ||
export * from './types.js' |
155
src/types.ts
@@ -1,6 +0,5 @@ | ||
import type { Consensus } from './consensus' | ||
import type { GenesisState } from './genesisStates' | ||
import type { Blockchain } from '.' | ||
import type { Block, BlockHeader } from '@ethereumjs/block' | ||
import type { Common } from '@ethereumjs/common' | ||
import type { AbstractLevel } from 'abstract-level' | ||
import type { Common, ConsensusAlgorithm } from '@ethereumjs/common' | ||
import type { DB, DBObject, GenesisState } from '@ethereumjs/util' | ||
@@ -24,3 +23,3 @@ export type OnBlock = (block: Block, reorg: boolean) => Promise<void> | void | ||
*/ | ||
delBlock(blockHash: Buffer): Promise<void> | ||
delBlock(blockHash: Uint8Array): Promise<void> | ||
@@ -30,3 +29,3 @@ /** | ||
*/ | ||
getBlock(blockId: Buffer | number | bigint): Promise<Block> | ||
getBlock(blockId: Uint8Array | number | bigint): Promise<Block> | ||
@@ -50,5 +49,5 @@ /** | ||
/** | ||
* Returns a copy of the blockchain | ||
* Returns a shallow copy of the blockchain that may share state with the original | ||
*/ | ||
copy(): BlockchainInterface | ||
shallowCopy(): BlockchainInterface | ||
@@ -67,14 +66,16 @@ /** | ||
*/ | ||
getIteratorHead?(name?: string): Promise<Block> | ||
getIteratorHead(name?: string): Promise<Block> | ||
/** | ||
* Gets total difficulty for a block specified by hash and number | ||
* Set header hash of a certain `tag`. | ||
* When calling the iterator, the iterator will start running the first child block after the header hash currently stored. | ||
* @param tag - The tag to save the headHash to | ||
* @param headHash - The head hash to save | ||
*/ | ||
getTotalDifficulty?(hash: Buffer, number?: bigint): Promise<bigint> | ||
setIteratorHead(tag: string, headHash: Uint8Array): Promise<void> | ||
/** | ||
* Returns the genesis state of the blockchain. | ||
* All values are provided as hex-prefixed strings. | ||
* Gets total difficulty for a block specified by hash and number | ||
*/ | ||
genesisState?(): GenesisState | ||
getTotalDifficulty?(hash: Uint8Array, number?: bigint): Promise<bigint> | ||
@@ -84,9 +85,51 @@ /** | ||
*/ | ||
getCanonicalHeadBlock?(): Promise<Block> | ||
getCanonicalHeadBlock(): Promise<Block> | ||
} | ||
export interface GenesisOptions { | ||
/** | ||
* The blockchain only initializes successfully if it has a genesis block. If | ||
* there is no block available in the DB and a `genesisBlock` is provided, | ||
* then the provided `genesisBlock` will be used as genesis. If no block is | ||
* present in the DB and no block is provided, then the genesis block as | ||
* provided from the `common` will be used. | ||
*/ | ||
genesisBlock?: Block | ||
/** | ||
* If you are using a custom chain {@link Common}, pass the genesis state. | ||
* | ||
* Pattern 1 (with genesis state see {@link GenesisState} for format): | ||
* | ||
* ```javascript | ||
* { | ||
* '0x0...01': '0x100', // For EoA | ||
* } | ||
* ``` | ||
* | ||
* Pattern 2 (with complex genesis state, containing contract accounts and storage). | ||
* Note that in {@link AccountState} there are two | ||
* accepted types. This allows to easily insert accounts in the genesis state: | ||
* | ||
* A complex genesis state with Contract and EoA states would have the following format: | ||
* | ||
* ```javascript | ||
* { | ||
* '0x0...01': '0x100', // For EoA | ||
* '0x0...02': ['0x1', '0xRUNTIME_BYTECODE', [[storageKey1, storageValue1], [storageKey2, storageValue2]]] // For contracts | ||
* } | ||
* ``` | ||
*/ | ||
genesisState?: GenesisState | ||
/** | ||
* State root of the genesis state | ||
*/ | ||
genesisStateRoot?: Uint8Array | ||
} | ||
/** | ||
* This are the options that the Blockchain constructor can receive. | ||
*/ | ||
export interface BlockchainOptions { | ||
export interface BlockchainOptions extends GenesisOptions { | ||
/** | ||
@@ -113,10 +156,5 @@ * Specify the chain and hardfork by passing a {@link Common} instance. | ||
* Database to store blocks and metadata. | ||
* Should be an `abstract-leveldown` compliant store | ||
* wrapped with `encoding-down`. | ||
* For example: | ||
* `levelup(encode(leveldown('./db1')))` | ||
* or use the `level` convenience package: | ||
* `new MemoryLevel('./db1')` | ||
* Can be any database implementation that adheres to the `DB` interface | ||
*/ | ||
db?: AbstractLevel<string | Buffer | Uint8Array, string | Buffer, string | Buffer> | ||
db?: DB<Uint8Array | string | number, Uint8Array | string | DBObject> | ||
@@ -143,40 +181,49 @@ /** | ||
/** | ||
* The blockchain only initializes successfully if it has a genesis block. If | ||
* there is no block available in the DB and a `genesisBlock` is provided, | ||
* then the provided `genesisBlock` will be used as genesis. If no block is | ||
* present in the DB and no block is provided, then the genesis block as | ||
* provided from the `common` will be used. | ||
* Optional custom consensus that implements the {@link Consensus} class | ||
*/ | ||
genesisBlock?: Block | ||
consensus?: Consensus | ||
} | ||
/** | ||
* Interface that a consensus class needs to implement. | ||
*/ | ||
export interface Consensus { | ||
algorithm: ConsensusAlgorithm | string | ||
/** | ||
* If you are using a custom chain {@link Common}, pass the genesis state. | ||
* | ||
* Pattern 1 (with genesis state see {@link GenesisState} for format): | ||
* | ||
* ```javascript | ||
* { | ||
* '0x0...01': '0x100', // For EoA | ||
* } | ||
* ``` | ||
* | ||
* Pattern 2 (with complex genesis state, containing contract accounts and storage). | ||
* Note that in {@link AccountState} there are two | ||
* accepted types. This allows to easily insert accounts in the genesis state: | ||
* | ||
* A complex genesis state with Contract and EoA states would have the following format: | ||
* | ||
* ```javascript | ||
* { | ||
* '0x0...01': '0x100', // For EoA | ||
* '0x0...02': ['0x1', '0xRUNTIME_BYTECODE', [[storageKey1, storageValue1], [storageKey2, storageValue2]]] // For contracts | ||
* } | ||
* ``` | ||
* Initialize genesis for consensus mechanism | ||
* @param genesisBlock genesis block | ||
*/ | ||
genesisState?: GenesisState | ||
genesisInit(genesisBlock: Block): Promise<void> | ||
/** | ||
* Optional custom consensus that implements the {@link Consensus} class | ||
* Set up consensus mechanism | ||
*/ | ||
consensus?: Consensus | ||
setup({ blockchain }: ConsensusOptions): Promise<void> | ||
/** | ||
* Validate block consensus parameters | ||
* @param block block to be validated | ||
*/ | ||
validateConsensus(block: Block): Promise<void> | ||
validateDifficulty(header: BlockHeader): Promise<void> | ||
/** | ||
* Update consensus on new block | ||
* @param block new block | ||
* @param commonAncestor common ancestor block header (optional) | ||
* @param ancientHeaders array of ancestor block headers (optional) | ||
*/ | ||
newBlock( | ||
block: Block, | ||
commonAncestor?: BlockHeader, | ||
ancientHeaders?: BlockHeader[] | ||
): Promise<void> | ||
} | ||
/** | ||
* Options when initializing a class that implements the Consensus interface. | ||
*/ | ||
export interface ConsensusOptions { | ||
blockchain: Blockchain | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
10
0
114
215
605353
8838
1
1
+ Added@ethereumjs/block@5.0.0-rc.1(transitive)
+ Added@ethereumjs/common@4.0.0-rc.1(transitive)
+ Added@ethereumjs/ethash@3.0.0-rc.1(transitive)
+ Added@ethereumjs/rlp@5.0.0-rc.1(transitive)
+ Added@ethereumjs/trie@6.0.0-rc.1(transitive)
+ Added@ethereumjs/tx@5.0.0-rc.1(transitive)
+ Added@ethereumjs/util@9.0.0-rc.1(transitive)
+ Addedcrc@4.3.2(transitive)
+ Addedlru-cache@10.4.3(transitive)
- Removedabstract-level@^1.0.3
- Removedlevel@^8.0.0
- Removedmemory-level@^1.0.0
- Removed@ethereumjs/block@4.3.0(transitive)
- Removed@ethereumjs/common@3.2.0(transitive)
- Removed@ethereumjs/ethash@2.1.0(transitive)
- Removed@ethereumjs/rlp@4.0.1(transitive)
- Removed@ethereumjs/trie@5.1.0(transitive)
- Removed@ethereumjs/tx@4.2.0(transitive)
- Removed@ethereumjs/util@8.1.0(transitive)
- Removedabstract-level@1.0.4(transitive)
- Removedbase64-js@1.5.1(transitive)
- Removedbrowser-level@1.0.1(transitive)
- Removedbuffer@6.0.3(transitive)
- Removedcatering@2.1.1(transitive)
- Removedclassic-level@1.4.1(transitive)
- Removedcrc-32@1.2.2(transitive)
- Removedfunctional-red-black-tree@1.0.1(transitive)
- Removedieee754@1.2.1(transitive)
- Removedis-buffer@2.0.5(transitive)
- Removedlevel@8.0.1(transitive)
- Removedlevel-supports@4.0.1(transitive)
- Removedlevel-transcoder@1.0.1(transitive)
- Removedlru-cache@5.1.1(transitive)
- Removedmemory-level@1.0.0(transitive)
- Removedmicro-ftch@0.3.1(transitive)
- Removedmodule-error@1.0.2(transitive)
- Removednapi-macros@2.2.2(transitive)
- Removednode-gyp-build@4.8.2(transitive)
- Removedqueue-microtask@1.2.3(transitive)
- Removedrun-parallel-limit@1.1.0(transitive)
- Removedyallist@3.1.1(transitive)
Updated@ethereumjs/block@5.0.0-rc.1
Updated@ethereumjs/rlp@5.0.0-rc.1
Updated@ethereumjs/trie@6.0.0-rc.1
Updated@ethereumjs/tx@5.0.0-rc.1
Updated@ethereumjs/util@9.0.0-rc.1
Updatedethereum-cryptography@^2.1.2
Updatedlru-cache@^10.0.0