Block Assembly Service Settings¶
Related Topic: Block Assembly Service
Configuration Settings¶
| Setting | Type | Default | Environment Variable | Usage |
|---|---|---|---|---|
| Disabled | bool | false | blockassembly_disabled | Service-level kill switch, all operations return early |
| GRPCAddress | string | "localhost:8085" | blockassembly_grpcAddress | Client connection address |
| GRPCListenAddress | string | ":8085" | blockassembly_grpcListenAddress | CRITICAL - gRPC server binding (service skipped if empty) |
| GRPCMaxRetries | int | 3 | blockassembly_grpcMaxRetries | gRPC client retry attempts |
| GRPCRetryBackoff | time.Duration | 2s | blockassembly_grpcRetryBackoff | Retry delay timing |
| LocalDAHCache | string | "" | blockassembly_localDAHCache | UNUSED - Reserved for future DAH caching |
| MaxBlockReorgCatchup | int | 100 | blockassembly_maxBlockReorgCatchup | Map capacity for current chain tracking |
| MaxBlockReorgRollback | int | 100 | blockassembly_maxBlockReorgRollback | UNUSED - Defined but not referenced in code |
| MoveBackBlockConcurrency | int | 375 | blockassembly_moveBackBlockConcurrency | Concurrency limit for reorg processing (SubtreeProcessor) |
| ProcessRemainderTxHashesConcurrency | int | 375 | blockassembly_processRemainderTxHashesConcurrency | Concurrency limit for remainder tx hash processing |
| SendBatchSize | int | 100 | blockassembly_sendBatchSize | Client batch size for sending transactions |
| SendBatchTimeout | int | 2 | blockassembly_sendBatchTimeout | Client batch timeout in seconds |
| SubtreeProcessorBatcherSize | int | 1000 | blockassembly_subtreeProcessorBatcherSize | Subtree processing batch size |
| SubtreeProcessorConcurrentReads | int | 375 | blockassembly_subtreeProcessorConcurrentReads | CRITICAL - Subtree read parallelism |
| NewSubtreeChanBuffer | int | 1000 | blockassembly_newSubtreeChanBuffer | CRITICAL - New subtree channel buffer |
| SubtreeRetryChanBuffer | int | 1000 | blockassembly_subtreeRetryChanBuffer | CRITICAL - Retry channel buffer |
| SubmitMiningSolutionWaitForResponse | bool | true | blockassembly_SubmitMiningSolution_waitForResponse | CRITICAL - Sync (true) vs async (false) mining solution processing |
| InitialMerkleItemsPerSubtree | int | 1048576 | initial_merkle_items_per_subtree | Initial subtree size |
| MinimumMerkleItemsPerSubtree | int | 1024 | minimum_merkle_items_per_subtree | Minimum subtree size |
| MaximumMerkleItemsPerSubtree | int | 1048576 | maximum_merkle_items_per_subtree | Maximum subtree size |
| DoubleSpendWindow | time.Duration | BlockTime * 6 | N/A | Double-spend detection window (calculated) |
| MaxGetReorgHashes | int | 10000 | blockassembly_maxGetReorgHashes | CRITICAL - Reorganization hash limit |
| MinerWalletPrivateKeys | []string | [] | miner_wallet_private_keys | Mining wallet keys |
| DifficultyCache | bool | true | blockassembly_difficultyCache | Enables difficulty calculation caching (Blockchain service) |
| UseDynamicSubtreeSize | bool | false | blockassembly_useDynamicSubtreeSize | Dynamic subtree sizing |
| BlockchainSubscriptionTimeout | time.Duration | 5m | blockassembly_blockchainSubscriptionTimeout | Blockchain event subscription timeout |
| OnRestartValidateParentChain | bool | true | blockassembly_onRestartValidateParentChain | Enables parent chain validation on restart |
| ParentValidationBatchSize | int | 1000 | blockassembly_parentValidationBatchSize | Parent validation batch size |
| OnRestartRemoveInvalidParentChainTxs | bool | true | blockassembly_onRestartRemoveInvalidParentChainTxs | Filters transactions with invalid parent chains |
| SubtreeStorageWorkers | int | 4 | blockassembly_subtreeStorageWorkers | Workers for subtree storage operations |
| SubtreeAnnouncementInterval | time.Duration | 10s | blockassembly_subtreeAnnouncementInterval | Subtree announcement frequency |
| UseColumnarBatch | bool | false | blockassembly_useColumnarBatch | Use columnar batch format for data layout |
| UnminedTxDiskSortPath | string | "" | blockassembly_unminedTxDiskSortPath | Path for unmined transaction disk sorting |
| UnminedTxDiskSortEnabled | bool | false | blockassembly_unminedTxDiskSortEnabled | Enable disk-based sorting for large mempools |
| UnminedLoadingBatchSize | int | 10485760 | blockassembly_unminedLoadingBatchSize | Batch size for loading unmined transactions |
| ParallelSetIfNotExistsThreshold | int | 10000 | blockassembly_parallelSetIfNotExistsThreshold | Threshold for parallelizing conditional writes |
| StoreTxInpointsForSubtreeMeta | bool | true | blockassembly_storeTxInpointsForSubtreeMeta | Store transaction input points in subtree metadata (required for checkblocktemplate) |
| IdleSleepDuration | time.Duration | 10ms | blockassembly_idle_sleep_duration | Sleep duration when subtree processor queue is empty |
| CoinbaseRecoveryMaxGapBlocks | int | 200 | blockassembly_coinbaseRecoveryMaxGapBlocks | Startup scan window below the tip, and the largest coinbase gap auto-repair will fix |
| CoinbaseRecoveryConsecutiveGood | int | 6 | blockassembly_coinbaseRecoveryConsecutiveGood | Consecutive present coinbases proving the gap floor during walk-back |
| CoinbaseRecoveryMaxAttempts | int | 3 | blockassembly_coinbaseRecoveryMaxAttempts | Automatic coinbase-recovery attempts before raising an operator alert |
Coinbase Divergence Recovery¶
Block assembly keeps two pieces of state that have to agree: the chain pointer (the header it believes it is on) and the coinbase UTXOs it has created. Nothing makes those two writes atomic, so an unclean shutdown — a crash part-way through the fast-forward create loop, for instance — can leave the pointer ahead of the coinbase set. The damage is not visible immediately: it shows up a hundred blocks later, when a coinbase-maturity spend looks for a parent coinbase that was never written, cannot find it, and wedges the tip permanently.
To catch that, block assembly runs a check once during startup, after the subtree processor is running but before it starts consuming block notifications. It walks down from the persisted tip looking for canonical coinbases missing from the UTXO store. Each one it finds is repaired in place: it scopes the contiguous gap beneath that height and re-creates just those coinbases — never the transactions in those blocks, so the cost is proportional to the number of affected blocks rather than to chain size. The scan then carries on below the repair, because a repair only proves a few present coinbases underneath itself and a second hole further down would otherwise go unnoticed until the next restart.
Three settings bound that work:
blockassembly_coinbaseRecoveryMaxGapBlocksdoes double duty. It is how far below the tip the startup scan looks, and it is the largest gap recovery will repair on its own. The two are deliberately the same number: there is no value in detecting a divergence deeper than the node is willing to fix automatically. A clean boot costs one blockchain-client lookup and one UTXO store read per height in the window, issued one after another — and the first of those is a call to the blockchain service, not a local read, so the startup cost tracks the latency to that service rather than local disk. The scan ends early only when a divergence cannot be repaired, since a refusal is structural and would repeat identically at every lower height. Tuning this down to limit repair scope also shrinks detection coverage by the same amount. A second, non-configurable bound applies underneath it — see the safety floor below — so with the default settings the effective reach is the coinbase-maturity window.blockassembly_coinbaseRecoveryConsecutiveGoodguards against under-repair. The fast-forward create loop runs one goroutine per block, so a crash can leave a present coinbase sitting above still-missing ones. Stopping the walk-back at the first present coinbase would miss those, so it keeps going until it has seen this many present coinbases in a row.blockassembly_coinbaseRecoveryMaxAttemptscaps automatic retries. Retries exist for transient failures (a store or blockchain-client blip), with a short pause between them. A gap larger than the cap is structural and escalates straight away without spending retries on it.
With the shipped defaults the gap cap never fires. The walk-back stops at the
maturity safety floor described below, so it can never collect more than
CoinbaseMaturity (100) misses, while coinbaseRecoveryMaxGapBlocks defaults to
200. The cap only becomes a live control when it is set below the maturity —
otherwise every escalation you see came from the safety floor, not from the cap.
Its other role, bounding the startup scan window, stays live at any setting.
The safety floor¶
Recovery never looks further back than the coinbase-maturity window, no matter how the settings above are tuned. This is a correctness limit rather than a performance one, and it is worth understanding because it is what keeps the repair safe.
"No coinbase in the UTXO store" is an ambiguous observation. It is true of a coinbase that was never created — the divergence being repaired — but it is equally true of one that was created, matured, fully spent, and then deleted by the pruner. Both look identical to a lookup. Re-creating the second kind would put outputs that were already legitimately spent back into the UTXO set as unspent, which is a far worse problem than the wedge this feature exists to fix.
Coinbase maturity separates the two cases cleanly. A coinbase cannot be spent until
maturity blocks have been built on top of it, and an unspent output is never marked
for deletion, so it is never pruned. Above tip - CoinbaseMaturity, therefore, absence
can only mean "never created". Below it, the answer is unknowable from a lookup alone,
so the walk stops rather than guessing. If a genuine divergence extends past that line,
recovery escalates to an operator instead of repairing it.
The tip in that subtraction is the chain tip as the UTXO store sees it, not block
assembly's own position. The pruner marks outputs for deletion against the store's
height, and block assembly's pointer can sit hundreds of blocks behind it during a
normal catch-up. Measuring the floor from the lagging pointer would put the bottom of
the window inside already-prunable territory, which is the exact mistake the floor
exists to prevent, so the higher of the two heights is always used. The visible effect
is that a block assembler well behind the chain scans a narrower window, and one behind
by more than the maturity window scans nothing and logs that it skipped the check —
correct, because at that distance nothing it could look at is provably unpruned.
Detection coverage is consequently a bounded window, not the whole chain. A hole
further below the tip than the smaller of coinbaseRecoveryMaxGapBlocks and the
maturity window is not found at startup; detecting that cheaply during normal
operation is separate, future work.
Because the maturity window is also the ceiling on how far the walk-back can reach,
a network configured with a small coinbase maturity gets little or nothing out of
this feature. Below coinbaseRecoveryConsecutiveGood there are not enough probeable
heights left to prove a floor, so every miss is refused rather than repaired, and at
a maturity of zero — where a coinbase is spendable immediately and so nothing is ever
provably unspent — the feature is a deliberate no-op. Every shipped network uses 100,
which leaves ample room; only custom chain parameters can land in that territory.
When recovery cannot fix a divergence it logs a single MANUAL INTERVENTION REQUIRED
line naming resetblockassembly, and increments the escalated outcome on the
teranode_blockassembly_coinbase_divergence_total metric. Startup does not fail
in that case — the node boots and keeps building on the diverged tip. That is
deliberate: crash-looping the process helps nobody, and a running node an operator can
inspect is strictly more useful. The trade-off is that the escalation log is a call to
action, not a description of a node that has stopped itself.
Every detection records exactly one follow-up outcome on that metric, so
detected == repaired + no_gap + escalated + aborted. A non-zero no_gap means the
divergence had already closed by the time recovery scoped it. aborted means the node
was shut down while a repair was still retrying — nothing is known to be wrong in that
case, and it deliberately raises no alarm, so a node stopped mid-boot does not tell its
operator that the UTXO state needs manual intervention.
Hardcoded Settings (Not Configurable)¶
| Setting | Value | Usage |
|---|---|---|
| jobTTL | 10 minutes | Mining job cache TTL |
| coinbaseRecoveryRetryBackoff | 500 milliseconds | Pause between automatic coinbase-recovery attempts |
| coinbaseRecoveryProbeRetries | 2 | Probe retries the whole startup divergence scan may spend, shared across every height it visits |
Configuration Dependencies¶
Service Startup¶
- Service skipped (not added to ServiceManager) if
GRPCListenAddressis empty - Channel buffers allocated during Init() based on configured sizes
Service Disable¶
- When
Disabled = true, all block assembly operations return early - All other settings become irrelevant when service is disabled
Channel Buffer Management¶
NewSubtreeChanBufferandSubtreeRetryChanBuffermust accommodate concurrent processing loads- Buffer sizes affect pipeline performance and memory usage
Mining Solution Processing¶
SubmitMiningSolutionWaitForResponse = true: gRPC call blocks until submission completesSubmitMiningSolutionWaitForResponse = false: Returns immediately for async processing- Significantly affects mining pool integration behavior
Reorganization Handling¶
MaxGetReorgHashesprevents excessive memory usage during large reorganizations- Works with
MaxBlockReorgCatchup,MaxBlockReorgRollback,MoveBackBlockConcurrency
Dynamic Subtree Sizing¶
- When
UseDynamicSubtreeSize = true, subtree size adjusts based on transaction volume - Uses
InitialMerkleItemsPerSubtreeas starting size - Adjusts within
MinimumMerkleItemsPerSubtreeandMaximumMerkleItemsPerSubtreebounds
Parent Chain Validation¶
OnRestartValidateParentChain = true: Validates transaction parent chains after service restartParentValidationBatchSize: Controls batch processing size (default: 1000)OnRestartRemoveInvalidParentChainTxs = true(default): Filters out transactions with invalid parent chainsOnRestartRemoveInvalidParentChainTxs = false: Keeps transactions despite invalid parents
Service Dependencies¶
| Dependency | Interface | Usage |
|---|---|---|
| TxStore | blob.Store | Transaction data access |
| UTXOStore | utxostore.Store | CRITICAL - UTXO operations and validation |
| SubtreeStore | blob.Store | CRITICAL - Subtree storage and retrieval |
| BlockchainClient | blockchain.ClientI | CRITICAL - Blockchain operations, block submission |
Validation Rules¶
| Setting | Validation | Impact | When Checked |
|---|---|---|---|
| GRPCListenAddress | Must not be empty | Service skipped if empty | During daemon startup |
| MaxGetReorgHashes | Limits reorganization processing | Memory protection during reorgs | During reorg processing |
| Channel Buffers | Must accommodate processing loads | Pipeline performance and backpressure | During Init() |
Configuration Examples¶
Basic Configuration¶
Performance Tuning¶
blockassembly_subtreeProcessorConcurrentReads=500
blockassembly_newSubtreeChanBuffer=2000
blockassembly_subtreeRetryChanBuffer=2000