Skip to content

feat: centralized peer registry, transport abstraction, and LEGACYSYNCING removal - #565

Closed
oskarszoon wants to merge 69 commits into
bsv-blockchain:mainfrom
oskarszoon:feature/legacy-peer-registry
Closed

oskarszoon wants to merge 69 commits into
bsv-blockchain:mainfrom
oskarszoon:feature/legacy-peer-registry

Conversation

@oskarszoon

@oskarszoon oskarszoon commented Mar 9, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Centralized peer registry in the blockchain service — transport-agnostic, thread-safe store with reputation scoring, gRPC API (RegisterPeer/UpdatePeerMetrics/RemovePeer/ListPeers/GetPeer), JSON persistence, and ban management with decay.
  • Delegated wire-protocol catchup — BlockValidation orchestrates catchup centrally; for wire peers it calls Legacy's DelegateCatchup server-streaming gRPC. Legacy runs its existing headers-first sync and streams progress back. If Legacy's own startSync is already running when the request arrives, Legacy attaches to that sync rather than starting a duplicate.
  • FSM consolidation — removed LEGACYSYNCING; both legacy and HTTP catchup now use CATCHINGBLOCKS. BlockValidation owns the CATCHINGBLOCKS transition during catchup.
  • Dual-write registration — Legacy and P2P each register their peers into the central registry independently. BlockValidation queries it to pick the best catchup peer.
  • Dashboard integrationblocksValidated/blocksFetched and current height populated from the delegated progress stream so the existing catchup banner works for wire syncs too.

Architecture

BlockValidation poller
  ├─ HTTP peer  → catchup() (existing DataHub pipeline)
  └─ Wire peer  → catchupViaLegacy()
                    ├─ acquireCatchupLock + FSM → CATCHINGBLOCKS
                    ├─ DelegateCatchup gRPC stream → Legacy
                    │     └─ RunDelegatedCatchup
                    │          ├─ Attach to existing sync, OR
                    │          └─ Start new sync
                    │          └─ handleBlockMsg hook → progress channel
                    └─ Read progress stream → dashboard counters

Key Design Decisions

  • Delegate, don't reimplement: rather than driving wire sync block-by-block from BlockValidation via per-call RPCs, a single DelegateCatchup stream lets Legacy use its battle-tested wire pipeline.
  • Registry in blockchain service: already the FSM authority, no new service.
  • Fire-and-forget registry writes: legacy/P2P updates are best-effort; the system continues if the registry is unreachable.
  • Wire peers without BlockHash: Legacy manages block selection, so the poller allows nil BlockHash for wire-protocol peers.
  • FSM guarding during delegated catchup: legacy's RUN transitions are suppressed via sm.delegated.active so BlockValidation owns the state machine.

Test Plan

  • make test passes
  • make smoketest passes
  • No LEGACYSYNCING references remain in Go/proto sources
  • Manual: node syncs from a legacy wire peer via delegated catchup
  • Manual: dashboard catchup banner shows progress + height during wire sync
  • Manual: peer registry shows legacy:/Bitcoin SV:x.x.x/ entries with reputation rising on accepted blocks
  • Manual: poller selects wire peer when it has the higher tip; HTTP peer otherwise
  • Manual: peer registry persists and reloads across restart

@oskarszoon
oskarszoon force-pushed the feature/legacy-peer-registry branch from cd79d69 to 37eeff4 Compare March 18, 2026 12:22
@github-actions

github-actions Bot commented Mar 27, 2026

Copy link
Copy Markdown
Contributor

Benchmark Comparison Report

Baseline: main (unknown)

