Skip to content

docs(validator): clarify ValidateTransactionBatch.Valid is always true#769

Merged
ordishs merged 2 commits into
bsv-blockchain:mainfrom
ordishs:security/4567-validate-batch-valid-flag
May 12, 2026
Merged

docs(validator): clarify ValidateTransactionBatch.Valid is always true#769
ordishs merged 2 commits into
bsv-blockchain:mainfrom
ordishs:security/4567-validate-batch-valid-flag

Conversation

@ordishs

@ordishs ordishs commented Apr 28, 2026

Copy link
Copy Markdown
Collaborator

refs #4567

Summary

Supersedes the original fix attempt in this PR. After review, the original behaviour of ValidateTransactionBatch (setting Valid: true unconditionally) was determined to be documented intent, not a bug. The function's godoc explicitly stated:

Unlike single transaction validation, this method always returns a success status at the batch level - individual transaction validation results are included in the response arrays.

The audit's HIGH severity finding rested on hypothetical clients ignoring the documented contract. Rather than break the contract (which would silently change the wire-field semantics for every existing consumer), this PR tightens the documentation to prevent future "fixes" from reintroducing the same misreading.

Changes

  • services/validator/Server.go — inline comment above Valid: true stating the field is always-true by design and retained for wire compatibility.
  • services/validator/validator_api/validator_api.proto — tighten the field comment from "Overall batch validation status" to "Always true; callers must inspect per-item errors below."

Test plan

  • No behavioural change — comment-only PR.
  • Existing tests (e.g. TestServerValidateTransactionBatch/partial failures) continue to assert Valid==true for partial-failure batches, confirming the documented contract is preserved.

Recommendation for #4567

Audit finding #4567 should be reclassified from HIGH "server bug" to LOW "API field is footgun-shaped, mitigated by clearer documentation."

@github-actions

github-actions Bot commented Apr 28, 2026

Copy link
Copy Markdown
Contributor

🤖 Claude Code Review

Status: Complete


Current Review:

This PR accurately clarifies documentation to prevent future misreadings of the ValidateTransactionBatch.Valid field behavior.

Verification:

  • ✅ Inline comment at services/validator/Server.go:586-587 correctly explains that Valid: true is by design for wire compatibility
  • ✅ Proto field comment at validator_api.proto:85 now explicitly states "Always true; callers must inspect per-item errors below"
  • ✅ Existing godoc (line 532-534) already documented this contract: "this method always returns a success status at the batch level"
  • ✅ Test at Server_coverage_test.go:383 confirms require.True(t, response.Valid) for partial failures
  • ✅ Implementation at Server.go:588 hardcodes Valid: true

Documentation accuracy: All documentation changes match the actual implementation. No discrepancies found.

Previously fixed:

  • ✅ Comment grammar ("we don't want to cancel the context...") - corrected in current version

@github-actions

github-actions Bot commented Apr 28, 2026

Copy link
Copy Markdown
Contributor

Benchmark Comparison Report

Baseline: main (unknown)

