Skip to content

fix(utxo/aerospike): surface BatchOperate error in storeExternallyWithLock#784

Merged
ordishs merged 1 commit into
bsv-blockchain:mainfrom
ordishs:security/4588-batchoperate-error-bubble
May 5, 2026
Merged

fix(utxo/aerospike): surface BatchOperate error in storeExternallyWithLock#784
ordishs merged 1 commit into
bsv-blockchain:mainfrom
ordishs:security/4588-batchoperate-error-bubble

Conversation

@ordishs

@ordishs ordishs commented Apr 29, 2026

Copy link
Copy Markdown
Collaborator

Closes #4588.

Summary

storeExternallyWithLock discarded the top-level error from s.client.BatchOperate(...) with _ =. If the entire batch call failed (connection lost, timeout), per-record error checking ran against an unpopulated result set, hasFailures stayed false, and the function reported transaction creation as successful even though no records were written.

Fix

Capture the top-level error from BatchOperate. On non-nil, send a wrapped ProcessingError to bItem.done and return immediately. Per-record error iteration is unchanged for the success-of-call path.

A small test seam (batchOperateFn field + SetBatchOperateFn helper) was added so the regression test can drive the BatchOperate-failure path without TestContainers-level connection disruption. Production code path defaults to s.client.BatchOperate when the override is unset.

Test plan

  • Regression test injects a network-error aerospike.Error via the test seam; asserts bItem.done receives a non-nil ProcessingError referencing the tx hash.
  • Verified test fails on the unpatched code (top-level error swallowed, bItem.done got nil).
  • go test -race ./stores/utxo/aerospike/... passes.

@github-actions

github-actions Bot commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

🤖 Claude Code Review

Status: Complete

This PR fixes a critical bug where top-level BatchOperate failures were silently ignored, causing transactions to appear successful when they actually failed to write to Aerospike.

Review Result: No issues found

The implementation is solid:

  • Correctly captures and surfaces BatchOperate errors to bItem.done
  • Uses appropriate ProcessingError wrapper with tx hash for debugging
  • Test seam (batchOperateFn) is well-designed: production default, test-only override
  • Regression test validates the fix without TestContainers complexity
  • Error formatting is consistent with existing patterns in the file

@sonarqubecloud

Copy link
Copy Markdown

@github-actions

Copy link
Copy Markdown
Contributor

Benchmark Comparison Report

Baseline: main (unknown)

Current: PR-784 (29c0550)

Summary

  • Regressions: 0
  • Improvements: 0
  • Unchanged: 142
  • Significance level: p < 0.05
