diff --git a/execution_chain/pruner.nim b/execution_chain/pruner.nim index 3e5d1841a0..d22bc56aae 100644 --- a/execution_chain/pruner.nim +++ b/execution_chain/pruner.nim @@ -90,7 +90,11 @@ proc pruneLoop(pruner: BackgroundPrunerRef) {.async: (raises: [CancelledError]). if header.timestamp >= cutoff: break - kvt.deleteBlockBodyAndReceiptsBe(header) + if not kvt.deleteBlockBodyAndReceiptsBe(header): + warn "Background pruner: failed to delete block data", + blkNum = currentBlock + break + currentBlock += 1 blocksSinceSave += 1 diff --git a/execution_chain/pruner/db_utils.nim b/execution_chain/pruner/db_utils.nim index 58cc3da471..71be5d0cb0 100644 --- a/execution_chain/pruner/db_utils.nim +++ b/execution_chain/pruner/db_utils.nim @@ -25,18 +25,21 @@ logScope: # Direct-backend deletion helpers (bypass transaction layer) # ------------------------------------------------------------------------------ -proc deleteTransactionsBe(kvt: KvtDbRef, txRoot: Hash32) = +proc deleteTransactionsBe(kvt: KvtDbRef, txRoot: Hash32): bool = if txRoot == EMPTY_ROOT_HASH: - return + return true kvt.delRangeBe( hashIndexKey(txRoot, 0), hashIndexKey(txRoot, uint16.high), compactRange = true ).isOkOr: warn "pruner: deleteTransactionsBe", txRoot, error + return false -proc deleteReceiptsBe(kvt: KvtDbRef, receiptsRoot: Hash32) = + true + +proc deleteReceiptsBe(kvt: KvtDbRef, receiptsRoot: Hash32): bool = if receiptsRoot == EMPTY_ROOT_HASH: - return + return true kvt.delRangeBe( hashIndexKey(receiptsRoot, 0), @@ -44,24 +47,38 @@ proc deleteReceiptsBe(kvt: KvtDbRef, receiptsRoot: Hash32) = compactRange = true, ).isOkOr: warn "pruner: deleteReceiptsBe", receiptsRoot, error + return false + + true -proc deleteUnclesBe(kvt: KvtDbRef, ommersHash: Hash32) = +proc deleteUnclesBe(kvt: KvtDbRef, ommersHash: Hash32): bool = if ommersHash == EMPTY_UNCLE_HASH: - return + return true + kvt.delBe(genericHashKey(ommersHash).toOpenArray).isOkOr: warn "pruner: deleteUnclesBe", ommersHash, error + return false -proc deleteWithdrawalsBe(kvt: KvtDbRef, withdrawalsRoot: Hash32) = + true + +proc deleteWithdrawalsBe(kvt: KvtDbRef, withdrawalsRoot: Hash32): bool = if withdrawalsRoot == EMPTY_ROOT_HASH: - return + return true + kvt.delBe(withdrawalsKey(withdrawalsRoot).toOpenArray).isOkOr: warn "pruner: deleteWithdrawalsBe", withdrawalsRoot, error + return false + + true -proc deleteBlockBodyAndReceiptsBe*(kvt: KvtDbRef, header: Header) = - kvt.deleteTransactionsBe(header.transactionsRoot) - kvt.deleteUnclesBe(header.ommersHash) +proc deleteBlockBodyAndReceiptsBe*(kvt: KvtDbRef, header: Header): bool = + if not kvt.deleteTransactionsBe(header.transactionsRoot): + return false + if not kvt.deleteUnclesBe(header.ommersHash): + return false if header.withdrawalsRoot.isSome: - kvt.deleteWithdrawalsBe(header.withdrawalsRoot.get()) + if not kvt.deleteWithdrawalsBe(header.withdrawalsRoot.get()): + return false kvt.deleteReceiptsBe(header.receiptsRoot) # ------------------------------------------------------------------------------ diff --git a/tests/test_pruner.nim b/tests/test_pruner.nim index 685aed9898..fd25de1c70 100644 --- a/tests/test_pruner.nim +++ b/tests/test_pruner.nim @@ -271,6 +271,51 @@ suite "Pruner integration tests": for blkNum in 1'u64 .. 3'u64: check bt2.getBlockHeader(BlockNumber blkNum).isOk + test "pruner does not advance tail when backend deletion fails": + let com = env.newCom() + var chain = ForkedChainRef.init(com, baseDistance = 0, persistBatchSize = 1) + let + genesis = Block.init(com.genesisHeader, BlockBody()) + baseTxFrame = com.db.baseTxFrame() + txFrame = baseTxFrame.txFrameBegin + blk1 = txFrame.makeBlk(1, genesis) + blk2 = txFrame.makeBlk(2, blk1) + blk3 = txFrame.makeBlk(3, blk2) + txFrame.dispose() + + check (waitFor chain.importBlock(blk1)).isOk + check (waitFor chain.forkChoice(blk1.blockHash, blk1.blockHash)).isOk + check (waitFor chain.importBlock(blk2)).isOk + check (waitFor chain.forkChoice(blk2.blockHash, blk2.blockHash)).isOk + check (waitFor chain.importBlock(blk3)).isOk + check (waitFor chain.forkChoice(blk3.blockHash, blk3.blockHash)).isOk + + let + kvt = com.db.kvt + hdr1 = com.db.baseTxFrame().getBlockHeader(BlockNumber 1).expect("header exists") + wdRoot1 = hdr1.withdrawalsRoot.get() + originalDelKvpFn = kvt.delKvpFn + + check kvt.hasBe(withdrawalsKey(wdRoot1).toOpenArray) + kvt.putBe(tailIdKey().toOpenArray, BlockNumber(1).toBytesLE()) + + kvt.delKvpFn = + proc(key: openArray[byte]): Result[void, KvtError] {.gcsafe, raises: [].} = + discard key + err(RdbBeDriverDelError) + + let pruner = BackgroundPrunerRef.init(com, + batchSize = 10, + loopDelay = chronos.milliseconds(50)) + pruner.start() + + waitFor sleepAsync(chronos.milliseconds(150)) + waitFor pruner.stop() + kvt.delKvpFn = originalDelKvpFn + + check kvt.getHistoryExpiredBe() == BlockNumber(1) + check kvt.hasBe(withdrawalsKey(wdRoot1).toOpenArray) + test "getHistoryExpiredBe returns 0 when not set": let com = env.newCom() let kvt = com.db.kvt