Current: PR-565 (91a9bcf7)

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.659µ 1.677µ ~ 0.300
SplitSyncedParentMap_SetIfNotExists/256_buckets-4 59.41n 59.38n ~ 1.000
SplitSyncedParentMap_SetIfNotExists/16_buckets-4 59.15n 59.13n ~ 1.000
SplitSyncedParentMap_SetIfNotExists/1_bucket-4 59.26n 59.25n ~ 1.000
SplitSyncedParentMap_ConcurrentSetIfNotExists/256_buckets... 33.66n 33.35n ~ 1.000
SplitSyncedParentMap_ConcurrentSetIfNotExists/16_buckets_... 59.96n 56.84n ~ 0.100
SplitSyncedParentMap_ConcurrentSetIfNotExists/1_bucket_pa... 146.8n 148.9n ~ 0.700
MiningCandidate_Stringify_Short-4 249.3n 250.2n ~ 0.400
MiningCandidate_Stringify_Long-4 1.735µ 1.733µ ~ 1.000
MiningSolution_Stringify-4 898.0n 904.1n ~ 0.700
BlockInfo_MarshalJSON-4 1.716µ 1.698µ ~ 0.100
NewFromBytes-4 128.8n 156.2n ~ 0.300
Mine_EasyDifficulty-4 60.80µ 60.91µ ~ 0.700
Mine_WithAddress-4 7.077µ 6.838µ ~ 0.200
BlockAssembler_AddTx-4 0.02751n 0.02454n ~ 0.700
AddNode-4 10.92 10.90 ~ 1.000
AddNodeWithMap-4 11.18 11.44 ~ 0.100
DirectSubtreeAdd/4_per_subtree-4 61.41n 61.01n ~ 1.000
DirectSubtreeAdd/64_per_subtree-4 29.55n 29.25n ~ 1.000
DirectSubtreeAdd/256_per_subtree-4 27.66n 28.01n ~ 0.100
DirectSubtreeAdd/1024_per_subtree-4 26.58n 26.63n ~ 0.400
DirectSubtreeAdd/2048_per_subtree-4 26.07n 26.22n ~ 0.200
SubtreeProcessorAdd/4_per_subtree-4 285.5n 284.7n ~ 0.700
SubtreeProcessorAdd/64_per_subtree-4 279.4n 274.7n ~ 0.400
SubtreeProcessorAdd/256_per_subtree-4 275.8n 276.6n ~ 0.100
SubtreeProcessorAdd/1024_per_subtree-4 268.1n 270.8n ~ 0.300
SubtreeProcessorAdd/2048_per_subtree-4 269.4n 271.8n ~ 0.100
SubtreeProcessorRotate/4_per_subtree-4 272.4n 272.0n ~ 1.000
SubtreeProcessorRotate/64_per_subtree-4 271.5n 271.0n ~ 0.800
SubtreeProcessorRotate/256_per_subtree-4 271.5n 271.4n ~ 1.000
SubtreeProcessorRotate/1024_per_subtree-4 270.7n 271.5n ~ 1.000
SubtreeNodeAddOnly/4_per_subtree-4 54.97n 55.44n ~ 0.300
SubtreeNodeAddOnly/64_per_subtree-4 36.06n 36.22n ~ 0.100
SubtreeNodeAddOnly/256_per_subtree-4 35.13n 35.28n ~ 0.700
SubtreeNodeAddOnly/1024_per_subtree-4 34.43n 34.59n ~ 0.100
SubtreeCreationOnly/4_per_subtree-4 109.3n 110.2n ~ 0.100
SubtreeCreationOnly/64_per_subtree-4 347.3n 351.8n ~ 0.400
SubtreeCreationOnly/256_per_subtree-4 1.218µ 1.217µ ~ 1.000
SubtreeCreationOnly/1024_per_subtree-4 3.816µ 3.985µ ~ 0.100
SubtreeCreationOnly/2048_per_subtree-4 6.733µ 7.195µ ~ 0.100
SubtreeProcessorOverheadBreakdown/64_per_subtree-4 271.5n 268.2n ~ 0.100
SubtreeProcessorOverheadBreakdown/1024_per_subtree-4 270.6n 268.1n ~ 0.200
ParallelGetAndSetIfNotExists/1k_nodes-4 543.1µ 556.5µ ~ 0.200
ParallelGetAndSetIfNotExists/10k_nodes-4 1.342m 1.379m ~ 0.100
ParallelGetAndSetIfNotExists/50k_nodes-4 6.596m 6.684m ~ 0.700
ParallelGetAndSetIfNotExists/100k_nodes-4 13.14m 13.47m ~ 0.100
SequentialGetAndSetIfNotExists/1k_nodes-4 622.6µ 626.0µ ~ 1.000
SequentialGetAndSetIfNotExists/10k_nodes-4 2.856m 2.952m ~ 0.100
SequentialGetAndSetIfNotExists/50k_nodes-4 11.01m 11.81m ~ 0.100
SequentialGetAndSetIfNotExists/100k_nodes-4 21.24m 21.84m ~ 0.100
ProcessOwnBlockSubtreeNodesParallel/1k_nodes-4 593.6µ 593.5µ ~ 1.000
ProcessOwnBlockSubtreeNodesParallel/10k_nodes-4 4.644m 4.571m ~ 0.400
ProcessOwnBlockSubtreeNodesParallel/100k_nodes-4 17.01m 16.95m ~ 0.700
ProcessOwnBlockSubtreeNodesSequential/1k_nodes-4 665.6µ 664.2µ ~ 1.000
ProcessOwnBlockSubtreeNodesSequential/10k_nodes-4 6.276m 6.307m ~ 1.000
ProcessOwnBlockSubtreeNodesSequential/100k_nodes-4 40.64m 39.54m ~ 0.100
DiskTxMap_SetIfNotExists-4 3.733µ 3.903µ ~ 0.700
DiskTxMap_SetIfNotExists_Parallel-4 3.531µ 3.709µ ~ 0.400
DiskTxMap_ExistenceOnly-4 316.6n 325.5n ~ 0.100
Queue-4 194.4n 191.4n ~ 0.100
AtomicPointer-4 3.715n 3.720n ~ 0.100
ReorgOptimizations/DedupFilterPipeline/Old/10K-4 844.8µ 869.9µ ~ 0.700
ReorgOptimizations/DedupFilterPipeline/New/10K-4 802.0µ 882.9µ ~ 0.100
ReorgOptimizations/AllMarkFalse/Old/10K-4 108.4µ 123.5µ ~ 0.100
ReorgOptimizations/AllMarkFalse/New/10K-4 64.07µ 64.30µ ~ 1.000
ReorgOptimizations/HashSlicePool/Old/10K-4 59.10µ 67.54µ ~ 0.400
ReorgOptimizations/HashSlicePool/New/10K-4 10.93µ 11.21µ ~ 0.400
ReorgOptimizations/NodeFlags/Old/10K-4 5.216µ 6.230µ ~ 0.100
ReorgOptimizations/NodeFlags/New/10K-4 1.782µ 2.686µ ~ 0.100
ReorgOptimizations/DedupFilterPipeline/Old/100K-4 11.78m 11.63m ~ 0.700
ReorgOptimizations/DedupFilterPipeline/New/100K-4 10.68m 10.33m ~ 0.100
ReorgOptimizations/AllMarkFalse/Old/100K-4 1.156m 1.185m ~ 0.100
ReorgOptimizations/AllMarkFalse/New/100K-4 708.5µ 702.3µ ~ 0.200
ReorgOptimizations/HashSlicePool/Old/100K-4 584.3µ 586.0µ ~ 1.000
ReorgOptimizations/HashSlicePool/New/100K-4 199.5µ 203.8µ ~ 0.400
ReorgOptimizations/NodeFlags/Old/100K-4 56.03µ 54.88µ ~ 0.700
ReorgOptimizations/NodeFlags/New/100K-4 19.62µ 19.21µ ~ 0.700
TxMapSetIfNotExists-4 46.77n 46.49n ~ 0.100
TxMapSetIfNotExistsDuplicate-4 38.90n 38.88n ~ 1.000
ChannelSendReceive-4 580.7n 603.6n ~ 0.100
CalcBlockWork-4 510.7n 541.3n ~ 1.000
CalculateWork-4 685.6n 690.8n ~ 0.700
BuildBlockLocatorString_Helpers/Size_10-4 1.020µ 1.024µ ~ 0.400
BuildBlockLocatorString_Helpers/Size_100-4 10.922µ 9.679µ ~ 0.400
BuildBlockLocatorString_Helpers/Size_1000-4 96.27µ 97.62µ ~ 0.700
CatchupWithHeaderCache-4 103.5m 103.8m ~ 0.200
_BufferPoolAllocation/16KB-4 4.365µ 3.488µ ~ 0.100
_BufferPoolAllocation/32KB-4 8.605µ 9.514µ ~ 0.400
_BufferPoolAllocation/64KB-4 14.25µ 16.63µ ~ 0.100
_BufferPoolAllocation/128KB-4 28.49µ 30.90µ ~ 0.100
_BufferPoolAllocation/512KB-4 109.7µ 120.8µ ~ 0.100
_BufferPoolConcurrent/32KB-4 17.36µ 19.27µ ~ 0.100
_BufferPoolConcurrent/64KB-4 28.16µ 30.27µ ~ 0.100
_BufferPoolConcurrent/512KB-4 148.6µ 150.8µ ~ 0.700
_SubtreeDeserializationWithBufferSizes/16KB-4 623.8µ 639.9µ ~ 0.400
_SubtreeDeserializationWithBufferSizes/32KB-4 651.8µ 658.1µ ~ 0.700
_SubtreeDeserializationWithBufferSizes/64KB-4 627.7µ 643.2µ ~ 0.200
_SubtreeDeserializationWithBufferSizes/128KB-4 643.6µ 646.1µ ~ 1.000
_SubtreeDeserializationWithBufferSizes/512KB-4 655.0µ 652.0µ ~ 0.200
_SubtreeDataDeserializationWithBufferSizes/16KB-4 35.80m 36.05m ~ 0.100
_SubtreeDataDeserializationWithBufferSizes/32KB-4 35.67m 36.10m ~ 0.100
_SubtreeDataDeserializationWithBufferSizes/64KB-4 35.98m 35.78m ~ 0.400
_SubtreeDataDeserializationWithBufferSizes/128KB-4 35.85m 36.03m ~ 0.100
_SubtreeDataDeserializationWithBufferSizes/512KB-4 35.37m 35.61m ~ 0.400
_PooledVsNonPooled/Pooled-4 739.5n 739.3n ~ 0.800
_PooledVsNonPooled/NonPooled-4 6.966µ 6.780µ ~ 0.200
_MemoryFootprint/Current_512KB_32concurrent-4 6.773µ 7.050µ ~ 0.100
_MemoryFootprint/Proposed_32KB_32concurrent-4 9.319µ 9.948µ ~ 0.100
_MemoryFootprint/Alternative_64KB_32concurrent-4 9.090µ 9.813µ ~ 0.100
SubtreeSizes/10k_tx_4_per_subtree-4 1.349m 1.369m ~ 1.000
SubtreeSizes/10k_tx_16_per_subtree-4 325.8µ 319.5µ ~ 0.400
SubtreeSizes/10k_tx_64_per_subtree-4 76.28µ 76.16µ ~ 0.700
SubtreeSizes/10k_tx_256_per_subtree-4 19.20µ 18.91µ ~ 0.700
SubtreeSizes/10k_tx_512_per_subtree-4 9.438µ 9.444µ ~ 1.000
SubtreeSizes/10k_tx_1024_per_subtree-4 4.757µ 4.700µ ~ 0.100
SubtreeSizes/10k_tx_2k_per_subtree-4 2.359µ 2.334µ ~ 0.100
BlockSizeScaling/10k_tx_64_per_subtree-4 76.11µ 74.52µ ~ 0.200
BlockSizeScaling/10k_tx_256_per_subtree-4 18.92µ 19.04µ ~ 0.700
BlockSizeScaling/10k_tx_1024_per_subtree-4 4.780µ 4.610µ ~ 0.100
BlockSizeScaling/50k_tx_64_per_subtree-4 396.7µ 392.8µ ~ 0.100
BlockSizeScaling/50k_tx_256_per_subtree-4 94.62µ 93.31µ ~ 0.700
BlockSizeScaling/50k_tx_1024_per_subtree-4 23.44µ 23.01µ ~ 0.700
SubtreeAllocations/small_subtrees_exists_check-4 164.1µ 159.5µ ~ 0.700
SubtreeAllocations/small_subtrees_data_fetch-4 165.6µ 163.3µ ~ 0.400
SubtreeAllocations/small_subtrees_full_validation-4 328.4µ 323.7µ ~ 0.100
SubtreeAllocations/medium_subtrees_exists_check-4 9.380µ 9.474µ ~ 0.400
SubtreeAllocations/medium_subtrees_data_fetch-4 9.780µ 9.542µ ~ 0.100
SubtreeAllocations/medium_subtrees_full_validation-4 19.07µ 18.82µ ~ 0.200
SubtreeAllocations/large_subtrees_exists_check-4 2.246µ 2.238µ ~ 1.000
SubtreeAllocations/large_subtrees_data_fetch-4 2.384µ 2.331µ ~ 0.100
SubtreeAllocations/large_subtrees_full_validation-4 4.785µ 4.689µ ~ 0.100
_prepareTxsPerLevel-4 401.0m 405.5m ~ 1.000
_prepareTxsPerLevelOrdered-4 3.451m 3.524m ~ 0.100
_prepareTxsPerLevel_Comparison/Original-4 401.9m 400.8m ~ 0.400
_prepareTxsPerLevel_Comparison/Optimized-4 3.441m 3.460m ~ 0.200
StoreBlock_Sequential/BelowCSVHeight-4 334.7µ 328.8µ ~ 0.700
StoreBlock_Sequential/AboveCSVHeight-4 328.4µ 326.1µ ~ 0.400
GetUtxoHashes-4 259.1n 256.1n ~ 1.000
GetUtxoHashes_ManyOutputs-4 44.55µ 42.95µ ~ 0.200
_NewMetaDataFromBytes-4 237.8n 238.5n ~ 1.000
_Bytes-4 626.2n 629.3n ~ 0.400
_MetaBytes-4 564.6n 568.4n ~ 0.500

