Skip to content

Commit 7d60a4f

Browse files
ScottyPoiacolytec3
andauthored
Client: separate TxIndex from ReceiptsManager (#4012)
* client: create txIndex.ts and export index types * client: extract txIndex logic from receiptsManager to txIndex * client: add txIndex as attribute of vmexecution * client: move calls to updateIndex out of receiptsManager * client: update RPC methods to use txIndex * test: use MemoryLevel in test * test: fix receipts test / add txIndex tests * client: remove unnecessary lookup from eth_getTransactionByHash * optimize debug_getRawTransaction * test TxIndex class * test vm exec initialization with metaDB * remove unreachable case * test get transaction without index --------- Co-authored-by: acolytec3 <[email protected]>
1 parent 09a83bd commit 7d60a4f

File tree

10 files changed

+193
-132
lines changed

10 files changed

+193
-132
lines changed

packages/client/src/execution/receipt.ts

Lines changed: 5 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { RLP } from '@ethereumjs/rlp'
22
import {
3-
BIGINT_0,
43
EthereumJSErrorWithoutCode,
54
bigIntToBytes,
65
bytesToBigInt,
@@ -17,6 +16,7 @@ import type { Block } from '@ethereumjs/block'
1716
import type { Log } from '@ethereumjs/evm'
1817
import type { TransactionType, TypedTransaction } from '@ethereumjs/tx'
1918
import type { PostByzantiumTxReceipt, PreByzantiumTxReceipt, TxReceipt } from '@ethereumjs/vm'
19+
import type { TxHashIndex } from './txIndex.ts'
2020

2121
/**
2222
* TxReceiptWithType extends TxReceipt to provide:
@@ -49,30 +49,11 @@ type GetLogsReturn = {
4949
logIndex: number
5050
}[]
5151

52-
/**
53-
* Indexes
54-
*/
55-
type TxHashIndex = [blockHash: Uint8Array, txIndex: number]
56-
57-
export type IndexType = (typeof IndexType)[keyof typeof IndexType]
58-
59-
export const IndexType = {
60-
TxHash: 'txhash',
61-
} as const
62-
63-
export type IndexOperation = (typeof IndexOperation)[keyof typeof IndexOperation]
64-
65-
export const IndexOperation = {
66-
Save: 'save',
67-
Delete: 'delete',
68-
} as const
69-
7052
/**
7153
* Storage encodings
7254
*/
7355
type rlpLog = Log
7456
type rlpReceipt = [postStateOrStatus: Uint8Array, cumulativeGasUsed: Uint8Array, logs: rlpLog[]]
75-
type rlpTxHash = [blockHash: Uint8Array, txIndex: Uint8Array]
7657

7758
export type RlpConvert = (typeof RlpConvert)[keyof typeof RlpConvert]
7859

@@ -85,9 +66,8 @@ export type RlpType = (typeof RlpType)[keyof typeof RlpType]
8566
export const RlpType = {
8667
Receipts: 'receipts',
8768
Logs: 'logs',
88-
TxHash: 'txhash',
8969
} as const
90-
type rlpOut = Log[] | TxReceipt[] | TxHashIndex
70+
type rlpOut = Log[] | TxReceipt[]
9171

9272
export class ReceiptsManager extends MetaDBManager {
9373
/**
@@ -114,12 +94,10 @@ export class ReceiptsManager extends MetaDBManager {
11494
async saveReceipts(block: Block, receipts: TxReceipt[]) {
11595
const encoded = this.rlp(RlpConvert.Encode, RlpType.Receipts, receipts)
11696
await this.put(DBKey.Receipts, block.hash(), encoded)
117-
void this.updateIndex(IndexOperation.Save, IndexType.TxHash, block)
11897
}
11998

12099
async deleteReceipts(block: Block) {
121100
await this.delete(DBKey.Receipts, block.hash())
122-
void this.updateIndex(IndexOperation.Delete, IndexType.TxHash, block)
123101
}
124102

125103
/**
@@ -166,9 +144,9 @@ export class ReceiptsManager extends MetaDBManager {
166144
* Returns receipt by tx hash with additional metadata for the JSON RPC response, or null if not found
167145
* @param txHash the tx hash
168146
*/
169-
async getReceiptByTxHash(txHash: Uint8Array): Promise<GetReceiptByTxHashReturn | null> {
170-
const txHashIndex = await this.getIndex(IndexType.TxHash, txHash)
171-
if (!txHashIndex) return null
147+
async getReceiptByTxHashIndex(
148+
txHashIndex: TxHashIndex,
149+
): Promise<GetReceiptByTxHashReturn | null> {
172150
const [blockHash, txIndex] = txHashIndex
173151
const receipts = await this.getReceipts(blockHash)
174152
if (receipts.length === 0) return null
@@ -247,72 +225,6 @@ export class ReceiptsManager extends MetaDBManager {
247225
return returnedLogs
248226
}
249227

250-
/**
251-
* Saves or deletes an index from the metaDB
252-
* @param operation the {@link IndexOperation}
253-
* @param type the {@link IndexType}
254-
* @param value for {@link IndexType.TxHash}, the block to save or delete the tx hash indexes for
255-
*/
256-
private async updateIndex(
257-
operation: IndexOperation,
258-
type: typeof IndexType.TxHash,
259-
value: Block,
260-
): Promise<void>
261-
private async updateIndex(operation: IndexOperation, type: IndexType, value: any): Promise<void> {
262-
switch (type) {
263-
case IndexType.TxHash: {
264-
const block = value
265-
if (operation === IndexOperation.Save) {
266-
const withinTxLookupLimit =
267-
this.config.txLookupLimit === 0 ||
268-
this.chain.headers.height - BigInt(this.config.txLookupLimit) < block.header.number
269-
if (withinTxLookupLimit) {
270-
for (const [i, tx] of block.transactions.entries()) {
271-
const index: TxHashIndex = [block.hash(), i]
272-
const encoded = this.rlp(RlpConvert.Encode, RlpType.TxHash, index)
273-
await this.put(DBKey.TxHash, tx.hash(), encoded)
274-
}
275-
}
276-
if (this.config.txLookupLimit > 0) {
277-
// Remove tx hashes for one block past txLookupLimit
278-
const limit = this.chain.headers.height - BigInt(this.config.txLookupLimit)
279-
if (limit < BIGINT_0) return
280-
const blockDelIndexes = await this.chain.getBlock(limit)
281-
void this.updateIndex(IndexOperation.Delete, IndexType.TxHash, blockDelIndexes)
282-
}
283-
} else if (operation === IndexOperation.Delete) {
284-
for (const tx of block.transactions) {
285-
await this.delete(DBKey.TxHash, tx.hash())
286-
}
287-
}
288-
break
289-
}
290-
default:
291-
throw EthereumJSErrorWithoutCode('Unsupported index type')
292-
}
293-
}
294-
295-
/**
296-
* Returns the value for an index or null if not found
297-
* @param type the {@link IndexType}
298-
* @param value for {@link IndexType.TxHash}, the txHash to get
299-
*/
300-
private async getIndex(
301-
type: typeof IndexType.TxHash,
302-
value: Uint8Array,
303-
): Promise<TxHashIndex | null>
304-
private async getIndex(type: IndexType, value: Uint8Array): Promise<any | null> {
305-
switch (type) {
306-
case IndexType.TxHash: {
307-
const encoded = await this.get(DBKey.TxHash, value)
308-
if (!encoded) return null
309-
return this.rlp(RlpConvert.Decode, RlpType.TxHash, encoded)
310-
}
311-
default:
312-
throw EthereumJSErrorWithoutCode('Unsupported index type')
313-
}
314-
}
315-
316228
/**
317229
* RLP encodes or decodes the specified data type for storage or retrieval from the metaDB
318230
* @param conversion {@link RlpConvert.Encode} or {@link RlpConvert.Decode}
@@ -330,11 +242,6 @@ export class ReceiptsManager extends MetaDBManager {
330242
type: typeof RlpType.Logs,
331243
value: rlpLog[],
332244
): Log[]
333-
private rlp(
334-
conversion: typeof RlpConvert.Decode,
335-
type: typeof RlpType.TxHash,
336-
value: Uint8Array,
337-
): TxHashIndex
338245
private rlp(
339246
conversion: RlpConvert,
340247
type: RlpType,
@@ -380,14 +287,6 @@ export class ReceiptsManager extends MetaDBManager {
380287
} else {
381288
return RLP.decode(value as Uint8Array) as Log[]
382289
}
383-
case RlpType.TxHash:
384-
if (conversion === RlpConvert.Encode) {
385-
const [blockHash, txIndex] = value as TxHashIndex
386-
return RLP.encode([blockHash, intToBytes(txIndex)])
387-
} else {
388-
const [blockHash, txIndex] = RLP.decode(value as Uint8Array) as unknown as rlpTxHash
389-
return [blockHash, bytesToInt(txIndex)] as TxHashIndex
390-
}
391290
default:
392291
throw EthereumJSErrorWithoutCode('Unknown rlp conversion')
393292
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { RLP } from '@ethereumjs/rlp'
2+
import { BIGINT_0, bytesToInt, intToBytes } from '@ethereumjs/util'
3+
import { DBKey, MetaDBManager } from '../util/metaDBManager.ts'
4+
5+
export type TxHashIndex = [blockHash: Uint8Array, txIndex: number]
6+
7+
export type IndexType = (typeof IndexType)[keyof typeof IndexType]
8+
9+
export const IndexType = {
10+
TxHash: 'txhash',
11+
} as const
12+
13+
export type IndexOperation = (typeof IndexOperation)[keyof typeof IndexOperation]
14+
15+
export const IndexOperation = {
16+
Save: 'save',
17+
Delete: 'delete',
18+
} as const
19+
20+
export type rlpTxHash = [blockHash: Uint8Array, txIndex: Uint8Array]
21+
export class TxIndex extends MetaDBManager {
22+
private rlpEncode(value: TxHashIndex): Uint8Array {
23+
const [blockHash, txIndex] = value as TxHashIndex
24+
return RLP.encode([blockHash, intToBytes(txIndex)])
25+
}
26+
private rlpDecode(value: Uint8Array): TxHashIndex {
27+
const [blockHash, txIndex] = RLP.decode(value) as unknown as rlpTxHash
28+
return [blockHash, bytesToInt(txIndex)] as TxHashIndex
29+
}
30+
async updateIndex(operation: IndexOperation, type: IndexType, value: any): Promise<void> {
31+
switch (type) {
32+
case IndexType.TxHash: {
33+
const block = value
34+
if (operation === IndexOperation.Save) {
35+
const withinTxLookupLimit =
36+
this.config.txLookupLimit === 0 ||
37+
this.chain.headers.height - BigInt(this.config.txLookupLimit) < block.header.number
38+
if (withinTxLookupLimit) {
39+
for (const [i, tx] of block.transactions.entries()) {
40+
const index: TxHashIndex = [block.hash(), i]
41+
const encoded = this.rlpEncode(index)
42+
await this.put(DBKey.TxHash, tx.hash(), encoded)
43+
}
44+
}
45+
if (this.config.txLookupLimit > 0) {
46+
// Remove tx hashes for one block past txLookupLimit
47+
const limit = this.chain.headers.height - BigInt(this.config.txLookupLimit)
48+
if (limit < BIGINT_0) return
49+
const blockDelIndexes = await this.chain.getBlock(limit)
50+
void this.updateIndex(IndexOperation.Delete, IndexType.TxHash, blockDelIndexes)
51+
}
52+
} else if (operation === IndexOperation.Delete) {
53+
for (const tx of block.transactions) {
54+
await this.delete(DBKey.TxHash, tx.hash())
55+
}
56+
}
57+
break
58+
}
59+
}
60+
}
61+
62+
/**
63+
* Returns the value for an index or null if not found
64+
* @param value for {@link IndexType.TxHash}, the txHash to get
65+
*/
66+
async getIndex(value: Uint8Array): Promise<TxHashIndex | null> {
67+
const encoded = await this.get(DBKey.TxHash, value)
68+
if (encoded === null) return null
69+
return this.rlpDecode(encoded)
70+
}
71+
}

packages/client/src/execution/vmexecution.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import { Execution } from './execution.ts'
3838
import { LevelDB } from './level.ts'
3939
import { PreimagesManager } from './preimage.ts'
4040
import { ReceiptsManager } from './receipt.ts'
41+
import { IndexOperation, IndexType, TxIndex } from './txIndex.ts'
4142

4243
import type { Block } from '@ethereumjs/block'
4344
import type { PrefixedHexString } from '@ethereumjs/util'
@@ -71,6 +72,7 @@ export class VMExecution extends Execution {
7172

7273
public receiptsManager?: ReceiptsManager
7374
public preimagesManager?: PreimagesManager
75+
public txIndex?: TxIndex
7476
private pendingReceipts?: Map<string, TxReceipt[]>
7577
private vmPromise?: Promise<number | null>
7678

@@ -117,6 +119,11 @@ export class VMExecution extends Execution {
117119
}
118120

119121
if (this.metaDB) {
122+
this.txIndex = new TxIndex({
123+
metaDB: this.metaDB,
124+
chain: this.chain,
125+
config: this.config,
126+
})
120127
if (this.config.saveReceipts) {
121128
this.receiptsManager = new ReceiptsManager({
122129
chain: this.chain,
@@ -130,6 +137,7 @@ export class VMExecution extends Execution {
130137
// Once a block gets deleted from the chain, delete the receipts also
131138
for (const block of blocks) {
132139
await this.receiptsManager?.deleteReceipts(block)
140+
void this.txIndex?.updateIndex(IndexOperation.Delete, IndexType.TxHash, block)
133141
}
134142
if (resolve !== undefined) {
135143
resolve()
@@ -319,7 +327,6 @@ export class VMExecution extends Execution {
319327
await this.vm.stateManager.generateCanonicalGenesis!(genesisState)
320328
}
321329
}
322-
323330
await super.open()
324331
// TODO: Should a run be started to execute any left over blocks?
325332
// void this.run()
@@ -534,6 +541,9 @@ export class VMExecution extends Execution {
534541
await this.receiptsManager?.saveReceipts(block, receipts)
535542
this.pendingReceipts?.delete(bytesToHex(block.hash()))
536543
}
544+
if (this.txIndex) {
545+
void this.txIndex.updateIndex(IndexOperation.Save, IndexType.TxHash, block)
546+
}
537547
}
538548

539549
// check if the head, safe and finalized are now canonical
@@ -740,6 +750,9 @@ export class VMExecution extends Execution {
740750
}
741751

742752
await this.receiptsManager?.saveReceipts(block, result.receipts)
753+
if (this.txIndex) {
754+
void this.txIndex.updateIndex(IndexOperation.Save, IndexType.TxHash, block)
755+
}
743756
if (this.config.savePreimages && result.preimages !== undefined) {
744757
await this.savePreimages(result.preimages)
745758
}

packages/client/src/miner/miner.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import type { CliqueConfig } from '@ethereumjs/common'
1313
import type { Miner as EthashMiner, Solution } from '@ethereumjs/ethash'
1414
import type { Config } from '../config.ts'
1515
import type { VMExecution } from '../execution/index.ts'
16+
import { IndexOperation, IndexType } from '../execution/txIndex.ts'
1617
import type { FullEthereumService } from '../service/index.ts'
1718
import type { FullSynchronizer } from '../sync/index.ts'
1819

@@ -336,6 +337,9 @@ export class Miner {
336337
if (this.config.saveReceipts) {
337338
await this.execution.receiptsManager?.saveReceipts(block, receipts)
338339
}
340+
if (this.execution.txIndex) {
341+
void this.execution.txIndex.updateIndex(IndexOperation.Save, IndexType.TxHash, block)
342+
}
339343
this.config.logger?.info(
340344
`Miner: Sealed block with ${block.transactions.length} txs ${
341345
this.config.chainCommon.consensusType() === ConsensusType.ProofOfWork

packages/client/src/rpc/modules/debug.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -176,9 +176,10 @@ export class Debug {
176176

177177
const opts = validateTracerConfig(config)
178178

179-
const result = await this.service.execution.receiptsManager.getReceiptByTxHash(
180-
hexToBytes(txHash),
181-
)
179+
if (!this.service.execution.txIndex) throw EthereumJSErrorWithoutCode('missing txIndex')
180+
const txHashIndex = await this.service.execution.txIndex.getIndex(hexToBytes(txHash))
181+
if (!txHashIndex) return null
182+
const result = await this.service.execution.receiptsManager.getReceiptByTxHashIndex(txHashIndex)
182183
if (!result) return null
183184
const [_, blockHash, txIndex] = result
184185
const block = await this.service.chain.getBlock(blockHash)
@@ -445,11 +446,10 @@ export class Debug {
445446
const [txHash] = params
446447
if (!this.service.execution.receiptsManager)
447448
throw EthereumJSErrorWithoutCode('missing receiptsManager')
448-
const result = await this.service.execution.receiptsManager.getReceiptByTxHash(
449-
hexToBytes(txHash),
450-
)
451-
if (!result) return null
452-
const [_receipt, blockHash, txIndex] = result
449+
if (!this.service.execution.txIndex) throw EthereumJSErrorWithoutCode('missing txIndex')
450+
const txHashIndex = await this.service.execution.txIndex.getIndex(hexToBytes(txHash))
451+
if (!txHashIndex) return null
452+
const [blockHash, txIndex] = txHashIndex
453453
const block = await this.chain.getBlock(blockHash)
454454
const tx = block.transactions[txIndex]
455455
return bytesToHex(tx.serialize())

0 commit comments

Comments
 (0)