Skip to content

feat(netsync): pre-assign block ID in legacy path to enable skipping setTxMined#711

Merged
icellan merged 3 commits into
mainfrom
feat/legacy-netsync-block-id-pre-assignment
Apr 23, 2026
Merged

feat(netsync): pre-assign block ID in legacy path to enable skipping setTxMined#711
icellan merged 3 commits into
mainfrom
feat/legacy-netsync-block-id-pre-assignment

Conversation

@icellan

@icellan icellan commented Apr 16, 2026

Copy link
Copy Markdown
Contributor

Problem

During LEGACYSYNCING, the legacy netsync path (handle_block.go) calls createUtxos which creates UTXOs without a block ID. Later, blockvalidation runs full ValidateBlockWithOptions, adds the block to the blockchain (auto-assigning an ID), then updateSubtreesDAH triggers the setMinedChan background worker. That worker calls setTxMinedStatusUpdateTxMinedStatusSetMinedMulti to retroactively write the block ID to every UTXO in the block. This is a redundant round-trip for each transaction in every legacy-synced block.

The catchup path (quickValidateBlock) already avoids this by:

  1. Calling GetNextBlockID upfront to get block ID X
  2. Creating UTXOs with WithMinedBlockInfo{BlockID: X}
  3. Calling AddBlock(WithID(X), WithMinedSet(true)) — block stored with mined_set = true
  4. The setMinedChan worker sees blockHeaderMeta.MinedSet == true (guard at BlockValidation.go:533–536) and skips setTxMinedStatus entirely

This PR replicates the same pattern for the legacy netsync path.

Solution

Three layers of change:

1. Netsync (handle_block.go)

  • prepareSubtrees now calls blockchainClient.GetNextBlockID only when in LEGACYSYNCING mode (quickValidationMode = legacyMode) and returns the assigned block ID
  • The block ID is threaded through ValidateTransactionsLegacyModecreateUtxos, which now passes WithMinedBlockInfo{BlockID, BlockHeight, SubtreeIdx: 0} to utxoStore.Create
  • ProcessBlock accepts the block ID and forwards it to blockvalidation

2. gRPC interface (blockvalidation_api.proto)

  • New block_id uint32 field added to ProcessBlockRequest (field 5)
  • Allows netsync to pass the pre-assigned block ID across the service boundary without modifying the block wire format

3. Blockvalidation (Server.go + BlockValidation.go)

  • Server.ProcessBlock sets block.ID = request.BlockId when non-zero
  • New buildAddBlockOpts helper: when block.ID != 0, returns [WithID(block.ID), WithMinedSet(true)]
  • Both AddBlock call sites in ValidateBlockWithOptions use this helper, so blocks arriving from the legacy netsync path are stored with mined_set = true upfront
  • The existing setMinedChan guard (blockHeaderMeta.MinedSet == truecontinue) then skips setTxMinedStatus entirely for those blocks

Why the gRPC field is necessary

block.Bytes() (the wire serialisation used in the gRPC request) does not include block.ID — it is an internal database field. Without the new proto field, the pre-assigned ID is lost at the service boundary and blockvalidation would auto-assign a different ID, causing UTXOs to be created with ID X but the block stored with ID Y, and setTxMined would still run to overwrite X→Y.

Impact

  • LEGACYSYNCING blocks: setTxMinedStatus is now skipped entirely (via existing guard). UTXOs are written with the correct block ID at creation time.
  • All other blocks (live, catchup, revalidation): block.ID == 0 in the gRPC request → buildAddBlockOpts returns nilAddBlock behaves exactly as before. No behaviour change.
  • Proto: backward-compatible addition (field 5, default value 0 = "not pre-assigned").

Testing

  • go build ./... — clean
  • make lint — 0 issues
  • Unit tests for all modified packages pass:
    • services/legacy/netsync/...
    • services/blockvalidation/...
    • services/rpc/...
    • services/asset/...
  • All existing ProcessBlock call sites in integration/smoke tests updated with 0 (no pre-assigned ID) — no behaviour change for those tests

Verification in a live environment

With LEGACYSYNCING active, the setMinedChan worker will log:

[BlockValidation:start][<hash>] block already has mined_set true, skipping setTxMined

for every block processed via the legacy path, confirming setTxMinedStatus is bypassed.

