Skip to content

Commit f6127f2

Browse files
authored
blockchain: Fix and enhance the blockchain iterator (#2308)
1 parent be6449a commit f6127f2

File tree

8 files changed

+255
-174
lines changed

8 files changed

+255
-174
lines changed

packages/blockchain/src/blockchain.ts

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import { Block, BlockHeader } from '@ethereumjs/block'
22
import { Chain, Common, ConsensusAlgorithm, ConsensusType, Hardfork } from '@ethereumjs/common'
3+
import { Lock } from '@ethereumjs/util'
34
import { MemoryLevel } from 'memory-level'
45

56
import { CasperConsensus, CliqueConsensus, EthashConsensus } from './consensus'
67
import { DBOp, DBSaveLookups, DBSetBlockOrHeader, DBSetHashToNumber, DBSetTD } from './db/helpers'
78
import { DBManager } from './db/manager'
89
import { DBTarget } from './db/operation'
910
import { genesisStateRoot } from './genesisStates'
10-
import { Lock } from './lock'
1111

1212
import type { Consensus } from './consensus'
1313
import type { GenesisState } from './genesisStates'
@@ -911,33 +911,47 @@ export class Blockchain implements BlockchainInterface {
911911
* @param maxBlocks - How many blocks to run. By default, run all unprocessed blocks in the canonical chain.
912912
* @returns number of blocks actually iterated
913913
*/
914-
async iterator(name: string, onBlock: OnBlock, maxBlocks?: number): Promise<number> {
915-
return this._iterator(name, onBlock, maxBlocks)
916-
}
917-
918-
/**
919-
* @hidden
920-
*/
921-
private async _iterator(name: string, onBlock: OnBlock, maxBlocks?: number): Promise<number> {
914+
async iterator(
915+
name: string,
916+
onBlock: OnBlock,
917+
maxBlocks?: number,
918+
releaseLockOnCallback?: boolean
919+
): Promise<number> {
922920
return this.runWithLock<number>(async (): Promise<number> => {
923-
const headHash = this._heads[name] ?? this.genesisBlock.hash()
921+
let headHash = this._heads[name] ?? this.genesisBlock.hash()
924922

925923
if (typeof maxBlocks === 'number' && maxBlocks < 0) {
926924
throw 'If maxBlocks is provided, it has to be a non-negative number'
927925
}
928926

929-
const headBlockNumber = await this.dbManager.hashToNumber(headHash)
927+
let headBlockNumber = await this.dbManager.hashToNumber(headHash)
930928
let nextBlockNumber = headBlockNumber + BigInt(1)
931929
let blocksRanCounter = 0
932930
let lastBlock: Block | undefined
933931

934932
while (maxBlocks !== blocksRanCounter) {
935933
try {
936-
const nextBlock = await this._getBlock(nextBlockNumber)
934+
let nextBlock = await this._getBlock(nextBlockNumber)
935+
const reorg = lastBlock ? !lastBlock.hash().equals(nextBlock.header.parentHash) : false
936+
if (reorg) {
937+
// If reorg has happened, the _heads must have been updated so lets reload the counters
938+
headHash = this._heads[name] ?? this.genesisBlock.hash()
939+
headBlockNumber = await this.dbManager.hashToNumber(headHash)
940+
nextBlockNumber = headBlockNumber + BigInt(1)
941+
nextBlock = await this._getBlock(nextBlockNumber)
942+
}
937943
this._heads[name] = nextBlock.hash()
938-
const reorg = lastBlock ? lastBlock.hash().equals(nextBlock.header.parentHash) : false
939944
lastBlock = nextBlock
940-
await onBlock(nextBlock, reorg)
945+
if (releaseLockOnCallback === true) {
946+
this._lock.release()
947+
}
948+
try {
949+
await onBlock(nextBlock, reorg)
950+
} finally {
951+
if (releaseLockOnCallback === true) {
952+
await this._lock.acquire()
953+
}
954+
}
941955
nextBlockNumber++
942956
blocksRanCounter++
943957
} catch (error: any) {

packages/blockchain/src/types.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,12 @@ export interface BlockchainInterface {
3737
* @param maxBlocks - optional maximum number of blocks to iterate through
3838
* reorg: boolean)
3939
*/
40-
iterator(name: string, onBlock: OnBlock, maxBlocks?: number): Promise<number>
40+
iterator(
41+
name: string,
42+
onBlock: OnBlock,
43+
maxBlocks?: number,
44+
releaseLockOnCallback?: boolean
45+
): Promise<number>
4146

4247
/**
4348
* Returns a copy of the blockchain

packages/blockchain/test/index.spec.ts

Lines changed: 0 additions & 154 deletions
Original file line numberDiff line numberDiff line change
@@ -368,143 +368,6 @@ tape('blockchain test', (t) => {
368368
st.end()
369369
})
370370

371-
t.test('should iterate through 24 blocks', async (st) => {
372-
const { blockchain, blocks, error } = await generateBlockchain(25)
373-
st.error(error, 'no error')
374-
let i = 0
375-
const iterated = await blockchain.iterator('test', (block: Block) => {
376-
if (block.hash().equals(blocks[i + 1].hash())) {
377-
i++
378-
}
379-
})
380-
st.equal(iterated, 24)
381-
st.equal(i, 24)
382-
st.end()
383-
})
384-
385-
t.test(
386-
'should iterate through maxBlocks blocks if maxBlocks parameter is provided',
387-
async (st) => {
388-
const { blockchain, blocks, error } = await generateBlockchain(25)
389-
st.error(error, 'no error')
390-
let i = 0
391-
const iterated = await blockchain.iterator(
392-
'test',
393-
(block: Block) => {
394-
if (block.hash().equals(blocks[i + 1].hash())) {
395-
i++
396-
}
397-
},
398-
5
399-
)
400-
st.equal(iterated, 5)
401-
st.equal(i, 5)
402-
st.end()
403-
}
404-
)
405-
406-
t.test(
407-
'should iterate through 0 blocks in case 0 maxBlocks parameter is provided',
408-
async (st) => {
409-
const { blockchain, blocks, error } = await generateBlockchain(25)
410-
st.error(error, 'no error')
411-
let i = 0
412-
const iterated = await blockchain
413-
.iterator(
414-
'test',
415-
(block: Block) => {
416-
if (block.hash().equals(blocks[i + 1].hash())) {
417-
i++
418-
}
419-
},
420-
0
421-
)
422-
.catch(() => {
423-
st.fail('Promise cannot throw when running 0 blocks')
424-
})
425-
st.equal(iterated, 0)
426-
st.equal(i, 0)
427-
st.end()
428-
}
429-
)
430-
431-
t.test('should throw on a negative maxBlocks parameter in iterator', async (st) => {
432-
const { blockchain, blocks, error } = await generateBlockchain(25)
433-
st.error(error, 'no error')
434-
let i = 0
435-
await blockchain
436-
.iterator(
437-
'test',
438-
(block: Block) => {
439-
if (block.hash().equals(blocks[i + 1].hash())) {
440-
i++
441-
}
442-
},
443-
-1
444-
)
445-
.catch(() => {
446-
st.end()
447-
})
448-
// Note: if st.end() is not called (Promise did not throw), then this test fails, as it does not end.
449-
})
450-
451-
t.test('should test setIteratorHead method', async (st) => {
452-
const { blockchain, blocks, error } = await generateBlockchain(25)
453-
st.error(error, 'no error')
454-
455-
const headBlockIndex = 5
456-
457-
const headHash = blocks[headBlockIndex].hash()
458-
await blockchain.setIteratorHead('myHead', headHash)
459-
const currentHeadBlock = await blockchain.getIteratorHead('myHead')
460-
461-
st.ok(headHash.equals(currentHeadBlock.hash()), 'head hash equals the provided head hash')
462-
463-
let i = 0
464-
// check that iterator starts from this head block
465-
await blockchain.iterator(
466-
'myHead',
467-
(block: Block) => {
468-
if (block.hash().equals(blocks[headBlockIndex + 1].hash())) {
469-
i++
470-
}
471-
},
472-
5
473-
)
474-
475-
st.equal(i, 1)
476-
477-
st.end()
478-
})
479-
480-
t.test('should catch iterator func error', async (st) => {
481-
const { blockchain, error } = await generateBlockchain(25)
482-
st.error(error, 'no error')
483-
try {
484-
await blockchain.iterator('error', () => {
485-
throw new Error('iterator func error')
486-
})
487-
} catch (error: any) {
488-
st.ok(error)
489-
st.equal(error.message, 'iterator func error', 'should return correct error')
490-
st.end()
491-
}
492-
})
493-
494-
t.test('should not call iterator function in an empty blockchain', async (st) => {
495-
const blockchain = await Blockchain.create({
496-
validateBlocks: true,
497-
validateConsensus: false,
498-
})
499-
500-
await blockchain.iterator('test', () => {
501-
st.fail('should not call iterator function')
502-
})
503-
504-
st.pass('should finish iterating')
505-
st.end()
506-
})
507-
508371
t.test('should add fork header and reset stale heads', async (st) => {
509372
const { blockchain, blocks, error } = await generateBlockchain(15)
510373
st.error(error, 'no error')
@@ -615,23 +478,6 @@ tape('blockchain test', (t) => {
615478
st.end()
616479
})
617480

618-
t.test('should get heads', async (st) => {
619-
const [db, genesis] = await createTestDB()
620-
const blockchain = await Blockchain.create({ db, genesisBlock: genesis })
621-
const head = await blockchain.getIteratorHead()
622-
if (typeof genesis !== 'undefined') {
623-
st.ok(head.hash().equals(genesis.hash()), 'should get head')
624-
st.equal(
625-
(blockchain as any)._heads['head0'].toString('hex'),
626-
'abcd',
627-
'should get state root heads'
628-
)
629-
st.end()
630-
} else {
631-
st.fail()
632-
}
633-
})
634-
635481
t.test('should validate', async (st) => {
636482
const genesisBlock = Block.fromBlockData({ header: { gasLimit: 8000000 } })
637483
const blockchain = await Blockchain.create({

0 commit comments

Comments
 (0)