perf(blockassembly): optimize capacity management with early validation checks#455
perf(blockassembly): optimize capacity management with early validation checks#455ordishs wants to merge 3 commits intobsv-blockchain:mainfrom
Conversation
|
🤖 Claude Code Review Status: Complete Current Review: The race condition previously identified still exists but is acceptable for this use case:
No other issues found. The implementation is well-structured with good error handling, comprehensive tests, and clear documentation. History:
|
| } | ||
|
|
||
| if !ba.settings.BlockAssembly.Disabled { | ||
| if !ba.blockAssembler.subtreeProcessor.CanAcceptTransactions(1) { |
There was a problem hiding this comment.
Race Condition: Check-Then-Act Pattern
The capacity check is not atomic with the subsequent queue operation. While AddBatch provides a second check, the race still exists:
- Thread A: CanAcceptTransactions(1) returns true (current=99, limit=100)
- Thread B: CanAcceptTransactions(1) returns true (current=99, limit=100)
- Thread A: AddBatch → CanAcceptTransactions(1) returns true → enqueueBatch increments to 100
- Thread B: AddBatch → CanAcceptTransactions(1) returns true (reads 100) → enqueueBatch increments to 101
The window is narrow but exists between lines 1707 (CanAcceptTransactions check) and 1716 (enqueueBatch).
Resolution: This is acceptable for this use case. The limit is a soft operational safety measure to prevent OOM during restarts, not a hard security boundary. The worst-case overage is bounded by concurrent request count, which is acceptable. Alternative approaches (atomic compare-and-swap) would add complexity without significant benefit for this operational protection mechanism.
|
|
||
| // Add entire batch in one call | ||
| if !ba.settings.BlockAssembly.Disabled { | ||
| if !ba.blockAssembler.subtreeProcessor.CanAcceptTransactions(len(nodes)) { |
There was a problem hiding this comment.
Same Race Condition (Amplified by Batch Size)
This has the same check-then-act race as AddTx. With batches, the overage could be significant - if the limit is 1000 and two threads each submit batches of 500 when current=600, both could pass the check and push the total to 1600 (60% over limit). The dual-check pattern (here + AddBatch) narrows the window but does not eliminate the race between CanAcceptTransactions and enqueueBatch.
Resolution: This is acceptable for this use case. The limit is a soft operational safety measure to prevent OOM during restarts, not a hard security boundary. Even with batch amplification, the overage is bounded by the number of concurrent batch submissions, which is limited by server concurrency. The protection still significantly reduces the risk of OOM compared to having no limit. Alternative approaches would add complexity without sufficient benefit for this operational protection mechanism.
Add capacity limit for unmined transactions in BlockAssembly to prevent OOM crashes on restart. When BlockAssembly restarts, it loads all unmined transactions from UTXOStore - if there are too many, this can cause memory exhaustion. Changes: - Add settings: MaxUnminedTransactions, BytesPerTransaction, MemoryLimitPercent - Auto-calculate limit based on system memory using gopsutil (default: 80% of RAM) - Enforce limit in AddBatch and AddNodesDirectly methods - Return gRPC ResourceExhausted error when capacity is reached - Add Prometheus metrics for monitoring capacity state Closes #4459
Remove auto-calculation complexity and provide single MaxUnminedTransactions setting: - 0 = unlimited (default) - positive value = hard limit on transaction count Changes: - Remove BytesPerTransaction and MemoryLimitPercent settings - Remove memory detection helpers (memory.go, memory_test.go) - Simplify initializeCapacityLimit() to just set the configured value - Update documentation with memory guidelines for manual configuration - Add comprehensive e2e test for capacity limit enforcement
96828d7 to
0d16eeb
Compare
Add CanAcceptTransaction gRPC method to check block assembly capacity before expensive UTXO operations. This optimization prevents wasted database writes when capacity limit is reached. Key improvements: - New gRPC endpoint returns capacity status (can_accept, current_count, max_limit, remaining_capacity) - Validator checks capacity before spendUtxos() and CreateInUtxoStore() - Zero overhead when MaxUnminedTransactions=0 (unlimited) - no gRPC call made - Prevents unnecessary UTXO spending, creation, and reversal operations when at capacity - Better throughput under heavy load by failing fast with ERR_THRESHOLD_EXCEEDED Implementation: - Add CanAcceptTransaction RPC to blockassembly_api.proto - Implement handler in BlockAssembly server - Add client method and interface definition - Update all mocks (blockassembly, validator, rpc test mocks) - Early check in Validator.validateInternal() before expensive operations
|


Summary
This PR optimizes block assembly capacity management through two key improvements:
MaxUnminedTransactionssetting (0=unlimited, positive=hard limit)Problem
Previously, when block assembly reached capacity:
This wasted database resources and reduced throughput under heavy load.
Solution
Part 1: Simplified Configuration (Commits 1-2)
Removed:
BytesPerTransactionsettingMemoryLimitPercentsettingmemory.go,memory_test.go)Simplified to:
MaxUnminedTransactionssetting:0= unlimited (default)Part 2: Early Capacity Check (Commit 3)
New gRPC Method:
CanAcceptTransactioncan_accept,current_count,max_limit,remaining_capacityOptimization:
MaxUnminedTransactions=0)Performance Impact
Transaction Flow Comparison
Before
After
Changes
Commits
feat(blockassembly): limit unmined transactions based on system memory- Initial implementationrefactor(blockassembly): simplify capacity limit configuration- Removed auto-calc complexityperf(validator): add early capacity check before UTXO operations- Performance optimizationFiles Modified
services/blockassembly/BlockAssembler.go- Simplified capacity initializationservices/blockassembly/blockassembly_api.proto- NewCanAcceptTransactionRPCservices/blockassembly/Server.go- gRPC handler implementationservices/blockassembly/Client.go- Client methodservices/blockassembly/Interface.go- Interface definitionsservices/blockassembly/mock.go- Mock implementationsservices/validator/Validator.go- Early capacity checkservices/validator/Validator_test.go- Mock implementationservices/rpc/handlers_additional_test.go- Mock implementationsettings/blockassembly_settings.go- Updated documentationtest/e2e/daemon/ready/capacity_limit_test.go- E2E testFiles Deleted
services/blockassembly/memory.go- Memory detection removedservices/blockassembly/memory_test.go- Memory tests removedTesting
Migration Guide
No migration needed - existing configurations continue to work:
MaxUnminedTransactionsnot set or0: unlimited (default behavior)Optional: Operators can now set explicit limits based on available RAM:
Related Issues
Addresses capacity management and performance optimization for block assembly under heavy transaction load.