@icellan icellan requested a review from oskarszoon April 16, 2026 10:43
@github-actions

github-actions Bot commented Apr 16, 2026

Copy link
Copy Markdown
Contributor

🤖 Claude Code Review

Status: Complete


Current Review:

No critical issues found. The implementation correctly optimizes UTXO creation during LEGACYSYNCING by:

  • Pre-assigning block IDs to avoid redundant setTxMinedStatus calls
  • Preserving ID across the gRPC boundary via a separate proto field (necessary because block.Bytes() does not serialize the ID field)
  • Maintaining backward compatibility (blockID=0 means auto-assign)

Documentation claims verified:

  • block.Valid() calls checkParentExistsOnChain for double-spend detection (Block.go:846)
  • setMinedChan worker checks MinedSet guard to skip processing (BlockValidation.go:533-536)
  • ✅ ID gap tolerance is acceptable (sequence is monotonic, not contiguous)

Addressed human reviewer question about parameter design in inline comment thread.

@github-actions

github-actions Bot commented Apr 16, 2026

Copy link
Copy Markdown
Contributor

Benchmark Comparison Report

Baseline: main (unknown)

Current: PR-711 (2b3e571)

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.668µ 1.786µ ~ 0.100
SplitSyncedParentMap_SetIfNotExists/256_buckets-4 61.66n 61.71n ~ 0.700
SplitSyncedParentMap_SetIfNotExists/16_buckets-4 61.71n 61.68n ~ 0.300
SplitSyncedParentMap_SetIfNotExists/1_bucket-4 61.81n 62.04n ~ 0.300
SplitSyncedParentMap_ConcurrentSetIfNotExists/256_buckets... 30.10n 30.39n ~ 0.400
SplitSyncedParentMap_ConcurrentSetIfNotExists/16_buckets_... 52.47n 51.80n ~ 0.700
SplitSyncedParentMap_ConcurrentSetIfNotExists/1_bucket_pa... 109.3n 106.8n ~ 0.200
MiningCandidate_Stringify_Short-4 272.4n 273.1n ~ 0.400
MiningCandidate_Stringify_Long-4 1.893µ 1.903µ ~ 0.100
MiningSolution_Stringify-4 969.5n 965.7n ~ 0.200
BlockInfo_MarshalJSON-4 1.757µ 1.739µ ~ 0.700
NewFromBytes-4 125.3n 124.8n ~ 0.700
Mine_EasyDifficulty-4 58.95µ 58.39µ ~ 0.100
Mine_WithAddress-4 4.758µ 4.826µ ~ 0.200
BlockAssembler_AddTx-4 0.02885n 0.02873n ~ 1.000
AddNode-4 10.84 10.85 ~ 1.000
AddNodeWithMap-4 11.05 11.25 ~ 1.000
DiskTxMap_SetIfNotExists-4 3.434µ 3.596µ ~ 0.700
DiskTxMap_SetIfNotExists_Parallel-4 3.370µ 3.276µ ~ 0.200
DiskTxMap_ExistenceOnly-4 294.5n 293.9n ~ 0.400
Queue-4 195.6n 194.9n ~ 0.700
AtomicPointer-4 4.721n 4.472n ~ 0.700
ReorgOptimizations/DedupFilterPipeline/Old/10K-4 874.8µ 858.6µ ~ 0.200
ReorgOptimizations/DedupFilterPipeline/New/10K-4 829.9µ 842.8µ ~ 0.100
ReorgOptimizations/AllMarkFalse/Old/10K-4 131.1µ 110.1µ ~ 0.100
ReorgOptimizations/AllMarkFalse/New/10K-4 62.42µ 62.28µ ~ 0.400
ReorgOptimizations/HashSlicePool/Old/10K-4 67.63µ 63.48µ ~ 1.000
ReorgOptimizations/HashSlicePool/New/10K-4 11.10µ 12.04µ ~ 0.100
ReorgOptimizations/NodeFlags/Old/10K-4 5.184µ 5.862µ ~ 0.200
ReorgOptimizations/NodeFlags/New/10K-4 1.843µ 1.778µ ~ 0.100
ReorgOptimizations/DedupFilterPipeline/Old/100K-4 9.327m 9.608m ~ 0.100
ReorgOptimizations/DedupFilterPipeline/New/100K-4 9.589m 9.263m ~ 0.200
ReorgOptimizations/AllMarkFalse/Old/100K-4 1.118m 1.123m ~ 0.700
ReorgOptimizations/AllMarkFalse/New/100K-4 680.1µ 677.4µ ~ 1.000
ReorgOptimizations/HashSlicePool/Old/100K-4 626.4µ 649.4µ ~ 0.100
ReorgOptimizations/HashSlicePool/New/100K-4 276.2µ 284.9µ ~ 0.200
ReorgOptimizations/NodeFlags/Old/100K-4 54.35µ 56.33µ ~ 0.100
ReorgOptimizations/NodeFlags/New/100K-4 19.38µ 19.98µ ~ 0.100
TxMapSetIfNotExists-4 51.84n 51.50n ~ 0.200
TxMapSetIfNotExistsDuplicate-4 38.03n 37.92n ~ 0.300
ChannelSendReceive-4 594.5n 600.0n ~ 0.100
DirectSubtreeAdd/4_per_subtree-4 61.94n 58.13n ~ 0.200
DirectSubtreeAdd/64_per_subtree-4 28.82n 29.63n ~ 0.200
DirectSubtreeAdd/256_per_subtree-4 27.68n 27.83n ~ 0.200
DirectSubtreeAdd/1024_per_subtree-4 26.57n 26.57n ~ 1.000
DirectSubtreeAdd/2048_per_subtree-4 26.11n 26.15n ~ 0.700
SubtreeProcessorAdd/4_per_subtree-4 318.1n 316.2n ~ 0.700
SubtreeProcessorAdd/64_per_subtree-4 313.3n 317.2n ~ 0.100
SubtreeProcessorAdd/256_per_subtree-4 316.3n 319.0n ~ 1.000
SubtreeProcessorAdd/1024_per_subtree-4 310.7n 317.9n ~ 0.200
SubtreeProcessorAdd/2048_per_subtree-4 315.5n 314.3n ~ 1.000
SubtreeProcessorRotate/4_per_subtree-4 318.2n 320.3n ~ 0.700
SubtreeProcessorRotate/64_per_subtree-4 318.2n 320.0n ~ 0.700
SubtreeProcessorRotate/256_per_subtree-4 320.3n 317.6n ~ 1.000
SubtreeProcessorRotate/1024_per_subtree-4 304.6n 309.6n ~ 0.700
SubtreeNodeAddOnly/4_per_subtree-4 68.45n 68.93n ~ 0.700
SubtreeNodeAddOnly/64_per_subtree-4 40.86n 41.53n ~ 0.100
SubtreeNodeAddOnly/256_per_subtree-4 39.55n 39.70n ~ 1.000
SubtreeNodeAddOnly/1024_per_subtree-4 39.23n 39.23n ~ 1.000
SubtreeCreationOnly/4_per_subtree-4 165.5n 167.1n ~ 0.100
SubtreeCreationOnly/64_per_subtree-4 608.6n 600.1n ~ 0.400
SubtreeCreationOnly/256_per_subtree-4 1.899µ 1.953µ ~ 0.400
SubtreeCreationOnly/1024_per_subtree-4 4.459µ 4.603µ ~ 1.000
SubtreeCreationOnly/2048_per_subtree-4 7.613µ 6.897µ ~ 0.200
SubtreeProcessorOverheadBreakdown/64_per_subtree-4 309.3n 310.4n ~ 1.000
SubtreeProcessorOverheadBreakdown/1024_per_subtree-4 315.3n 316.7n ~ 1.000
ParallelGetAndSetIfNotExists/1k_nodes-4 921.4µ 931.7µ ~ 0.700
ParallelGetAndSetIfNotExists/10k_nodes-4 1.954m 2.114m ~ 0.100
ParallelGetAndSetIfNotExists/50k_nodes-4 9.097m 9.031m ~ 1.000
ParallelGetAndSetIfNotExists/100k_nodes-4 17.71m 18.44m ~ 0.100
SequentialGetAndSetIfNotExists/1k_nodes-4 733.6µ 739.4µ ~ 0.700
SequentialGetAndSetIfNotExists/10k_nodes-4 3.081m 3.099m ~ 0.700
SequentialGetAndSetIfNotExists/50k_nodes-4 11.91m 11.67m ~ 0.100
SequentialGetAndSetIfNotExists/100k_nodes-4 22.75m 22.16m ~ 0.100
ProcessOwnBlockSubtreeNodesParallel/1k_nodes-4 1.001m 1.035m ~ 0.200
ProcessOwnBlockSubtreeNodesParallel/10k_nodes-4 5.182m 5.313m ~ 0.200
ProcessOwnBlockSubtreeNodesParallel/100k_nodes-4 19.55m 20.08m ~ 0.100
ProcessOwnBlockSubtreeNodesSequential/1k_nodes-4 802.6µ 814.6µ ~ 0.100
ProcessOwnBlockSubtreeNodesSequential/10k_nodes-4 6.561m 6.822m ~ 0.200
ProcessOwnBlockSubtreeNodesSequential/100k_nodes-4 41.95m 43.46m ~ 0.400
CalcBlockWork-4 501.3n 504.7n ~ 0.700
CalculateWork-4 674.5n 685.8n ~ 0.400
BuildBlockLocatorString_Helpers/Size_10-4 1.019µ 1.024µ ~ 0.100
BuildBlockLocatorString_Helpers/Size_100-4 11.17µ 11.58µ ~ 1.000
BuildBlockLocatorString_Helpers/Size_1000-4 96.28µ 97.12µ ~ 0.700
CatchupWithHeaderCache-4 103.6m 103.6m ~ 1.000
SubtreeSizes/10k_tx_4_per_subtree-4 1.365m 1.405m ~ 0.700
SubtreeSizes/10k_tx_16_per_subtree-4 316.5µ 323.1µ ~ 0.100
SubtreeSizes/10k_tx_64_per_subtree-4 75.03µ 76.45µ ~ 0.700
SubtreeSizes/10k_tx_256_per_subtree-4 18.74µ 18.94µ ~ 0.100
SubtreeSizes/10k_tx_512_per_subtree-4 9.297µ 9.291µ ~ 1.000
SubtreeSizes/10k_tx_1024_per_subtree-4 4.570µ 4.648µ ~ 0.100
SubtreeSizes/10k_tx_2k_per_subtree-4 2.286µ 2.300µ ~ 0.700
BlockSizeScaling/10k_tx_64_per_subtree-4 73.88µ 72.97µ ~ 0.700
BlockSizeScaling/10k_tx_256_per_subtree-4 18.36µ 18.38µ ~ 0.700
BlockSizeScaling/10k_tx_1024_per_subtree-4 4.571µ 4.603µ ~ 0.700
BlockSizeScaling/50k_tx_64_per_subtree-4 389.5µ 388.9µ ~ 1.000
BlockSizeScaling/50k_tx_256_per_subtree-4 91.30µ 91.80µ ~ 0.400
BlockSizeScaling/50k_tx_1024_per_subtree-4 22.72µ 22.39µ ~ 0.200
SubtreeAllocations/small_subtrees_exists_check-4 152.5µ 155.1µ ~ 0.400
SubtreeAllocations/small_subtrees_data_fetch-4 162.3µ 161.7µ ~ 0.700
SubtreeAllocations/small_subtrees_full_validation-4 316.8µ 313.0µ ~ 0.100
SubtreeAllocations/medium_subtrees_exists_check-4 9.017µ 9.080µ ~ 0.400
SubtreeAllocations/medium_subtrees_data_fetch-4 9.380µ 9.383µ ~ 1.000
SubtreeAllocations/medium_subtrees_full_validation-4 18.28µ 18.32µ ~ 0.700
SubtreeAllocations/large_subtrees_exists_check-4 2.154µ 2.182µ ~ 0.200
SubtreeAllocations/large_subtrees_data_fetch-4 2.276µ 2.287µ ~ 0.700
SubtreeAllocations/large_subtrees_full_validation-4 4.586µ 4.602µ ~ 0.400
_BufferPoolAllocation/16KB-4 3.284µ 3.240µ ~ 0.700
_BufferPoolAllocation/32KB-4 8.976µ 7.755µ ~ 0.700
_BufferPoolAllocation/64KB-4 15.79µ 14.30µ ~ 0.100
_BufferPoolAllocation/128KB-4 28.64µ 27.79µ ~ 0.100
_BufferPoolAllocation/512KB-4 115.6µ 113.6µ ~ 0.100
_BufferPoolConcurrent/32KB-4 18.49µ 19.36µ ~ 0.100
_BufferPoolConcurrent/64KB-4 29.16µ 29.41µ ~ 0.400
_BufferPoolConcurrent/512KB-4 138.3µ 145.0µ ~ 0.700
_SubtreeDeserializationWithBufferSizes/16KB-4 602.6µ 671.9µ ~ 0.100
_SubtreeDeserializationWithBufferSizes/32KB-4 610.6µ 671.1µ ~ 0.100
_SubtreeDeserializationWithBufferSizes/64KB-4 628.8µ 664.7µ ~ 0.100
_SubtreeDeserializationWithBufferSizes/128KB-4 620.6µ 667.3µ ~ 0.100
_SubtreeDeserializationWithBufferSizes/512KB-4 619.4µ 662.5µ ~ 0.100
_SubtreeDataDeserializationWithBufferSizes/16KB-4 35.97m 36.37m ~ 0.400
_SubtreeDataDeserializationWithBufferSizes/32KB-4 36.01m 36.11m ~ 0.700
_SubtreeDataDeserializationWithBufferSizes/64KB-4 35.41m 35.92m ~ 0.400
_SubtreeDataDeserializationWithBufferSizes/128KB-4 35.52m 36.11m ~ 0.400
_SubtreeDataDeserializationWithBufferSizes/512KB-4 35.44m 35.71m ~ 0.200
_PooledVsNonPooled/Pooled-4 837.5n 836.9n ~ 1.000
_PooledVsNonPooled/NonPooled-4 6.970µ 6.962µ ~ 0.700
_MemoryFootprint/Current_512KB_32concurrent-4 7.469µ 7.593µ ~ 0.200
_MemoryFootprint/Proposed_32KB_32concurrent-4 10.51µ 10.99µ ~ 0.100
_MemoryFootprint/Alternative_64KB_32concurrent-4 10.37µ 10.73µ ~ 0.100
_prepareTxsPerLevel-4 382.5m 389.0m ~ 0.400
_prepareTxsPerLevelOrdered-4 4.145m 4.129m ~ 1.000
_prepareTxsPerLevel_Comparison/Original-4 388.3m 390.5m ~ 1.000
_prepareTxsPerLevel_Comparison/Optimized-4 4.038m 4.167m ~ 0.100
StoreBlock_Sequential/BelowCSVHeight-4 307.0µ 307.4µ ~ 0.400
StoreBlock_Sequential/AboveCSVHeight-4 307.8µ 314.7µ ~ 0.700
GetUtxoHashes-4 255.4n 257.4n ~ 0.200
GetUtxoHashes_ManyOutputs-4 51.04µ 50.97µ ~ 1.000
_NewMetaDataFromBytes-4 243.0n 242.3n ~ 1.000
_Bytes-4 654.0n 649.4n ~ 1.000
_MetaBytes-4 597.1n 597.1n ~ 0.800

