Skip to content

fix(legacy/netsync): chunk SetMinedMulti in createUtxos (#936)#937

Merged
oskarszoon merged 5 commits into
bsv-blockchain:mainfrom
oskarszoon:fix/teranode-mined-batch
May 26, 2026
Merged

fix(legacy/netsync): chunk SetMinedMulti in createUtxos (#936)#937
oskarszoon merged 5 commits into
bsv-blockchain:mainfrom
oskarszoon:fix/teranode-mined-batch

Conversation

@oskarszoon

@oskarszoon oskarszoon commented May 24, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Replace the unbounded SetMinedMulti call at the tail of createUtxos with a caller-side worker pool: divide existingTxHashes into UtxoStore.MaxMinedRoutines ranges, each worker iterates its range in UtxoStore.MaxMinedBatchSize chunks. Pattern mirrors stores/utxo/aerospike/longest_chain.go:MarkTransactionsOnLongestChain.
  • Fail-fast errgroup.WithContext semantics — matches the existing Create() loop already in createUtxos. First chunk failure cancels siblings via gCtx; wrapped ProcessingError propagated up.
  • Regression introduced by fix(legacy): merge blockID into pre-existing tx in createUtxos #854 (fix(legacy): merge blockID into pre-existing tx in createUtxos) which moved the per-block merge into the synchronous critical path without chunking. On block 755880 (2.87M txs, almost all pre-existing via propagation) the monolithic 2.87M-record BatchOperate exhausts the aerospike client connection pool (ConnectionQueueSize=16 on .docker.m), hits MAX_RETRIES_EXCEEDED / NETWORK_ERROR / connection reset by peer, and stalls mainnet sync in an infinite retry loop.

Fixes #936.

Test plan

  • go test -race ./services/legacy/netsync/... passes (88/88).
  • go vet ./services/legacy/netsync/... clean.
  • golangci-lint run ./services/legacy/netsync/... clean.
  • New TestSyncManager_createUtxos_ChunksExistingTxs asserts call count, per-chunk size bound, and union coverage with MaxMinedBatchSize=4 / MaxMinedRoutines=2 / 10 txs.
  • New TestSyncManager_createUtxos_ChunkErrorPropagates asserts wrapped ProcessingError and sibling short-circuit on first chunk failure.
  • Pre-existing sqlitememory-backed TestSyncManager_createUtxos_MergesBlockIDsForExistingTxs left untouched and still green (1-tx case flows through new path as 1 worker × 1 chunk).
  • Resume mainnet sync on bsva-ovh-teranode-eu-3 at block 755880; expect [createUtxos] merging blockID … into 2867287 pre-existing tx(s) followed by completion within the existing HandleBlockDirect deadline (post-merge field validation).

…in#936)

createUtxos was calling SetMinedMulti with one unbounded slice containing every
pre-existing tx in the block. On fat mainnet blocks (e.g. 755880 = 2.87M txs)
this monolithic batch exhausts the aerospike client connection pool and stalls
sync in a MAX_RETRIES_EXCEEDED retry loop. Regression from bsv-blockchain#854.

Split the merge across a worker pool bounded by UtxoStore.MaxMinedRoutines,
each worker iterating its range in MaxMinedBatchSize chunks. Mirrors the
proven MarkTransactionsOnLongestChain pattern.

Fail-fast errgroup semantics match the existing createUtxos Create() loop:
first chunk error cancels siblings via gCtx and propagates a wrapped
ProcessingError.

Fixes bsv-blockchain#936
- Restore tx count in error wrap for operator forensics
- Fix incorrect chunk-count comment (4 chunks not 3 with 2 workers)
- Drop redundant inline comments per repo conventions
ChunkErrorPropagates with 20 txs / batchSize=4 / 2 workers yields 6 chunks
(2 workers × ceil(10/4) chunks each), not 5. The previous comment carried
the pre-worker-pool math.
@oskarszoon oskarszoon requested a review from icellan May 24, 2026 14:45
@github-actions

github-actions Bot commented May 24, 2026

Copy link
Copy Markdown
Contributor

🤖 Claude Code Review

Status: Complete

No issues found. This PR successfully addresses the connection pool exhaustion issue (#936) by chunking SetMinedMulti calls in createUtxos.

Strengths:

  • Follows proven pattern from MarkTransactionsOnLongestChain (stores/utxo/aerospike/longest_chain.go:36-145)
  • Fail-fast errgroup semantics match existing Create() loop in same function
  • Comprehensive test coverage: 7 new tests including chunking verification, error propagation, cancellation semantics, boundary cases (exact/one-over batch size), and defensive clamps (zero values)
  • Mutation-resistant test design (ChunkFailureCancelsSiblings) verified across multiple interleavings
  • Clear inline documentation explaining rationale and referencing issue legacy: createUtxos calls SetMinedMulti with unbounded slice — stalls aerospike on fat blocks (regression from #854) #936

@oskarszoon oskarszoon requested review from freemans13 and ordishs May 24, 2026 14:56
@github-actions

github-actions Bot commented May 24, 2026

Copy link
Copy Markdown
Contributor

Benchmark Comparison Report

Baseline: main (unknown)

Current: PR-937 (1f37a87)

Summary

  • Regressions: 0
  • Improvements: 0
  • Unchanged: 144
  • Significance level: p < 0.05
All benchmark results (sec/op)
Benchmark Baseline Current Change p-value
_NewBlockFromBytes-4 1.770µ 1.921µ ~ 0.700
SplitSyncedParentMap_SetIfNotExists/256_buckets-4 59.51n 59.22n ~ 0.200
SplitSyncedParentMap_SetIfNotExists/16_buckets-4 59.33n 59.20n ~ 1.000
SplitSyncedParentMap_SetIfNotExists/1_bucket-4 59.40n 59.26n ~ 0.200
SplitSyncedParentMap_ConcurrentSetIfNotExists/256_buckets... 35.96n 34.39n ~ 0.100
SplitSyncedParentMap_ConcurrentSetIfNotExists/16_buckets_... 60.28n 56.73n ~ 0.100
SplitSyncedParentMap_ConcurrentSetIfNotExists/1_bucket_pa... 157.3n 161.2n ~ 0.700
MiningCandidate_Stringify_Short-4 256.3n 261.2n ~ 0.200
MiningCandidate_Stringify_Long-4 1.777µ 1.775µ ~ 0.600
MiningSolution_Stringify-4 938.8n 944.7n ~ 0.700
BlockInfo_MarshalJSON-4 1.784µ 1.791µ ~ 0.300
NewFromBytes-4 123.0n 123.1n ~ 1.000
AddTxBatchColumnar_Validation-4 2.540µ 2.536µ ~ 0.700
OffsetValidationLoop-4 546.9n 544.1n ~ 0.400
Mine_EasyDifficulty-4 60.29µ 60.27µ ~ 1.000
Mine_WithAddress-4 6.684µ 6.707µ ~ 0.700
BlockAssembler_AddTx-4 0.02652n 0.02632n ~ 1.000
AddNode-4 10.70 10.57 ~ 0.700
AddNodeWithMap-4 11.27 11.56 ~ 0.100
DirectSubtreeAdd/4_per_subtree-4 57.99n 57.52n ~ 1.000
DirectSubtreeAdd/64_per_subtree-4 29.23n 28.97n ~ 0.300
DirectSubtreeAdd/256_per_subtree-4 27.83n 28.40n ~ 0.700
DirectSubtreeAdd/1024_per_subtree-4 26.46n 26.56n ~ 0.100
DirectSubtreeAdd/2048_per_subtree-4 26.15n 26.16n ~ 0.500
SubtreeProcessorAdd/4_per_subtree-4 295.1n 290.8n ~ 0.400
SubtreeProcessorAdd/64_per_subtree-4 291.9n 287.2n ~ 0.200
SubtreeProcessorAdd/256_per_subtree-4 283.7n 284.9n ~ 1.000
SubtreeProcessorAdd/1024_per_subtree-4 276.5n 280.5n ~ 0.100
SubtreeProcessorAdd/2048_per_subtree-4 284.0n 280.4n ~ 0.700
SubtreeProcessorRotate/4_per_subtree-4 289.4n 287.0n ~ 1.000
SubtreeProcessorRotate/64_per_subtree-4 282.2n 282.6n ~ 0.700
SubtreeProcessorRotate/256_per_subtree-4 275.8n 282.7n ~ 0.100
SubtreeProcessorRotate/1024_per_subtree-4 276.1n 283.5n ~ 0.400
SubtreeNodeAddOnly/4_per_subtree-4 54.92n 54.81n ~ 1.000
SubtreeNodeAddOnly/64_per_subtree-4 36.22n 36.19n ~ 1.000
SubtreeNodeAddOnly/256_per_subtree-4 35.06n 34.96n ~ 0.700
SubtreeNodeAddOnly/1024_per_subtree-4 34.38n 34.43n ~ 0.700
SubtreeCreationOnly/4_per_subtree-4 109.9n 112.3n ~ 0.200
SubtreeCreationOnly/64_per_subtree-4 348.0n 346.8n ~ 0.600
SubtreeCreationOnly/256_per_subtree-4 1.219µ 1.227µ ~ 0.300
SubtreeCreationOnly/1024_per_subtree-4 3.815µ 3.826µ ~ 0.100
SubtreeCreationOnly/2048_per_subtree-4 6.794µ 6.879µ ~ 0.100
SubtreeProcessorOverheadBreakdown/64_per_subtree-4 278.8n 279.0n ~ 1.000
SubtreeProcessorOverheadBreakdown/1024_per_subtree-4 282.4n 281.5n ~ 1.000
ParallelGetAndSetIfNotExists/1k_nodes-4 1.999m 1.996m ~ 1.000
ParallelGetAndSetIfNotExists/10k_nodes-4 5.101m 5.114m ~ 0.700
ParallelGetAndSetIfNotExists/50k_nodes-4 7.182m 7.208m ~ 0.400
ParallelGetAndSetIfNotExists/100k_nodes-4 9.788m 10.496m ~ 0.100
SequentialGetAndSetIfNotExists/1k_nodes-4 1.772m 1.780m ~ 0.200
SequentialGetAndSetIfNotExists/10k_nodes-4 4.455m 4.478m ~ 0.700
SequentialGetAndSetIfNotExists/50k_nodes-4 13.57m 15.67m ~ 0.100
SequentialGetAndSetIfNotExists/100k_nodes-4 25.13m 29.40m ~ 0.100
ProcessOwnBlockSubtreeNodesParallel/1k_nodes-4 2.046m 2.075m ~ 0.400
ProcessOwnBlockSubtreeNodesParallel/10k_nodes-4 8.333m 8.335m ~ 1.000
ProcessOwnBlockSubtreeNodesParallel/100k_nodes-4 13.41m 13.62m ~ 0.100
ProcessOwnBlockSubtreeNodesSequential/1k_nodes-4 1.801m 1.785m ~ 0.700
ProcessOwnBlockSubtreeNodesSequential/10k_nodes-4 8.026m 8.523m ~ 0.100
ProcessOwnBlockSubtreeNodesSequential/100k_nodes-4 43.21m 46.46m ~ 0.100
DiskTxMap_SetIfNotExists-4 3.750µ 3.930µ ~ 0.700
DiskTxMap_SetIfNotExists_Parallel-4 3.703µ 3.514µ ~ 0.200
DiskTxMap_ExistenceOnly-4 326.9n 338.9n ~ 0.700
Queue-4 184.2n 187.6n ~ 0.400
AtomicPointer-4 3.286n 3.249n ~ 0.100
ReorgOptimizations/DedupFilterPipeline/Old/10K-4 816.4µ 824.3µ ~ 0.400
ReorgOptimizations/DedupFilterPipeline/New/10K-4 803.8µ 775.6µ ~ 0.100
ReorgOptimizations/AllMarkFalse/Old/10K-4 105.2µ 103.4µ ~ 0.100
ReorgOptimizations/AllMarkFalse/New/10K-4 63.85µ 63.96µ ~ 0.700
ReorgOptimizations/HashSlicePool/Old/10K-4 57.01µ 51.15µ ~ 0.100
ReorgOptimizations/HashSlicePool/New/10K-4 11.40µ 10.99µ ~ 0.100
ReorgOptimizations/NodeFlags/Old/10K-4 4.579µ 4.818µ ~ 0.100
ReorgOptimizations/NodeFlags/New/10K-4 1.537µ 1.704µ ~ 0.100
ReorgOptimizations/DedupFilterPipeline/Old/100K-4 9.871m 9.562m ~ 0.400
ReorgOptimizations/DedupFilterPipeline/New/100K-4 10.126m 9.839m ~ 0.400
ReorgOptimizations/AllMarkFalse/Old/100K-4 1.092m 1.075m ~ 0.200
ReorgOptimizations/AllMarkFalse/New/100K-4 703.5µ 706.0µ ~ 0.100
ReorgOptimizations/HashSlicePool/Old/100K-4 519.4µ 570.9µ ~ 0.200
ReorgOptimizations/HashSlicePool/New/100K-4 204.0µ 209.0µ ~ 0.700
ReorgOptimizations/NodeFlags/Old/100K-4 42.29µ 49.34µ ~ 0.100
ReorgOptimizations/NodeFlags/New/100K-4 14.07µ 16.89µ ~ 0.100
TxMapSetIfNotExists-4 49.27n 49.60n ~ 0.700
TxMapSetIfNotExistsDuplicate-4 41.29n 41.41n ~ 0.600
ChannelSendReceive-4 616.0n 592.3n ~ 0.100
CalcBlockWork-4 463.3n 465.8n ~ 0.200
CalculateWork-4 663.9n 626.2n ~ 0.800
BuildBlockLocatorString_Helpers/Size_10-4 1.332µ 1.354µ ~ 0.800
BuildBlockLocatorString_Helpers/Size_100-4 12.99µ 13.01µ ~ 0.400
BuildBlockLocatorString_Helpers/Size_1000-4 148.9µ 136.1µ ~ 0.700
CatchupWithHeaderCache-4 104.3m 104.1m ~ 0.700
SubtreeSizes/10k_tx_4_per_subtree-4 1.325m 1.312m ~ 0.700
SubtreeSizes/10k_tx_16_per_subtree-4 320.3µ 310.8µ ~ 0.400
SubtreeSizes/10k_tx_64_per_subtree-4 74.65µ 73.76µ ~ 0.700
SubtreeSizes/10k_tx_256_per_subtree-4 18.57µ 18.60µ ~ 1.000
SubtreeSizes/10k_tx_512_per_subtree-4 9.368µ 9.364µ ~ 0.800
SubtreeSizes/10k_tx_1024_per_subtree-4 4.702µ 4.636µ ~ 0.700
SubtreeSizes/10k_tx_2k_per_subtree-4 2.322µ 2.334µ ~ 0.400
BlockSizeScaling/10k_tx_64_per_subtree-4 73.72µ 74.02µ ~ 0.400
BlockSizeScaling/10k_tx_256_per_subtree-4 18.76µ 18.47µ ~ 0.100
BlockSizeScaling/10k_tx_1024_per_subtree-4 4.674µ 4.639µ ~ 0.200
BlockSizeScaling/50k_tx_64_per_subtree-4 390.7µ 393.6µ ~ 1.000
BlockSizeScaling/50k_tx_256_per_subtree-4 92.29µ 94.81µ ~ 0.400
BlockSizeScaling/50k_tx_1024_per_subtree-4 22.90µ 22.97µ ~ 1.000
SubtreeAllocations/small_subtrees_exists_check-4 158.7µ 158.7µ ~ 1.000
SubtreeAllocations/small_subtrees_data_fetch-4 160.8µ 162.0µ ~ 0.700
SubtreeAllocations/small_subtrees_full_validation-4 321.8µ 322.4µ ~ 0.700
SubtreeAllocations/medium_subtrees_exists_check-4 9.499µ 9.505µ ~ 1.000
SubtreeAllocations/medium_subtrees_data_fetch-4 9.338µ 9.269µ ~ 0.400
SubtreeAllocations/medium_subtrees_full_validation-4 18.67µ 18.70µ ~ 0.700
SubtreeAllocations/large_subtrees_exists_check-4 2.277µ 2.263µ ~ 0.600
SubtreeAllocations/large_subtrees_data_fetch-4 2.259µ 2.273µ ~ 0.400
SubtreeAllocations/large_subtrees_full_validation-4 4.659µ 4.681µ ~ 0.700
_BufferPoolAllocation/16KB-4 3.866µ 3.791µ ~ 0.400
_BufferPoolAllocation/32KB-4 8.357µ 8.786µ ~ 0.700
_BufferPoolAllocation/64KB-4 14.96µ 16.07µ ~ 0.200
_BufferPoolAllocation/128KB-4 25.35µ 23.92µ ~ 1.000
_BufferPoolAllocation/512KB-4 108.0µ 102.9µ ~ 0.400
_BufferPoolConcurrent/32KB-4 20.08µ 18.57µ ~ 0.100
_BufferPoolConcurrent/64KB-4 28.31µ 28.82µ ~ 0.700
_BufferPoolConcurrent/512KB-4 138.8µ 140.0µ ~ 0.400
_SubtreeDeserializationWithBufferSizes/16KB-4 603.3µ 656.3µ ~ 0.100
_SubtreeDeserializationWithBufferSizes/32KB-4 606.0µ 665.9µ ~ 0.100
_SubtreeDeserializationWithBufferSizes/64KB-4 599.7µ 665.9µ ~ 0.100
_SubtreeDeserializationWithBufferSizes/128KB-4 582.7µ 651.3µ ~ 0.100
_SubtreeDeserializationWithBufferSizes/512KB-4 582.0µ 606.7µ ~ 0.100
_SubtreeDataDeserializationWithBufferSizes/16KB-4 35.84m 36.35m ~ 0.100
_SubtreeDataDeserializationWithBufferSizes/32KB-4 35.61m 36.04m ~ 0.400
_SubtreeDataDeserializationWithBufferSizes/64KB-4 35.90m 36.03m ~ 0.700
_SubtreeDataDeserializationWithBufferSizes/128KB-4 35.86m 36.08m ~ 0.700
_SubtreeDataDeserializationWithBufferSizes/512KB-4 35.69m 35.66m ~ 1.000
_PooledVsNonPooled/Pooled-4 832.5n 831.1n ~ 0.400
_PooledVsNonPooled/NonPooled-4 7.353µ 7.037µ ~ 0.100
_MemoryFootprint/Current_512KB_32concurrent-4 6.444µ 6.589µ ~ 0.100
_MemoryFootprint/Proposed_32KB_32concurrent-4 9.173µ 9.497µ ~ 0.100
_MemoryFootprint/Alternative_64KB_32concurrent-4 9.053µ 9.082µ ~ 0.700
_prepareTxsPerLevel-4 403.4m 404.0m ~ 1.000
_prepareTxsPerLevelOrdered-4 3.613m 3.865m ~ 0.400
_prepareTxsPerLevel_Comparison/Original-4 398.9m 400.0m ~ 1.000
_prepareTxsPerLevel_Comparison/Optimized-4 3.511m 3.545m ~ 1.000
StoreBlock_Sequential/BelowCSVHeight-4 329.4µ 334.2µ ~ 0.100
StoreBlock_Sequential/AboveCSVHeight-4 331.7µ 340.0µ ~ 0.100
GetUtxoHashes-4 272.1n 271.3n ~ 0.700
GetUtxoHashes_ManyOutputs-4 45.95µ 46.10µ ~ 0.400
_NewMetaDataFromBytes-4 215.4n 215.2n ~ 1.000
_Bytes-4 408.3n 403.3n ~ 0.200
_MetaBytes-4 141.5n 139.5n ~ 0.700

Threshold: >10% with p < 0.05 | Generated: 2026-05-25 13:31 UTC

…v-blockchain#937)

- TestSyncManager_createUtxos_ChunkFailureCancelsSiblings: orchestrates a
  scenario that observably fails when the mergeCtx.Err() short-circuit is
  removed. A naive "fail every call after N" cannot distinguish a working
  check from a stripped impl because each worker dies on its first error
  anyway. Instead we block the surviving worker inside SetMinedMulti until
  the failing call has triggered errgroup cancellation, then assert the
  surviving worker's second iteration is suppressed by the loop-top check.
  Verified by mutation: removing the check produces failureCount == 2
  (assertion at handle_block_test.go:1418 fails).
- Rename ChunkErrorPropagates -> ChunkErrorReturnsWrappedProcessingError
  to accurately describe what it tests (drops the misleading short-circuit
  assertion now owned by the new test).
- Add boundary tests: ExactBatchSize (n == batchSize) and
  OneOverBatchSize (n == batchSize + 1).
- Add clamp tests: BatchSizeZeroClamped and RoutinesZeroClamped, exercising
  the defensive clamps that were previously untested.
- Extract newChunkingTestSetup, recordChunksOnMock, and assertUnionCovers
  helpers to DRY repeated boilerplate across the chunking tests.

Follow-up on review feedback from PR bsv-blockchain#937.

@ordishs ordishs left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — the chunking pattern correctly mirrors stores/utxo/aerospike/longest_chain.go:MarkTransactionsOnLongestChain, the defensive clamps are a small improvement over the reference, and the loop-variable capture / non-overlapping slice access are race-free. A few notes before / after merge:

TestSyncManager_createUtxos_ChunkFailureCancelsSiblings is potentially flaky

The test is the only one that exercises the if mergeCtx.Err() != nil { return mergeCtx.Err() } short-circuit. With totalTxs=16, batchSize=4, routines=2, each worker performs exactly 2 iterations, and the .Once() chain orders exp1 → exp2 → exp3 in mock-match order, not worker order. Two scheduler interleavings are legal:

  • Path A (test intent). Worker A iter 1 → exp1; Worker B iter 1 → exp2 (blocks); Worker A iter 2 → exp3 (fails, cancels); Worker B unblocks; Worker B iter 2 short-circuits. Removing the short-circuit fires exp4 → failureCount == 2 → test fails. ✅
  • Path B (also legal). Worker A iter 1 → exp1; Worker A iter 2 → exp2 (blocks); Worker B iter 1 → exp3 (fails, cancels); Worker A iter 2 unblocks → returns nil; A done (no iter 3), B done. Neither worker has another iteration left to short-circuit. Removing the short-circuit makes no observable difference; the test passes regardless.

Under Path B the test green-lights a stripped implementation. Suggest either bumping totalTxs so both workers have ≥ 2 post-trigger iterations, or constraining the call order with an explicit handoff (e.g. atomic counter pinning the first call to a specific worker via the captured chunk identity).

Manual production validation still unchecked

The last item in the test plan — resume mainnet sync on bsva-ovh-teranode-eu-3 at block 755880 — is the only check that actually proves the connection-pool exhaustion is resolved. With default settings (MaxMinedRoutines=128, ConnectionQueueSize=16 per node per the PR description) the worker count still exceeds the per-node connection pool — the fix relies on Aerospike sharding spreading the writes across nodes. If shard skew is high on 755880, the same symptom could recur. Worth gating merge on completing that run.

Minor — discarded return map

SetMinedMulti returns map[chainhash.Hash][]uint32 of prior BlockIDs. Both pre- and post-PR code discards it, so no regression — but worth a sentence in the comment block on why the legacy netsync path is fine without prior-block-ID tracking, or a TODO if it should aggregate across chunks later.

Minor — settings doc drift

settings/utxostore_settings.go:49 still describes MaxMinedRoutines as "Block with 10,000 transactions split across 128 parallel workers (approximately 78 transactions per worker)." That math reflects the SafeSetLimit topology in model/update-tx-mined.go, not the worker-range topology now used here and in longest_chain.go. Two patterns coexist with one shared setting key — fine, but a follow-up to align the docs would help operators tune it.

Nit — symmetry with longest_chain.go

The batchSize < 1 and numWorkers < 1 clamps added here would also protect longest_chain.go:51-53 from misconfiguration. Optional follow-up.

@oskarszoon oskarszoon self-assigned this May 25, 2026
…tant

Existing test orchestrated worker identity via channels but worker A vs
worker B at runtime isn't predictable — existingTxHashes is appended by
parallel Create() goroutines in scheduler-derived order. Under one legal
interleaving (Path B from PR bsv-blockchain#937 review), removing the short-circuit
check did not change the assertion outcome.

Replace with a scheduler-agnostic design: 32 txs / batchSize=4 / routines=2
gives 4 chunks per worker. After the trigger fires on the 3rd call, the
surviving worker has multiple remaining iterations across every
interleaving. With the check intact, the surviving worker bails at the
top of its loop on every remaining iteration → postTriggerCount ≤ 1
(in-flight). Without the check, the surviving worker continues issuing
SetMinedMulti → postTriggerCount ≥ 2 (observed 2-4 across interleavings).

Verified by 20× mutation runs (all fail under mutation; observed counts:
14×4, 4×3, 2×2) and 20× normal runs (all pass).
@sonarqubecloud

Copy link
Copy Markdown

@oskarszoon oskarszoon merged commit eb1f1c5 into bsv-blockchain:main May 26, 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.

legacy: createUtxos calls SetMinedMulti with unbounded slice — stalls aerospike on fat blocks (regression from #854)

3 participants