-
-
Notifications
You must be signed in to change notification settings - Fork 28
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
✅ Test: Add 100% test coverage to tevm/blockchain package #1319
Changes from 1 commit
362ae4a
e19b08b
9e0cdab
aec7a63
afb954a
8dab74b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
// Bun Snapshot v1, https://goo.gl/fbAQLP | ||
|
||
exports[`createChain should throw an error for invalid block header validation 1`] = `"header.isGenesis is not a function. (In 'header.isGenesis()', 'header.isGenesis' is undefined)"`; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
// Bun Snapshot v1, https://goo.gl/fbAQLP | ||
|
||
exports[`validateHeader should throw an error for invalid block number 1`] = `[TypeError: header.isGenesis is not a function. (In 'header.isGenesis()', 'header.isGenesis' is undefined)]`; | ||
|
||
exports[`validateHeader should throw an error for invalid timestamp 1`] = `[TypeError: header.isGenesis is not a function. (In 'header.isGenesis()', 'header.isGenesis' is undefined)]`; | ||
|
||
exports[`validateHeader should throw an error for unsupported consensus type 1`] = `"header.isGenesis is not a function. (In 'header.isGenesis()', 'header.isGenesis' is undefined)"`; | ||
|
||
exports[`validateHeader should throw an error for invalid timestamp diff (clique) 1`] = `"header.isGenesis is not a function. (In 'header.isGenesis()', 'header.isGenesis' is undefined)"`; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import { describe, expect, it } from 'bun:test' | ||
import { deepCopy } from './deepCopy.js' | ||
import { createBaseChain } from '../createBaseChain.js' | ||
import { optimism } from '@tevm/common' | ||
import { putBlock } from './putBlock.js' | ||
import { bytesToHex } from '@tevm/utils' | ||
import { getBlock } from './getBlock.js' | ||
import { getMockBlocks } from '../test/getBlocks.js' | ||
|
||
describe(deepCopy.name, async () => { | ||
it('should deepCopy the chain', async () => { | ||
const blocks = await getMockBlocks() | ||
const chain = createBaseChain({ | ||
common: optimism.copy(), | ||
}) | ||
await putBlock(chain)(blocks[0]) | ||
await putBlock(chain)(blocks[1]) | ||
await putBlock(chain)(blocks[2]) | ||
await putBlock(chain)(blocks[3]) | ||
const copy = await deepCopy(chain)() | ||
expect(await getBlock(copy)(blocks[0].header.number)).toEqual(blocks[0]) | ||
expect(await getBlock(copy)(blocks[1].header.number)).toEqual(blocks[1]) | ||
expect(await getBlock(copy)(blocks[3].header.number)).toEqual(blocks[3]) | ||
expect(chain.blocksByTag.get('latest')).toEqual(blocks[3]) | ||
expect(chain.blocks.get(bytesToHex(blocks[0].hash()))).toEqual(blocks[0]) | ||
expect(chain.blocks.get(bytesToHex(blocks[1].hash()))).toEqual(blocks[1]) | ||
expect(chain.blocks.get(bytesToHex(blocks[2].hash()))).toEqual(blocks[2]) | ||
expect(chain.blocks.get(bytesToHex(blocks[3].hash()))).toEqual(blocks[3]) | ||
expect(chain.blocksByNumber.get(blocks[0].header.number)).toEqual(blocks[0]) | ||
expect(chain.blocksByNumber.get(blocks[1].header.number)).toEqual(blocks[1]) | ||
expect(chain.blocksByNumber.get(blocks[2].header.number)).toEqual(blocks[2]) | ||
expect(chain.blocksByNumber.get(blocks[3].header.number)).toEqual(blocks[3]) | ||
}) | ||
}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,28 @@ | ||
import { bytesToHex } from 'viem' | ||
import { getCanonicalHeadBlock } from './getCanonicalHeadBlock.js' | ||
import { getBlock } from './getBlock.js' | ||
import { InvalidBlockError } from '@tevm/errors' | ||
|
||
/** | ||
* Deletes a block from the blockchain | ||
* @param {import('../BaseChain.js').BaseChain} baseChain | ||
* @returns {import('../Chain.js').Chain['delBlock']} | ||
* @throws {InvalidBlockError} If the block is the `forked` block | ||
*/ | ||
export const delBlock = (baseChain) => (blockHash) => { | ||
baseChain.blocks.delete(bytesToHex(blockHash)) | ||
export const delBlock = (baseChain) => async (blockHash) => { | ||
const block = await getBlock(baseChain)(blockHash) | ||
const hexHash = bytesToHex(blockHash) | ||
|
||
const latest = await getCanonicalHeadBlock(baseChain)() | ||
const forkedBlock = baseChain.blocksByTag.get('forked') | ||
|
||
if (forkedBlock && hexHash === bytesToHex(forkedBlock.hash())) { | ||
throw new InvalidBlockError('Cannot delete the forked block!') | ||
} | ||
if (hexHash === bytesToHex(latest.hash())) { | ||
baseChain.blocksByTag.set('latest', await getBlock(baseChain)(latest.header.parentHash)) | ||
} | ||
baseChain.blocksByNumber.delete(block.header.number) | ||
baseChain.blocks.delete(hexHash) | ||
baseChain.logger.debug({ blockHash }, 'deleted block') | ||
return Promise.resolve() | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import { describe, expect, it } from 'bun:test' | ||
import { createBaseChain } from '../createBaseChain.js' | ||
import { optimism } from '@tevm/common' | ||
import { putBlock } from './putBlock.js' | ||
import { delBlock } from './delBlock.js' | ||
import { getMockBlocks } from '../test/getBlocks.js' | ||
import { bytesToHex } from 'viem' | ||
import { getCanonicalHeadBlock } from './getCanonicalHeadBlock.js' | ||
import { InvalidBlockError } from '@tevm/errors' | ||
|
||
describe(delBlock.name, async () => { | ||
it('should delete block', async () => { | ||
const chain = createBaseChain({ | ||
common: optimism.copy(), | ||
}) | ||
const blocks = await getMockBlocks() | ||
await putBlock(chain)(blocks[0]) | ||
await putBlock(chain)(blocks[1]) | ||
await putBlock(chain)(blocks[2]) | ||
await putBlock(chain)(blocks[3]) | ||
|
||
await delBlock(chain)(blocks[2].hash()) | ||
|
||
expect(chain.blocks.get(bytesToHex(blocks[2].hash()))).toBeUndefined() | ||
expect(chain.blocksByNumber.get(blocks[2].header.number)).toBeUndefined() | ||
}) | ||
|
||
it('should set latest to parent if we delete latest', async () => { | ||
const chain = createBaseChain({ | ||
common: optimism.copy(), | ||
}) | ||
const blocks = await getMockBlocks() | ||
await putBlock(chain)(blocks[0]) | ||
await putBlock(chain)(blocks[1]) | ||
await putBlock(chain)(blocks[2]) | ||
await putBlock(chain)(blocks[3]) | ||
|
||
await delBlock(chain)(blocks[3].hash()) | ||
|
||
expect(await getCanonicalHeadBlock(chain)()).toBe(blocks[2]) | ||
}) | ||
|
||
it('should throw an InvalidBlockError if we attempt to delete the fork block', async () => { | ||
const chain = createBaseChain({ | ||
common: optimism.copy(), | ||
}) | ||
const blocks = await getMockBlocks() | ||
await putBlock(chain)(blocks[0]) | ||
chain.blocksByTag.set('forked', blocks[0]) | ||
|
||
const error = await delBlock(chain)(blocks[0].hash()).then((e) => e) | ||
|
||
expect(error).toBeInstanceOf(InvalidBlockError) | ||
expect(error).toMatchSnapshot() | ||
}) | ||
}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
import { describe, expect, it } from 'bun:test' | ||
import { getBlock } from './getBlock.js' | ||
import { createBaseChain } from '../createBaseChain.js' | ||
import { mainnet } from '@tevm/common' | ||
import { putBlock } from './putBlock.js' | ||
import { getMockBlocks } from '../test/getBlocks.js' | ||
import { UnknownBlockError } from '@tevm/errors' | ||
import { transports } from '@tevm/test-utils' | ||
|
||
describe(getBlock.name, async () => { | ||
const chain = createBaseChain({ | ||
common: mainnet.copy(), | ||
}) | ||
|
||
const blocks = await getMockBlocks() | ||
|
||
await putBlock(chain)(blocks[0]) | ||
await putBlock(chain)(blocks[1]) | ||
await putBlock(chain)(blocks[2]) | ||
|
||
it('can get a block by hash', async () => { | ||
expect(await getBlock(chain)(blocks[0].hash())).toBe(blocks[0]) | ||
}) | ||
|
||
it('can get a block by number', async () => { | ||
expect(await getBlock(chain)(blocks[0].header.number)).toBe(blocks[0]) | ||
}) | ||
|
||
it('can get a block by number that is not a big int', async () => { | ||
expect(await getBlock(chain)(Number(blocks[0].header.number))).toBe(blocks[0]) | ||
}) | ||
|
||
it('should throw an error if the block does not exist', async () => { | ||
let error = await getBlock(chain)(69).then((e) => e) | ||
expect(error).toBeInstanceOf(UnknownBlockError) | ||
expect(error).toMatchSnapshot() | ||
error = await getBlock(chain)(blocks[3].hash()).catch((e) => e) | ||
expect(error).toBeInstanceOf(UnknownBlockError) | ||
expect(error).toMatchSnapshot() | ||
}) | ||
|
||
it('should fetch and cache the block from rpc if it does not exist', async () => { | ||
const chain = createBaseChain({ | ||
common: mainnet.copy(), | ||
fork: { | ||
transport: transports.optimism, | ||
blockTag: blocks[0].header.number, | ||
}, | ||
}) | ||
await chain.ready() | ||
expect(await getBlock(chain)(blocks[1].hash())).toEqual(blocks[1]) | ||
expect(chain.blocksByNumber.get(blocks[1].header.number)).toEqual(blocks[1]) | ||
}) | ||
|
||
it('should fetch and cache the block by number from rpc if it does not exist', async () => { | ||
const chain = createBaseChain({ | ||
common: mainnet.copy(), | ||
fork: { | ||
transport: transports.optimism, | ||
blockTag: blocks[0].header.number, | ||
}, | ||
}) | ||
await chain.ready() | ||
expect(await getBlock(chain)(blocks[1].header.number)).toEqual(blocks[1]) | ||
expect(chain.blocksByNumber.get(blocks[1].header.number)).toEqual(blocks[1]) | ||
}) | ||
|
||
it('should throw an error if attempting to fetch a block newer than the forked block', async () => { | ||
const chain = createBaseChain({ | ||
common: mainnet.copy(), | ||
fork: { | ||
transport: transports.optimism, | ||
blockTag: blocks[0].header.number, | ||
}, | ||
}) | ||
await chain.ready() | ||
const error = await getBlock(chain)(blocks[1].header.number).catch((e) => e) | ||
expect(error).toBeInstanceOf(UnknownBlockError) | ||
expect(error).toMatchSnapshot() | ||
}) | ||
}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import { describe, expect, it } from 'bun:test' | ||
import { getBlockByTag } from './getBlockByTag.js' | ||
import { createBaseChain } from '../createBaseChain.js' | ||
import { mainnet } from '@tevm/common' | ||
import { getMockBlocks } from '../test/getBlocks.js' | ||
import { putBlock } from './putBlock.js' | ||
import { UnknownBlockError } from '@tevm/errors' | ||
import { transports } from '@tevm/test-utils' | ||
|
||
describe(getBlockByTag.name, async () => { | ||
const chain = createBaseChain({ | ||
common: mainnet.copy(), | ||
}) | ||
|
||
const blocks = await getMockBlocks() | ||
|
||
await putBlock(chain)(blocks[0]) | ||
await putBlock(chain)(blocks[1]) | ||
await putBlock(chain)(blocks[2]) | ||
|
||
it('can get a block by hash', async () => { | ||
expect(await getBlockByTag(chain)(blocks[0].hash())).toBe(blocks[0]) | ||
}) | ||
|
||
it('can get a block by number', async () => { | ||
expect(await getBlockByTag(chain)(blocks[0].header.number)).toBe(blocks[0]) | ||
}) | ||
|
||
it('can get a block by number that is not a big int', async () => { | ||
expect(await getBlockByTag(chain)(Number(blocks[0].header.number))).toBe(blocks[0]) | ||
}) | ||
|
||
it('should throw an error if the block does not exist', async () => { | ||
let error = await getBlockByTag(chain)(69).then((e) => e) | ||
expect(error).toBeInstanceOf(UnknownBlockError) | ||
expect(error).toMatchSnapshot() | ||
error = await getBlockByTag(chain)(blocks[3].hash()).catch((e) => e) | ||
expect(error).toBeInstanceOf(UnknownBlockError) | ||
expect(error).toMatchSnapshot() | ||
}) | ||
|
||
it('should fetch and cache the block from rpc if it does not exist', async () => { | ||
const chain = createBaseChain({ | ||
common: mainnet.copy(), | ||
fork: { | ||
transport: transports.optimism, | ||
blockTag: blocks[0].header.number, | ||
}, | ||
}) | ||
await chain.ready() | ||
expect(await getBlockByTag(chain)('earliest')).toMatchSnapshot() | ||
}) | ||
|
||
it('should throw UnknownBlockError if tag doesn not exist', async () => { | ||
const chain = createBaseChain({ | ||
common: mainnet.copy(), | ||
}) | ||
const error = await getBlockByTag(chain)('safe').catch((e) => e) | ||
expect(error).toBeInstanceOf(UnknownBlockError) | ||
expect(error).toMatchSnapshot() | ||
}) | ||
}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,13 @@ | ||
import { InternalError } from '@tevm/errors' | ||
|
||
/** | ||
* @param {import('../BaseChain.js').BaseChain} baseChain | ||
* @returns {import('../Chain.js').Chain['getCanonicalHeadBlock']} | ||
*/ | ||
export const getCanonicalHeadBlock = (baseChain) => () => { | ||
const block = baseChain.blocksByTag.get('latest') | ||
if (!block) { | ||
throw new Error('No cannonical head exists on blockchain') | ||
throw new InternalError('No cannonical head exists on blockchain') | ||
} | ||
return Promise.resolve(block) | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,23 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { describe, expect, it } from 'bun:test' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { getCanonicalHeadBlock } from './getCanonicalHeadBlock.js' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { createBaseChain } from '../createBaseChain.js' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { putBlock } from './putBlock.js' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { getMockBlocks } from '../test/getBlocks.js' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { optimism } from '@tevm/common' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { InternalError } from '@tevm/errors' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
describe(getCanonicalHeadBlock.name, async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const blocks = await getMockBlocks() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
it('should get the canonical head block', async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const chain = createBaseChain({ common: optimism.copy() }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
await putBlock(chain)(blocks[0]) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
expect(await getCanonicalHeadBlock(chain)()).toBe(blocks[0]) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
it('should throw an error if not cannonical block', async () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const chain = createBaseChain({ common: optimism.copy() }) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
chain.blocksByTag.set('latest', undefined) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
let error = await getCanonicalHeadBlock(chain)().catch((e) => e) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
expect(error).toBeInstanceOf(InternalError) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
expect(error).toMatchSnapshot() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Comprehensive tests for The test suite comprehensively covers the functionality of - let error = await getCanonicalHeadBlock(chain)().catch((e) => e)
+ const error = await getCanonicalHeadBlock(chain)().catch((e) => e) This change ensures adherence to best practices in variable declaration. Committable suggestion
Suggested change
ToolsBiome
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,39 @@ | ||||||
import { describe, expect, it } from 'bun:test' | ||||||
import { getIteratorHead } from './getIteratorHead.js' | ||||||
import { createBaseChain } from '../createBaseChain.js' | ||||||
import { putBlock } from './putBlock.js' | ||||||
import { getMockBlocks } from '../test/getBlocks.js' | ||||||
import { optimism } from '@tevm/common' | ||||||
|
||||||
describe(getIteratorHead.name, async () => { | ||||||
const blocks = await getMockBlocks() | ||||||
|
||||||
it('should get the iterator head block for default tag', async () => { | ||||||
const chain = createBaseChain({ common: optimism.copy() }) | ||||||
await putBlock(chain)(blocks[0]) | ||||||
chain.blocksByTag.set('vm' as any, blocks[0]) // Set the iterator head | ||||||
expect(await getIteratorHead(chain)()).toBe(blocks[0]) | ||||||
}) | ||||||
|
||||||
it('should get the iterator head block for a specific tag', async () => { | ||||||
const chain = createBaseChain({ common: optimism.copy() }) | ||||||
await putBlock(chain)(blocks[1]) | ||||||
chain.blocksByTag.set('myTag' as any, blocks[1]) // Set the iterator head | ||||||
expect(await getIteratorHead(chain)('myTag')).toBe(blocks[1]) | ||||||
}) | ||||||
|
||||||
it('should throw an error if the iterator head block does not exist', async () => { | ||||||
const chain = createBaseChain({ common: optimism.copy() }) | ||||||
let error = await getIteratorHead(chain)('nonExistentTag').catch((e) => e) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use The variables - let error = await getIteratorHead(chain)('nonExistentTag').catch((e) => e)
+ const error = await getIteratorHead(chain)('nonExistentTag').catch((e) => e) Also applies to: 35-35 ToolsBiome
|
||||||
expect(error).toBeInstanceOf(Error) | ||||||
expect(error.message).toBe('No block with tag nonExistentTag exists. Current tags include ') | ||||||
}) | ||||||
|
||||||
it('should include current tags in error message if the iterator head block does not exist', async () => { | ||||||
const chain = createBaseChain({ common: optimism.copy() }) | ||||||
chain.blocksByTag.set('someTag' as any, blocks[0]) | ||||||
let error = await getIteratorHead(chain)('nonExistentTag').catch((e) => e) | ||||||
expect(error).toBeInstanceOf(Error) | ||||||
expect(error.message).toBe(`No block with tag nonExistentTag exists. Current tags include someTag`) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Avoid unnecessary template literals. The error message does not require a template literal as it does not perform any interpolation. - expect(error.message).toBe(`No block with tag nonExistentTag exists. Current tags include someTag`)
+ expect(error.message).toBe('No block with tag nonExistentTag exists. Current tags include someTag') Committable suggestion
Suggested change
ToolsBiome
|
||||||
}) | ||||||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use of
InternalError
enhances error specificity.The introduction of
InternalError
for non-existent canonical head blocks is a good practice, enhancing the clarity and manageability of errors. However, there's a small typo in the error message.Also applies to: 10-10