Threshold: >10% with p < 0.05 | Generated: 2026-04-17 16:23 UTC

@github-actions

github-actions Bot commented Apr 16, 2026

Copy link
Copy Markdown
Contributor

🤖 Claude Code Review

Status: Complete


Current Review:

[Major] Protobuf documentation not regenerated

The blockvalidation_api.proto file was modified to add the block_id field (field 5 in ProcessBlockRequest), but the generated protobuf documentation was not updated:

  • File: docs/references/protobuf_docs/blockvalidationProto.md:108-113
  • Issue: The ProcessBlockRequest table is missing the new block_id field
  • Expected: The table should include: | block_id | [uint32](#uint32) | | Pre-assigned block ID from legacy netsync (0 = not pre-assigned, blockchain will auto-assign) |
  • Fix: Run make gen to regenerate protobuf documentation and commit the updated file

Code Quality:

The implementation is well-structured and follows the existing patterns in the codebase:

Good practices observed:

  • Clear documentation of the ID gap trade-off (handle_block.go:297-301)
  • Proper safety documentation for skipping double-spend checks (BlockValidation.go:1112-1116)
  • Backward-compatible proto field addition (field 5, default 0)
  • Type conversion safety with nolint comment (handle_block.go:306)
  • Consistent use of mainChainRebuilding guards for on_main_chain optimization
  • All test mocks updated correctly

Logic correctness:

  • The pre-assignment pattern matches the existing catchup path
  • Guard at BlockValidation.go:533 correctly skips setTxMinedStatus when MinedSet=true
  • UTXOs created with mined info upfront (handle_block.go:591-595)
  • Non-zero block.ID check prevents affecting non-legacy paths (BlockValidation.go:1121)

Summary:

One documentation issue found (protobuf docs not regenerated). Code implementation is solid and well-documented. No logic errors or security concerns identified.

@icellan icellan self-assigned this Apr 16, 2026
@icellan icellan requested a review from freemans13 April 16, 2026 13:15
icellan added 2 commits April 17, 2026 13:56
…setTxMined

In LEGACYSYNCING mode, the legacy netsync path (handle_block.go) now:
1. Calls GetNextBlockID before creating UTXOs, so every UTXO is stored with
   the correct BlockID/BlockHeight via WithMinedBlockInfo from the start.
2. Threads the pre-assigned block ID through ProcessBlock → blockvalidation
   gRPC (new ProcessBlockRequest.block_id proto field).
3. blockvalidation Server sets block.ID from the request, and ValidateBlock
   calls AddBlock(WithID, WithMinedSet=true) when block.ID is pre-assigned.

This causes the existing setMinedChan worker guard (BlockValidation.go:533-536)
to see mined_set=true and skip setTxMinedStatus, eliminating the redundant
SetMinedMulti round-trip over every UTXO for legacy-synced blocks.
@icellan icellan force-pushed the feat/legacy-netsync-block-id-pre-assignment branch from 7e0858a to a4e0931 Compare April 17, 2026 12:05
@icellan icellan requested a review from freemans13 April 17, 2026 12:10
}