Current: PR-769 (cdbdce2)

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.673µ 1.708µ ~ 0.200
SplitSyncedParentMap_SetIfNotExists/256_buckets-4 61.60n 61.60n ~ 1.000
SplitSyncedParentMap_SetIfNotExists/16_buckets-4 61.51n 61.43n ~ 0.800
SplitSyncedParentMap_SetIfNotExists/1_bucket-4 61.40n 61.52n ~ 0.200
SplitSyncedParentMap_ConcurrentSetIfNotExists/256_buckets... 30.36n 30.07n ~ 0.700
SplitSyncedParentMap_ConcurrentSetIfNotExists/16_buckets_... 51.07n 50.72n ~ 0.400
SplitSyncedParentMap_ConcurrentSetIfNotExists/1_bucket_pa... 106.0n 108.4n ~ 0.100
MiningCandidate_Stringify_Short-4 263.2n 262.9n ~ 0.400
MiningCandidate_Stringify_Long-4 1.890µ 1.891µ ~ 0.800
MiningSolution_Stringify-4 977.9n 979.7n ~ 0.700
BlockInfo_MarshalJSON-4 1.750µ 1.751µ ~ 0.800
NewFromBytes-4 127.1n 127.3n ~ 0.400
Mine_EasyDifficulty-4 66.87µ 66.88µ ~ 1.000
Mine_WithAddress-4 6.850µ 7.579µ ~ 0.100
BlockAssembler_AddTx-4 0.02854n 0.03290n ~ 0.100
AddNode-4 11.11 10.59 ~ 0.200
AddNodeWithMap-4 11.06 10.90 ~ 0.700
DirectSubtreeAdd/4_per_subtree-4 60.55n 62.02n ~ 0.700
DirectSubtreeAdd/64_per_subtree-4 28.33n 28.96n ~ 0.700
DirectSubtreeAdd/256_per_subtree-4 27.20n 27.33n ~ 0.700
DirectSubtreeAdd/1024_per_subtree-4 26.18n 26.28n ~ 0.100
DirectSubtreeAdd/2048_per_subtree-4 25.87n 25.96n ~ 0.400
SubtreeProcessorAdd/4_per_subtree-4 282.2n 281.8n ~ 1.000
SubtreeProcessorAdd/64_per_subtree-4 272.0n 273.9n ~ 1.000
SubtreeProcessorAdd/256_per_subtree-4 276.4n 275.8n ~ 1.000
SubtreeProcessorAdd/1024_per_subtree-4 269.1n 268.7n ~ 1.000
SubtreeProcessorAdd/2048_per_subtree-4 267.9n 268.3n ~ 0.500
SubtreeProcessorRotate/4_per_subtree-4 275.1n 276.3n ~ 0.400
SubtreeProcessorRotate/64_per_subtree-4 271.9n 276.4n ~ 0.100
SubtreeProcessorRotate/256_per_subtree-4 273.2n 274.6n ~ 0.400
SubtreeProcessorRotate/1024_per_subtree-4 273.4n 274.6n ~ 0.400
SubtreeNodeAddOnly/4_per_subtree-4 54.58n 54.61n ~ 0.700
SubtreeNodeAddOnly/64_per_subtree-4 34.35n 34.53n ~ 0.300
SubtreeNodeAddOnly/256_per_subtree-4 33.50n 33.40n ~ 0.400
SubtreeNodeAddOnly/1024_per_subtree-4 32.74n 32.76n ~ 0.300
SubtreeCreationOnly/4_per_subtree-4 113.5n 113.6n ~ 0.500
SubtreeCreationOnly/64_per_subtree-4 399.5n 399.7n ~ 1.000
SubtreeCreationOnly/256_per_subtree-4 1.334µ 1.416µ ~ 0.100
SubtreeCreationOnly/1024_per_subtree-4 4.383µ 4.427µ ~ 0.400
SubtreeCreationOnly/2048_per_subtree-4 7.957µ 8.314µ ~ 0.100
SubtreeProcessorOverheadBreakdown/64_per_subtree-4 271.7n 273.9n ~ 0.700
SubtreeProcessorOverheadBreakdown/1024_per_subtree-4 270.2n 273.9n ~ 0.600
ParallelGetAndSetIfNotExists/1k_nodes-4 806.4µ 588.0µ ~ 0.100
ParallelGetAndSetIfNotExists/10k_nodes-4 1.571m 1.341m ~ 0.100
ParallelGetAndSetIfNotExists/50k_nodes-4 6.614m 6.804m ~ 0.100
ParallelGetAndSetIfNotExists/100k_nodes-4 13.34m 13.58m ~ 0.200
SequentialGetAndSetIfNotExists/1k_nodes-4 655.7µ 678.7µ ~ 0.100
SequentialGetAndSetIfNotExists/10k_nodes-4 2.767m 2.937m ~ 0.100
SequentialGetAndSetIfNotExists/50k_nodes-4 10.46m 10.60m ~ 0.100
SequentialGetAndSetIfNotExists/100k_nodes-4 20.16m 20.35m ~ 0.100
ProcessOwnBlockSubtreeNodesParallel/1k_nodes-4 634.7µ 643.2µ ~ 0.200
ProcessOwnBlockSubtreeNodesParallel/10k_nodes-4 4.200m 4.203m ~ 0.700
ProcessOwnBlockSubtreeNodesParallel/100k_nodes-4 16.66m 16.58m ~ 0.700
ProcessOwnBlockSubtreeNodesSequential/1k_nodes-4 699.9µ 709.3µ ~ 0.100
ProcessOwnBlockSubtreeNodesSequential/10k_nodes-4 5.764m 5.692m ~ 0.100
ProcessOwnBlockSubtreeNodesSequential/100k_nodes-4 38.03m 37.76m ~ 0.100
DiskTxMap_SetIfNotExists-4 3.493µ 3.689µ ~ 0.400
DiskTxMap_SetIfNotExists_Parallel-4 3.344µ 3.484µ ~ 0.400
DiskTxMap_ExistenceOnly-4 303.6n 319.2n ~ 0.100
Queue-4 196.9n 197.4n ~ 1.000
AtomicPointer-4 4.704n 4.566n ~ 0.700
ReorgOptimizations/DedupFilterPipeline/Old/10K-4 847.2µ 859.7µ ~ 0.100
ReorgOptimizations/DedupFilterPipeline/New/10K-4 817.4µ 870.1µ ~ 0.100
ReorgOptimizations/AllMarkFalse/Old/10K-4 105.9µ 112.9µ ~ 0.700
ReorgOptimizations/AllMarkFalse/New/10K-4 61.83µ 62.13µ ~ 0.400
ReorgOptimizations/HashSlicePool/Old/10K-4 66.85µ 66.76µ ~ 1.000
ReorgOptimizations/HashSlicePool/New/10K-4 11.92µ 11.74µ ~ 1.000
ReorgOptimizations/NodeFlags/Old/10K-4 5.997µ 5.293µ ~ 0.100
ReorgOptimizations/NodeFlags/New/10K-4 2.333µ 1.817µ ~ 0.100
ReorgOptimizations/DedupFilterPipeline/Old/100K-4 10.30m 10.37m ~ 0.700
ReorgOptimizations/DedupFilterPipeline/New/100K-4 10.30m 10.60m ~ 0.400
ReorgOptimizations/AllMarkFalse/Old/100K-4 1.180m 1.125m ~ 0.100
ReorgOptimizations/AllMarkFalse/New/100K-4 678.4µ 685.1µ ~ 0.100
ReorgOptimizations/HashSlicePool/Old/100K-4 697.8µ 682.3µ ~ 0.700
ReorgOptimizations/HashSlicePool/New/100K-4 297.2µ 325.0µ ~ 0.400
ReorgOptimizations/NodeFlags/Old/100K-4 53.93µ 55.19µ ~ 0.400
ReorgOptimizations/NodeFlags/New/100K-4 19.53µ 19.84µ ~ 0.100
TxMapSetIfNotExists-4 51.33n 51.53n ~ 0.700
TxMapSetIfNotExistsDuplicate-4 38.15n 37.90n ~ 1.000
ChannelSendReceive-4 591.3n 593.1n ~ 0.700
CalcBlockWork-4 515.3n 583.1n ~ 0.700
CalculateWork-4 702.8n 720.1n ~ 0.100
BuildBlockLocatorString_Helpers/Size_10-4 1.351µ 1.322µ ~ 0.100
BuildBlockLocatorString_Helpers/Size_100-4 14.20µ 13.07µ ~ 0.700
BuildBlockLocatorString_Helpers/Size_1000-4 126.5µ 125.3µ ~ 0.700
CatchupWithHeaderCache-4 104.3m 104.3m ~ 1.000
_BufferPoolAllocation/16KB-4 3.486µ 3.438µ ~ 0.100
_BufferPoolAllocation/32KB-4 9.569µ 7.031µ ~ 0.100
_BufferPoolAllocation/64KB-4 17.09µ 16.26µ ~ 0.700
_BufferPoolAllocation/128KB-4 32.46µ 29.06µ ~ 0.100
_BufferPoolAllocation/512KB-4 118.7µ 117.8µ ~ 0.700
_BufferPoolConcurrent/32KB-4 19.87µ 19.24µ ~ 0.100
_BufferPoolConcurrent/64KB-4 31.37µ 30.63µ ~ 0.200
_BufferPoolConcurrent/512KB-4 146.4µ 146.4µ ~ 1.000
_SubtreeDeserializationWithBufferSizes/16KB-4 638.1µ 638.6µ ~ 0.700
_SubtreeDeserializationWithBufferSizes/32KB-4 633.5µ 632.7µ ~ 1.000
_SubtreeDeserializationWithBufferSizes/64KB-4 647.9µ 635.3µ ~ 0.700
_SubtreeDeserializationWithBufferSizes/128KB-4 648.2µ 632.6µ ~ 0.200
_SubtreeDeserializationWithBufferSizes/512KB-4 677.9µ 649.9µ ~ 0.700
_SubtreeDataDeserializationWithBufferSizes/16KB-4 35.61m 35.68m ~ 0.400
_SubtreeDataDeserializationWithBufferSizes/32KB-4 35.20m 35.34m ~ 0.700
_SubtreeDataDeserializationWithBufferSizes/64KB-4 35.09m 35.41m ~ 0.700
_SubtreeDataDeserializationWithBufferSizes/128KB-4 35.40m 35.61m ~ 0.400
_SubtreeDataDeserializationWithBufferSizes/512KB-4 35.38m 35.06m ~ 0.700
_PooledVsNonPooled/Pooled-4 737.6n 739.3n ~ 1.000
_PooledVsNonPooled/NonPooled-4 6.689µ 6.586µ ~ 0.700
_MemoryFootprint/Current_512KB_32concurrent-4 6.682µ 6.791µ ~ 0.100
_MemoryFootprint/Proposed_32KB_32concurrent-4 9.441µ 9.362µ ~ 1.000
_MemoryFootprint/Alternative_64KB_32concurrent-4 8.867µ 8.957µ ~ 0.100
_prepareTxsPerLevel-4 415.0m 413.1m ~ 1.000
_prepareTxsPerLevelOrdered-4 3.753m 3.940m ~ 0.400
_prepareTxsPerLevel_Comparison/Original-4 425.4m 420.6m ~ 0.400
_prepareTxsPerLevel_Comparison/Optimized-4 3.813m 3.854m ~ 0.400
SubtreeSizes/10k_tx_4_per_subtree-4 1.424m 1.417m ~ 0.700
SubtreeSizes/10k_tx_16_per_subtree-4 335.2µ 333.2µ ~ 0.700
SubtreeSizes/10k_tx_64_per_subtree-4 79.67µ 79.12µ ~ 0.700
SubtreeSizes/10k_tx_256_per_subtree-4 19.75µ 19.73µ ~ 1.000
SubtreeSizes/10k_tx_512_per_subtree-4 9.811µ 9.799µ ~ 1.000
SubtreeSizes/10k_tx_1024_per_subtree-4 4.855µ 4.875µ ~ 0.400
SubtreeSizes/10k_tx_2k_per_subtree-4 2.450µ 2.408µ ~ 0.100
BlockSizeScaling/10k_tx_64_per_subtree-4 77.33µ 76.63µ ~ 0.200
BlockSizeScaling/10k_tx_256_per_subtree-4 19.66µ 19.18µ ~ 0.200
BlockSizeScaling/10k_tx_1024_per_subtree-4 4.899µ 4.811µ ~ 0.200
BlockSizeScaling/50k_tx_64_per_subtree-4 411.2µ 401.7µ ~ 0.200
BlockSizeScaling/50k_tx_256_per_subtree-4 97.22µ 96.95µ ~ 1.000
BlockSizeScaling/50k_tx_1024_per_subtree-4 23.91µ 24.18µ ~ 0.700
SubtreeAllocations/small_subtrees_exists_check-4 164.1µ 166.6µ ~ 0.100
SubtreeAllocations/small_subtrees_data_fetch-4 172.2µ 173.7µ ~ 0.700
SubtreeAllocations/small_subtrees_full_validation-4 340.6µ 340.0µ ~ 0.700
SubtreeAllocations/medium_subtrees_exists_check-4 9.748µ 9.778µ ~ 0.700
SubtreeAllocations/medium_subtrees_data_fetch-4 10.24µ 10.29µ ~ 0.400
SubtreeAllocations/medium_subtrees_full_validation-4 19.72µ 19.70µ ~ 1.000
SubtreeAllocations/large_subtrees_exists_check-4 2.351µ 2.330µ ~ 0.400
SubtreeAllocations/large_subtrees_data_fetch-4 2.501µ 2.467µ ~ 0.100
SubtreeAllocations/large_subtrees_full_validation-4 4.913µ 4.924µ ~ 0.400
StoreBlock_Sequential/BelowCSVHeight-4 332.8µ 328.5µ ~ 0.700
StoreBlock_Sequential/AboveCSVHeight-4 333.8µ 327.6µ ~ 0.400
GetUtxoHashes-4 257.2n 257.8n ~ 0.700
GetUtxoHashes_ManyOutputs-4 46.50µ 42.79µ ~ 0.100
_NewMetaDataFromBytes-4 238.1n 237.6n ~ 0.700
_Bytes-4 621.1n 613.6n ~ 0.100
_MetaBytes-4 564.0n 560.0n ~ 0.200

Threshold: >10% with p < 0.05 | Generated: 2026-05-12 17:00 UTC

@ordishs ordishs requested review from freemans13 and icellan May 12, 2026 15:01
The batch endpoint's Valid flag is intentionally always-true at the wire
level; callers must inspect per-item Errors. Tighten the .proto comment
and add an inline comment in Server.go to make the contract explicit
and prevent future "fixes" from misreading the field's semantics.

refs #4567
@ordishs ordishs force-pushed the security/4567-validate-batch-valid-flag branch from 515e5c7 to 2c4091e Compare May 12, 2026 16:42
@ordishs ordishs changed the title fix(validator): set Valid=false when ValidateTransactionBatch has per-item errors docs(validator): clarify ValidateTransactionBatch.Valid is always true May 12, 2026
Make explicit that returning nil prevents the errgroup context from
being cancelled, which would interrupt the other in-flight transactions
in the batch.
Comment thread services/validator/Server.go Outdated
metaData[idx] = validatorResponse.Metadata
errReasons[idx] = errors.Wrap(err)

// Never return an error because we don't the other transaction to stop.

@github-actions github-actions Bot May 12, 2026

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.

[Minor] Typo in comment: "we don't the other transaction" should be "we don't want the other transactions" (or "we don't want other transactions").

Fixed - Now reads: "Never return an error because we don't want to cancel the context for other transactions in the batch."

@sonarqubecloud

Copy link
Copy Markdown

@ordishs ordishs merged commit caeacc1 into bsv-blockchain:main May 12, 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