Threshold: >10% with p < 0.05 | Generated: 2026-05-08 10:09 UTC

@oskarszoon
oskarszoon force-pushed the feature/legacy-peer-registry branch from 571adfd to 6a0b37c Compare March 27, 2026 13:53
@oskarszoon oskarszoon self-assigned this Mar 27, 2026
@oskarszoon oskarszoon changed the title Improve legacy vs peer syncing feat: centralized peer registry, transport abstraction, and LEGACYSYNCING removal Mar 27, 2026
@oskarszoon
oskarszoon force-pushed the feature/legacy-peer-registry branch 6 times, most recently from 53e28e8 to 3ed504d Compare March 27, 2026 15:45
…CING removal

Introduce a centralized peer registry in the blockchain service that tracks
peers across both HTTP (P2P/DataHub) and wire protocol (legacy Bitcoin)
transports. This replaces the fragmented per-service peer tracking with a
single source of truth for peer information, reputation scoring, and
transport-aware catchup orchestration.

Key changes:

Centralized Peer Registry (blockchain service):
- Thread-safe in-memory registry with reputation scoring algorithm
- gRPC API: RegisterPeer, UpdatePeerMetrics, RemovePeer, ListPeers, GetPeer
- JSON file persistence with atomic writes and TTL-based cleanup
- PeerRegistryClientI interface with connection ownership tracking

Transport Abstraction (blockvalidation):
- CatchupTransport interface abstracting HTTP and wire protocol fetching
- HTTPTransport: extracts existing DataHub HTTP fetch logic
- WireTransport: delegates to Legacy service via gRPC for wire protocol
- Central registry polling for autonomous catchup orchestration

Legacy Service Integration:
- FetchHeadersFromPeer/FetchBlockFromPeer gRPC endpoints for wire protocol
- Dual-write to central registry on peer connect/disconnect/metrics
- One-shot request pattern with LoadOrStore for concurrency safety

FSM Simplification:
- Remove LEGACYSYNCING state — consolidate into CATCHINGBLOCKS
- Simplify P2P SyncCoordinator (remove Kafka catchup publishing)
- Legacy SyncManager now delegates catchup to BlockValidation
- All FSM transitions and references updated across codebase

Proto Changes:
- Add PeerRegistryService to blockchain_api.proto
- Add FetchHeadersFromPeer/FetchBlockFromPeer to legacy peer_api.proto
- Reserve removed LEGACYSYNCING/LEGACYSYNC enum values
- Regenerate all protobuf Go code
- Remove all LEGACYSYNCING/LEGACYSYNC references from miner guides,
  CLI docs, sync tutorials, dashboard docs, and protobuf docs
- Update mermaid sync state flow diagram for 3-state FSM
- Remove dead LEGACYSYNC event handling from dashboard UI
- Remove legacy sync button, state colors, and API functions from dashboard
- Fix pre-existing markdown lint issues (MD025, MD036, MD051)
- Delete obsolete fsm_legacy_sync PlantUML diagram and SVG
@oskarszoon
oskarszoon force-pushed the feature/legacy-peer-registry branch from 3ed504d to b7cd309 Compare March 27, 2026 15:49
…s, sync coordinator

- Transition FSM to CATCHINGBLOCKS immediately when catchup starts (Step 1.5)
  so subtree validation stops processing network messages during sync
- Central registry poller: fast 3s initial interval for 10 attempts, then 30s
- Central registry poller: skip poll when catchup already in progress
- Central registry poller: prefer full nodes over pruned for catchup
- Propagate storage mode (full/pruned) to central registry via updateStorage
- SyncCoordinator: stop rotating peers when behind (defer to central poller)
- SyncCoordinator: don't clear sync peer in handleRunningState if already set
- Raise max accumulated headers to reach next checkpoint for quick validation
- Pass maxHeadersOverride through catchupGetBlockHeaders without mutating settings
- Add ban scoring with decay, threshold, auto-unban to CentralizedPeerRegistry
- Add BanConfig with defaults matching existing P2P BanManager
- Add gRPC RPCs: AddBanScore, IsPeerBanned, ListBannedPeers, ClearBannedPeers
- Add ReconsiderBadPeers for reputation recovery after cooldown
- Add StartBanDecay background goroutine for score decay
- Update PeerRegistryClientI with ban methods
- Update all mock implementations
- Track per-peer cooldowns after failed catchup attempts
- Exponential backoff: 30s, 60s, 120s, up to 5min max per peer
- Skip peers on cooldown, try next best peer instead
- Clear all cooldowns on successful catchup
- Remove peerRegistry, syncCoordinator, banManager, peerSelector from Server
- centralRegistry is now REQUIRED (checked in Init)
- All peer/ban/metrics ops go through centralRegistry gRPC
- Rewire all test files to use central registry mocks
- Add //go:build ignore to SyncCoordinator test files (code being removed)
- Adapt Server_test.go, server_handler_test.go, report_invalid_block_test.go
BlockValidation now reports catchup metrics (success, failure, malicious,
attempt) directly to the central registry via UpdatePeerMetrics instead
of routing through P2P service RPCs. p2pClient is retained for non-metric
operations (GetPeersForCatchup, RecordBytesDownloaded, parallel fetch).
Call peerRegistry.StartBanDecay(ctx) during blockchain service startup
so ban scores automatically decay over time (1 point/minute).
… PeerSelector

Remove files replaced by the centralized registry in blockchain service:
- peer_registry.go, peer_registry_cache.go and their tests
- sync_coordinator.go and all related tests
- BanManager.go, BanManager_test.go
- peer_selector.go, peer_selector_test.go
- peer_registry_reputation_test.go

Fix remaining references: remove MockPeerBanManager, BanReason refs,
use string reason constants, skip tests needing local registry rewrite.
Ban management tests (18 tests in peer_registry_ban_test.go):
- AddBanScore: scoring, threshold, decay, peer info sync, config lookup
- IsBannedPeer: not banned, banned, auto-unban on expiry
- ListBannedPeers: empty, returns only banned
- ClearBannedPeers: clears all, resets peer info
- ReconsiderBadPeers: old failures reset, recent failures kept, count
- decayBanScores: decay over time, zero-score cleanup, banned entry kept
- StartBanDecay: context cancellation

Catchup poller tests (20 tests in central_registry_poller_test.go):
- nextCooldownForPeer: exponential backoff 30s-5min, per-peer tracking
- selectBestPeersFromCentralRegistry: height filter, full>pruned sort, wire protocol
- pollCentralRegistry: no peers, isCatchingUp skip, cooldown skip, nil hash,
  error handling, expired cooldown retry
…s wiring

- Fix TestCatchup_FSMStateManagement: setFSMCatchingBlocks now calls
  GetFSMCurrentState to check if already in CATCHINGBLOCKS before
  transitioning. Update mock to expect RUNNING state first.
- Fix TestServerInit* tests: Init() now requires centralRegistry to be set.
  Add newPermissiveMockRegistry() helper and SetCentralPeerRegistry calls.
- Wire P2P ban settings (BanThreshold, BanDuration) from settings.conf
  to the central registry's BanConfig instead of using 24h default.
  Fixes TestPeerIDBanExpirationE2E smoketest.
…eer ID encoding

- Add GetFSMCurrentState mock to setupTestCatchupServer (both instances)
  for early Step 1.5 FSM transition in catchup
- Add centralRegistry to all 47 Server struct literals in P2P tests
- Fix TestServer_GetPeer: use peerID.String() for mock expectations
  (peer.ID("non-existent").String() != "non-existent" due to base58 encoding)
- Fix mockPeerRegistryClient ban methods to actually call m.Called()
  instead of returning hardcoded values (IsPeerBanned, AddBanScore, etc)
- Fix TestIsBannedChecksBothBanSystems: remove extra context arg from
  IsPeerBanned mock expectation
- Fix TestCatchup_FSMStateManagement: filter permissive GetFSMCurrentState
  before setting ordered .Once() expectations
- Fix TestCatchup/Empty_Catchup_Headers: add FSM mocks to standalone
  Server setup (CatchUpBlocks, GetFSMCurrentState, Run)
- Fix gci formatting in central_registry_test.go
- Add FSM mocks (CatchUpBlocks, GetFSMCurrentState, Run) to
  createServerWithEnhancedCatchup helper in catchup_test.go for
  TestCatchupIntegrationScenarios/Context_Cancellation_During_Catchup
Must-fix:
- Bounded worker pool (4 workers, chan size 256) replaces unbounded
  fire-and-forget goroutines for central registry updates in P2P
- List() now checks ban expiry via banScores instead of stale IsBanned field
- TransportType only updated when TransportTypeSet=true (fixes wire peer reset)
- blockHashToBytes returns defensive copy to avoid slice aliasing

Should-fix:
- Throttle updatePeerLastMessageTime (30s cooldown per peer)
- Remove duplicate addConnectedPeer (identical to addPeer)
- Simplify nextCooldownForPeer with bit shift math
- Document single-goroutine invariant on cooldown maps
- Add TODO for getPeerIDFromDataHubURL efficiency
- Fix waitForLegacyMockCalls race condition with atomic counter
- Remove dead shouldSkipDuringSync and its test

Nice-to-have:
- Remove redundant nil checks in handle_catchup_metrics.go
- Improve WireTransport error messages
- Rename baseURL to peerEndpoint in CatchupTransport interface
- Improve stub RPC logging (Debug -> Info for no-ops)
…-registry

# Conflicts:
#	services/blockchain/Client.go
#	services/legacy/netsync/manager.go
#	services/p2p/sync_coordinator.go
#	services/p2p/sync_coordinator_test.go
#	services/subtreevalidation/streaming_processor.go
#	settings/blockchain_settings.go
#	settings/settings.go
…utines

Centrifuge reconnect loop can call logger.Errorf after test teardown
begins, racing with testing framework cleanup via t.Logf. Calling
ErrorTestLogger.Shutdown() at the start of teardown gates subsequent
Errorf calls and prevents the data race flagged by CI.
Legacy wire peers don't send the node_status message that HTTP peers use
to advertise storage mode, so legacyPeerToRegistryInfo left Storage
empty. The catchup peer selector sorts Storage=="full" peers ahead of
others, so legacy peers were ranked last and HTTP teranode peers were
picked for every catchup even when legacy peers were higher.

Derive Storage from the SFNodeNetwork service flag — peers advertising
NODE_NETWORK serve full historical blocks.
Two callers of UpdatePeerMetrics in the legacy server were passing our
local accepted height as the peer's height, corrupting the registry
entry and dragging the peer's tracked tip down to whatever block we
just synced. This caused legacy peers to fall below the catchup
selector's minHeight filter and effectively never get picked.

- handleUpdatePeerHeights: use sp.LastBlock() (peer's monotonic tracked
  tip from VERSION + UpdateLastBlockHeight) instead of umsg.newHeight
- SetOnBlockAccepted: drop height argument entirely; this callback
  records interaction success only, height belongs to UpdatePeerHeights
…-registry

# Conflicts:
#	services/p2p/Server.go
#	services/p2p/peer_registry.go
#	services/p2p/peer_registry_test.go
#	services/p2p/server_helpers.go
Add unit tests targeting the newly-introduced central registry helpers
in server_helpers.go to lift Sonar coverage on new code above the 80%
quality gate threshold:

- addConnectedPeer (with/without registry)
- updateStorage (with mode, empty mode short-circuit)
- addProtocolViolation (success + registry error swallowed)
- getPeerIDFromDataHubURL (match, no match, nil registry, list error)
- getPeer (found, not found, registry error, nil registry)
- InjectPeerForTesting (Storage=full override, nil registry)
- enqueueRegistryUpdate (sync fallback, full-channel drop)
- centralPeerToLocalPeerInfo (decodable libp2p ID + legacy address fallback)
…t_peers

Lift Sonar new-code coverage above the 80% gate.

- peer_selection.go: 8 tests covering selectBestPeersForCatchup
  (nil client, error, empty, height filter, listen-only filter, full
  population, all-filtered, success-rate log paths)
- catchup_status.go: 16 tests covering pure helpers (shortHash,
  formatInt/Float/Progress/Duration, formatCatchupStatusSummary) plus
  Server-state paths (no-active, with-previous, nil-context guard,
  headers/validating/finalizing phases, summary)
- get_peers.go: response type JSON shape tests
Lift Sonar new-code coverage further toward the 80% gate.

- get_peers.go: covers the NewPeerRegistryClient-fails branch (mock
  blockchain.Mock so the nil-check passes; bogus gRPC address makes the
  registry-client construction fail). Also tests PeerInfoResponse/
  PeersResponse JSON shape.
- get_catchup_status.go: covers happy path (full status), with-previous-
  attempt path, and BlockValidation client error path.
More Sonar new-code coverage:
- tryAlternativePeersForCatchup: 4 tests covering reachable branches
  without invoking u.catchup (no peers, all excluded, all malicious,
  selection error)
- categorizeWireCatchupError: 5 tests across all switch arms (validation,
  pruned, peer_misbehavior, network, unknown default)
- SetLegacyCatchupClient setter
- catchupViaLegacy nil-client guard returns ErrServiceUnavailable
Extract the PeerInfoResponse build loop from GetPeers into a pure
peersToResponse function so it can be unit-tested without mocking
gRPC dialing. Adds tests for:

- empty input
- full conversion (all 25+ fields)
- nil BlockHash branch

Lifts get_peers.go new-code coverage from ~17% toward 80%+, closing
the remaining ~0.2% Sonar gate gap.
oskarszoon added 5 commits May 4, 2026 17:04
…rror paths

Drives catchupViaLegacy through reachable branches by mocking
blockchain.Mock + a scripted legacyCatchupClient that streams progress
events and returns an rpcErr.

- HappyPath_AllPhases: DOWNLOADING_HEADERS + DOWNLOADING_BLOCKS +
  COMPLETE phases; verifies blocksValidated counter at end.
- FailedPhase: FAILED progress with category=validation yields
  BlockInvalid via categorizeWireCatchupError.
- GRPCStreamError: empty progress stream + rpc error wrapped as
  NetworkError.
- LockAlreadyHeld: pre-existing isCatchingUp blocks acquireCatchupLock.

Lifts catchup_via_legacy.go new-code coverage from ~25% toward 80%+.
Upstream PR bsv-blockchain#812 added tests for startPeerRegistryCleanup which exercises
the local peerRegistry / NewPeerRegistry / peerRegistryCleanupTimer
machinery. This branch deleted that local registry in favour of the
centralized peer registry in the blockchain service, so the new tests
reference symbols that no longer exist and break the build.

Drop them — the central registry has its own coverage in
central_registry_test.go and server_helpers_central_test.go.
@sonarqubecloud

sonarqubecloud Bot commented May 8, 2026

Copy link
Copy Markdown

@oskarszoon

Copy link
Copy Markdown
Contributor Author

Continues in #832

@oskarszoon oskarszoon closed this May 13, 2026
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.

1 participant