func (mv *MockBlockValidation) ProcessBlock(ctx context.Context, block *model.Block, blockHeight uint32, peerID, baseURL string) error {
func (mv *MockBlockValidation) ProcessBlock(ctx context.Context, block *model.Block, blockHeight uint32, peerID, baseURL string, blockID uint32) error {

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.

model.Block has ID attribute. I'm wondering whether we should be populating model.Block.ID rather than adding blockID parameter to each func? You can argue same for blockHeight param vs existing model.Block.Height attribute

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Good observation. The PR does populate model.Block.ID (at handle_block.go:166), and that's where the block ID is initially set.

The separate blockID parameter exists because block.Bytes() (the wire serialization) does not include the ID field — it's an internal database field, not part of the Bitcoin wire protocol. When the block crosses the gRPC boundary:

  1. Client.ProcessBlock calls block.Bytes() → ID is lost in serialization
  2. The proto field BlockId carries it separately (Client.go:164)
  3. Server.ProcessBlock restores it: block.ID = request.BlockId (Server.go:1268)

Reading teranodeBlock.ID in the netsync call (handle_block.go:246) avoids duplication as a separate variable, but the proto field is necessary to preserve the ID across the service boundary.

teranodeBlock.ID is populated by model.NewBlock at construction time, so
sm.ProcessBlock can read it from the struct instead of accepting it as a
separate parameter. The proto field on the gRPC boundary has to stay
because block.Bytes() does not serialize ID.
@sonarqubecloud

Copy link
Copy Markdown

@oskarszoon oskarszoon left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approve. Clean replication of the catchup path's block-ID-preassignment pattern for the legacy netsync path. Proto field backward-compatible, all call sites updated, CI green.

Minor suggestions (non-blocking):

  • Consider adding a unit test for the happy path where GetNextBlockID returns an ID and it propagates through to utxoStore.Create with WithMinedBlockInfo
  • buildAddBlockOpts doc comment could be trimmed to ~6 lines (keep the trade-off rationale, drop the restated mechanics)

@icellan icellan merged commit 95dbe32 into main Apr 23, 2026
30 checks passed
@icellan icellan deleted the feat/legacy-netsync-block-id-pre-assignment branch April 23, 2026 12:37
icellan added a commit that referenced this pull request Apr 27, 2026
Adapts new double-spend test files to the 6-arg ProcessBlock signature
introduced by #711 ("pre-assign block ID in legacy path"). All callers
in the legacy path use blockID=0, matching every existing test caller.
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