All benchmark results (sec/op)
Benchmark Baseline Current Change p-value
_NewBlockFromBytes-4 1.672µ 1.965µ ~ 0.200
SplitSyncedParentMap_SetIfNotExists/256_buckets-4 61.79n 61.49n ~ 0.700
SplitSyncedParentMap_SetIfNotExists/16_buckets-4 61.44n 61.60n ~ 0.400
SplitSyncedParentMap_SetIfNotExists/1_bucket-4 61.54n 61.78n ~ 0.300
SplitSyncedParentMap_ConcurrentSetIfNotExists/256_buckets... 31.13n 30.28n ~ 0.100
SplitSyncedParentMap_ConcurrentSetIfNotExists/16_buckets_... 53.11n 51.74n ~ 0.100
SplitSyncedParentMap_ConcurrentSetIfNotExists/1_bucket_pa... 106.5n 107.6n ~ 0.700
MiningCandidate_Stringify_Short-4 262.7n 265.7n ~ 0.200
MiningCandidate_Stringify_Long-4 1.874µ 1.902µ ~ 0.100
MiningSolution_Stringify-4 951.1n 974.6n ~ 0.100
BlockInfo_MarshalJSON-4 1.785µ 1.777µ ~ 0.600
NewFromBytes-4 129.8n 152.4n ~ 0.700
Mine_EasyDifficulty-4 59.20µ 58.65µ ~ 0.700
Mine_WithAddress-4 4.748µ 4.743µ ~ 0.700
DirectSubtreeAdd/4_per_subtree-4 59.98n 64.48n ~ 0.100
DirectSubtreeAdd/64_per_subtree-4 29.24n 29.15n ~ 0.400
DirectSubtreeAdd/256_per_subtree-4 27.48n 27.69n ~ 0.700
DirectSubtreeAdd/1024_per_subtree-4 26.45n 26.54n ~ 0.700
DirectSubtreeAdd/2048_per_subtree-4 26.16n 25.97n ~ 0.100
SubtreeProcessorAdd/4_per_subtree-4 315.0n 314.1n ~ 0.200
SubtreeProcessorAdd/64_per_subtree-4 313.1n 311.5n ~ 1.000
SubtreeProcessorAdd/256_per_subtree-4 316.7n 311.8n ~ 0.200
SubtreeProcessorAdd/1024_per_subtree-4 316.1n 313.1n ~ 0.700
SubtreeProcessorAdd/2048_per_subtree-4 311.6n 315.9n ~ 0.700
SubtreeProcessorRotate/4_per_subtree-4 313.4n 316.0n ~ 0.200
SubtreeProcessorRotate/64_per_subtree-4 316.2n 318.7n ~ 1.000
SubtreeProcessorRotate/256_per_subtree-4 316.4n 315.5n ~ 0.700
SubtreeProcessorRotate/1024_per_subtree-4 307.8n 307.7n ~ 1.000
SubtreeNodeAddOnly/4_per_subtree-4 66.81n 66.34n ~ 0.400
SubtreeNodeAddOnly/64_per_subtree-4 39.13n 39.49n ~ 0.100
SubtreeNodeAddOnly/256_per_subtree-4 37.80n 37.93n ~ 0.700
SubtreeNodeAddOnly/1024_per_subtree-4 37.14n 37.51n ~ 0.200
SubtreeCreationOnly/4_per_subtree-4 163.8n 164.0n ~ 1.000
SubtreeCreationOnly/64_per_subtree-4 642.6n 650.6n ~ 0.700
SubtreeCreationOnly/256_per_subtree-4 2.100µ 2.093µ ~ 1.000
SubtreeCreationOnly/1024_per_subtree-4 5.701µ 5.541µ ~ 1.000
SubtreeCreationOnly/2048_per_subtree-4 8.864µ 8.250µ ~ 0.700
SubtreeProcessorOverheadBreakdown/64_per_subtree-4 308.5n 305.9n ~ 0.700
SubtreeProcessorOverheadBreakdown/1024_per_subtree-4 310.2n 313.1n ~ 0.700
ParallelGetAndSetIfNotExists/1k_nodes-4 980.2µ 1012.9µ ~ 0.200
ParallelGetAndSetIfNotExists/10k_nodes-4 2.025m 1.939m ~ 0.100
ParallelGetAndSetIfNotExists/50k_nodes-4 8.943m 9.005m ~ 0.700
ParallelGetAndSetIfNotExists/100k_nodes-4 18.04m 17.43m ~ 0.200
SequentialGetAndSetIfNotExists/1k_nodes-4 789.7µ 768.2µ ~ 0.200
SequentialGetAndSetIfNotExists/10k_nodes-4 3.102m 2.996m ~ 0.400
SequentialGetAndSetIfNotExists/50k_nodes-4 10.69m 10.90m ~ 0.200
SequentialGetAndSetIfNotExists/100k_nodes-4 20.30m 20.77m ~ 0.100
ProcessOwnBlockSubtreeNodesParallel/1k_nodes-4 1.049m 1.037m ~ 0.700
ProcessOwnBlockSubtreeNodesParallel/10k_nodes-4 4.845m 4.846m ~ 0.700
ProcessOwnBlockSubtreeNodesParallel/100k_nodes-4 19.29m 19.50m ~ 0.400
ProcessOwnBlockSubtreeNodesSequential/1k_nodes-4 861.8µ 854.6µ ~ 0.400
ProcessOwnBlockSubtreeNodesSequential/10k_nodes-4 6.293m 6.308m ~ 0.700
ProcessOwnBlockSubtreeNodesSequential/100k_nodes-4 39.89m 40.23m ~ 0.100
DiskTxMap_SetIfNotExists-4 3.544µ 3.513µ ~ 0.400
DiskTxMap_SetIfNotExists_Parallel-4 3.369µ 3.450µ ~ 0.400
DiskTxMap_ExistenceOnly-4 302.5n 303.9n ~ 0.700
Queue-4 193.8n 194.1n ~ 1.000
AtomicPointer-4 4.483n 4.493n ~ 0.700
ReorgOptimizations/DedupFilterPipeline/Old/10K-4 878.0µ 854.7µ ~ 0.100
ReorgOptimizations/DedupFilterPipeline/New/10K-4 834.7µ 850.7µ ~ 0.100
ReorgOptimizations/AllMarkFalse/Old/10K-4 123.3µ 106.1µ ~ 0.100
ReorgOptimizations/AllMarkFalse/New/10K-4 62.61µ 62.23µ ~ 1.000
ReorgOptimizations/HashSlicePool/Old/10K-4 65.55µ 68.67µ ~ 1.000
ReorgOptimizations/HashSlicePool/New/10K-4 11.65µ 11.22µ ~ 0.100
ReorgOptimizations/NodeFlags/Old/10K-4 5.377µ 5.399µ ~ 1.000
ReorgOptimizations/NodeFlags/New/10K-4 1.808µ 1.868µ ~ 1.000
ReorgOptimizations/DedupFilterPipeline/Old/100K-4 10.10m 10.06m ~ 1.000
ReorgOptimizations/DedupFilterPipeline/New/100K-4 9.966m 11.256m ~ 0.100
ReorgOptimizations/AllMarkFalse/Old/100K-4 1.115m 1.186m ~ 0.200
ReorgOptimizations/AllMarkFalse/New/100K-4 690.0µ 683.0µ ~ 0.100
ReorgOptimizations/HashSlicePool/Old/100K-4 644.4µ 583.7µ ~ 0.100
ReorgOptimizations/HashSlicePool/New/100K-4 343.1µ 302.4µ ~ 0.100
ReorgOptimizations/NodeFlags/Old/100K-4 55.49µ 54.69µ ~ 1.000
ReorgOptimizations/NodeFlags/New/100K-4 19.87µ 19.33µ ~ 0.700
TxMapSetIfNotExists-4 51.64n 51.75n ~ 1.000
TxMapSetIfNotExistsDuplicate-4 37.99n 38.15n ~ 0.400
ChannelSendReceive-4 589.5n 593.5n ~ 0.400
BlockAssembler_AddTx-4 0.02137n 0.02242n ~ 0.100
AddNode-4 9.399 9.519 ~ 0.700
AddNodeWithMap-4 8.623 9.020 ~ 0.100
CalcBlockWork-4 501.2n 502.6n ~ 1.000
CalculateWork-4 674.6n 675.6n ~ 0.400
BuildBlockLocatorString_Helpers/Size_10-4 1.346µ 1.309µ ~ 0.400
BuildBlockLocatorString_Helpers/Size_100-4 13.59µ 13.26µ ~ 0.700
BuildBlockLocatorString_Helpers/Size_1000-4 125.7µ 124.1µ ~ 0.100
CatchupWithHeaderCache-4 104.2m 104.2m ~ 0.700
_BufferPoolAllocation/16KB-4 3.265µ 3.304µ ~ 0.700
_BufferPoolAllocation/32KB-4 8.989µ 7.499µ ~ 0.700
_BufferPoolAllocation/64KB-4 15.65µ 17.57µ ~ 0.400
_BufferPoolAllocation/128KB-4 30.49µ 29.20µ ~ 0.200
_BufferPoolAllocation/512KB-4 104.1µ 114.9µ ~ 0.100
_BufferPoolConcurrent/32KB-4 16.41µ 18.99µ ~ 0.100
_BufferPoolConcurrent/64KB-4 25.57µ 31.06µ ~ 0.100
_BufferPoolConcurrent/512KB-4 136.1µ 145.5µ ~ 0.200
_SubtreeDeserializationWithBufferSizes/16KB-4 640.2µ 681.3µ ~ 0.100
_SubtreeDeserializationWithBufferSizes/32KB-4 637.7µ 670.8µ ~ 0.100
_SubtreeDeserializationWithBufferSizes/64KB-4 661.7µ 666.6µ ~ 0.400
_SubtreeDeserializationWithBufferSizes/128KB-4 638.9µ 668.4µ ~ 0.100
_SubtreeDeserializationWithBufferSizes/512KB-4 667.5µ 652.2µ ~ 0.700
_SubtreeDataDeserializationWithBufferSizes/16KB-4 35.32m 35.67m ~ 0.400
_SubtreeDataDeserializationWithBufferSizes/32KB-4 35.07m 35.74m ~ 0.200
_SubtreeDataDeserializationWithBufferSizes/64KB-4 35.06m 35.28m ~ 1.000
_SubtreeDataDeserializationWithBufferSizes/128KB-4 34.96m 35.82m ~ 0.200
_SubtreeDataDeserializationWithBufferSizes/512KB-4 34.81m 35.44m ~ 0.400
_PooledVsNonPooled/Pooled-4 675.2n 832.9n ~ 0.100
_PooledVsNonPooled/NonPooled-4 6.418µ 6.876µ ~ 0.100
_MemoryFootprint/Current_512KB_32concurrent-4 6.635µ 6.791µ ~ 0.100
_MemoryFootprint/Proposed_32KB_32concurrent-4 9.053µ 10.248µ ~ 0.100
_MemoryFootprint/Alternative_64KB_32concurrent-4 8.915µ 9.196µ ~ 0.100
_prepareTxsPerLevel-4 394.5m 393.3m ~ 1.000
_prepareTxsPerLevelOrdered-4 3.652m 4.155m ~ 0.700
_prepareTxsPerLevel_Comparison/Original-4 409.9m 405.1m ~ 0.100
_prepareTxsPerLevel_Comparison/Optimized-4 3.555m 3.612m ~ 0.400
SubtreeSizes/10k_tx_4_per_subtree-4 1.390m 1.397m ~ 0.700
SubtreeSizes/10k_tx_16_per_subtree-4 324.0µ 330.0µ ~ 0.400
SubtreeSizes/10k_tx_64_per_subtree-4 79.79µ 80.29µ ~ 0.400
SubtreeSizes/10k_tx_256_per_subtree-4 19.99µ 20.07µ ~ 0.700
SubtreeSizes/10k_tx_512_per_subtree-4 9.992µ 10.014µ ~ 0.700
SubtreeSizes/10k_tx_1024_per_subtree-4 4.950µ 5.016µ ~ 0.100
SubtreeSizes/10k_tx_2k_per_subtree-4 2.479µ 2.483µ ~ 0.800
BlockSizeScaling/10k_tx_64_per_subtree-4 78.33µ 78.81µ ~ 0.100
BlockSizeScaling/10k_tx_256_per_subtree-4 20.17µ 20.08µ ~ 1.000
BlockSizeScaling/10k_tx_1024_per_subtree-4 4.951µ 4.955µ ~ 1.000
BlockSizeScaling/50k_tx_64_per_subtree-4 397.0µ 413.3µ ~ 0.700
BlockSizeScaling/50k_tx_256_per_subtree-4 100.8µ 100.8µ ~ 1.000
BlockSizeScaling/50k_tx_1024_per_subtree-4 24.72µ 24.97µ ~ 0.400
SubtreeAllocations/small_subtrees_exists_check-4 162.0µ 165.8µ ~ 0.100
SubtreeAllocations/small_subtrees_data_fetch-4 169.9µ 172.2µ ~ 0.100
SubtreeAllocations/small_subtrees_full_validation-4 330.8µ 335.4µ ~ 0.100
SubtreeAllocations/medium_subtrees_exists_check-4 9.995µ 10.027µ ~ 1.000
SubtreeAllocations/medium_subtrees_data_fetch-4 10.60µ 10.71µ ~ 0.200
SubtreeAllocations/medium_subtrees_full_validation-4 20.52µ 20.67µ ~ 1.000
SubtreeAllocations/large_subtrees_exists_check-4 2.452µ 2.437µ ~ 0.700
SubtreeAllocations/large_subtrees_data_fetch-4 2.647µ 2.630µ ~ 1.000
SubtreeAllocations/large_subtrees_full_validation-4 5.141µ 5.137µ ~ 0.400
StoreBlock_Sequential/BelowCSVHeight-4 332.7µ 312.5µ ~ 0.100
StoreBlock_Sequential/AboveCSVHeight-4 335.6µ 337.4µ ~ 0.700
GetUtxoHashes-4 270.0n 273.3n ~ 0.200
GetUtxoHashes_ManyOutputs-4 52.49µ 46.41µ ~ 0.100
_NewMetaDataFromBytes-4 230.5n 242.6n ~ 0.100
_Bytes-4 611.4n 655.0n ~ 0.100
_MetaBytes-4 564.6n 568.2n ~ 0.400

Threshold: >10% with p < 0.05 | Generated: 2026-04-29 03:12 UTC

@ordishs ordishs requested a review from freemans13 May 4, 2026 09:29
@ordishs ordishs enabled auto-merge (squash) May 4, 2026 09:30
@ordishs ordishs merged commit 14a3dd9 into bsv-blockchain:main May 5, 2026
25 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants