Skip to content

p2p: carry received counters over GetPeerRegistry - #1717

Open
gokutheengineer wants to merge 1 commit into
bsv-blockchain:mainfrom
gokutheengineer:gokhan/p2p-peerRegistryBlocksReceived
Open

p2p: carry received counters over GetPeerRegistry#1717
gokutheengineer wants to merge 1 commit into
bsv-blockchain:mainfrom
gokutheengineer:gokhan/p2p-peerRegistryBlocksReceived

Conversation

@gokutheengineer

Copy link
Copy Markdown
Collaborator

Closes bitcoin-sv/teranode#4760.

Problem

p2p_api.PeerRegistryInfo carried none of the interaction-type counters (blocks_received, subtrees_received, transactions_received, catchup_blocks), so peerInfoToP2PProto and convertFromAPIPeerInfo silently dropped them even though blockchain.PeerInfo populates them and p2p.PeerInfo documents them as part of the public contract. The asset service's peerTierCache.refresh classifies tierMiner only when BlocksReceived > 0, and the gRPC client is the only ClientI implementation, so in a real microservice deployment no allowlisted miner was ever promoted and all were capped at the ordinary per-peer rate limit.

All cited locations confirmed still valid on upstream/main.

Fix

  • Add fields 30-32 (blocks_received, subtrees_received, transactions_received) to PeerRegistryInfo, mirroring the names already used in blockchain_api.proto, and regenerate p2p_api.pb.go. catchup_blocks is deliberately not added: nothing in the repo writes CatchupBlocks, so a wire field would be permanently zero.
  • Populate them in peerInfoToP2PProto (Server.go) and map them back in convertFromAPIPeerInfo (Client.go).
  • Update the generated proto docs table (docs/references/protobuf_docs/p2pProto.md), which was also missing the existing catchup_* rows, and correct the GetPeerRegistry doc in Interface.go, which promised "complete information" while several fields never cross the wire.

No change to the tierMiner predicate: the field is the right signal, it was just never transmitted.

Rolling upgrade: a new asset service against an old p2p service still reads BlocksReceived == 0, so miners stay at tierPeer until p2p is upgraded. Fails closed (stricter limit), same as today.

Residual

Pre-existing, not introduced here: convertFromAPIPeerInfo maps a 0 unix timestamp to time.Unix(0, 0), which is not IsZero(). No gRPC consumer relies on IsZero() today (sync_coordinator uses the in-process registry client). Worth a separate follow-up.

Tests

  • TestServer_PeerInfoToP2PProto_RoundTripFields: now asserts the three counters.
  • TestServer_GetPeerRegistry_ReceivedCountersSurviveWire (new): registry RecordBlockReceived/RecordSubtreeReceived/RecordTransactionReceived -> Server.GetPeerRegistry -> proto.Marshal/Unmarshal -> convertFromAPIPeerInfo, asserting the counters survive the wire message.
  • TestServer_PeerRegistryInfo_CarriesEveryPeerInfoField (new): reflection guard that every exported p2p.PeerInfo field has a PeerRegistryInfo counterpart unless listed in an explicit, documented not-on-wire allowlist. This turns the class of bug behind this issue into a test failure.
  • TestSimpleClientGetPeerRegistry: gRPC client maps the three proto fields onto p2p.PeerInfo.
  • TestPeerTierCache_Refresh_ClassifiesByBlocksReceived (new, asset): refresh promotes only BlocksReceived > 0 with reputation at or above the threshold to tierMiner.
SETTINGS_CONTEXT=test go test -race -tags "testtxmetacache" -count=1 ./services/p2p/...   # ok
go vet -tags testtxmetacache ./services/asset/httpimpl/ ./services/p2p/                    # ok

services/asset/httpimpl tests could not be executed locally (pre-existing GoBDK cgo link failure on this machine); they type-check and run in CI.

🤖 Generated with Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

🤖 Claude Code Review

Status: Complete

Current Review: No issues found.

Verified the fix against the code:

  • The premise holds — peerTierCache.refresh gates tierMiner on p.BlocksReceived > 0 && p.ReputationScore >= threshold, and the gRPC convertFromAPIPeerInfo/peerInfoToP2PProto path previously dropped BlocksReceived, so every miner was silently capped at the ordinary per-peer rate limit in a real microservice deployment.
  • New proto fields 30–32 do not collide (highest prior was 29). The regenerated p2p_api.pb.go (struct fields, getters, rawDesc field tags) is internally consistent with the .proto change.
  • The CatchupBlocks "no writer exists yet" rationale is correct — no assignment to CatchupBlocks exists anywhere in services/.
  • The AvgResponseTime → AvgResponseTimeMs rename mapping in the reflection guard matches wire field 22.
  • Doc updates (p2pProto.md table, Interface.go godoc) are factually accurate against the transmitted subset.

Nicely scoped: minimal diff, and TestServer_PeerRegistryInfo_CarriesEveryPeerInfoField converts this whole bug class into a compile-time guard against future silently-dropped fields.

@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
Vulnerabilities with severity Minor found (required < Minor)

See analysis details on SonarQube Cloud

@github-actions

Copy link
Copy Markdown
Contributor

Benchmark Comparison Report

Baseline: main (unknown)

Current: PR-1717 (d34a5d2)

Summary

  • Regressions: 0
  • Improvements: 0
  • Unchanged: 139
  • Significance level: p < 0.05
All benchmark results (sec/op)
Benchmark Baseline Current Change p-value
_NewBlockFromBytes-4 1.763µ 1.736µ ~ 0.100
Block_ValidOrderAndBlessed_DiskVsMemory/leaves=1024/memory-4 12.29m 12.56m ~ 0.400
Block_ValidOrderAndBlessed_DiskVsMemory/leaves=1024/disk_1-4 12.90m 12.89m ~ 0.700
Block_ValidOrderAndBlessed_DiskVsMemory/leaves=1024/disk_2-4 12.91m 12.90m ~ 0.200
Block_ValidOrderAndBlessed_DiskVsMemory/leaves=16384/memo... 29.90m 29.99m ~ 1.000
Block_ValidOrderAndBlessed_DiskVsMemory/leaves=16384/disk... 38.91m 38.58m ~ 0.400
Block_ValidOrderAndBlessed_DiskVsMemory/leaves=16384/disk... 40.43m 39.04m ~ 0.100
SplitSyncedParentMap_SetIfNotExists/256_buckets-4 64.01n 64.14n ~ 0.100
SplitSyncedParentMap_SetIfNotExists/16_buckets-4 63.94n 63.82n ~ 0.700
SplitSyncedParentMap_SetIfNotExists/1_bucket-4 63.90n 63.96n ~ 0.300
SplitSyncedParentMap_ConcurrentSetIfNotExists/256_buckets... 30.94n 30.81n ~ 0.700
SplitSyncedParentMap_ConcurrentSetIfNotExists/16_buckets_... 53.71n 52.33n ~ 0.700
SplitSyncedParentMap_ConcurrentSetIfNotExists/1_bucket_pa... 114.2n 112.1n ~ 1.000
MiningCandidate_Stringify_Short-4 242.4n 239.8n ~ 0.100
MiningCandidate_Stringify_Long-4 1.605µ 1.593µ ~ 0.600
MiningSolution_Stringify-4 782.0n 780.6n ~ 1.000
BlockInfo_MarshalJSON-4 1.589µ 1.594µ ~ 1.000
NewFromBytes-4 129.2n 127.9n ~ 0.200
AddTxBatchColumnar_Validation-4 2.022µ 2.018µ ~ 0.400
OffsetValidationLoop-4 644.6n 635.6n ~ 0.400
Mine_EasyDifficulty-4 60.82µ 61.19µ ~ 0.200
Mine_WithAddress-4 7.035µ 7.014µ ~ 0.400
BlockAssembler_AddTx-4 0.02701n 0.02809n ~ 1.000
AddNode-4 10.68 10.66 ~ 1.000
AddNodeWithMap-4 11.31 11.26 ~ 0.700
DiskTxMap_SetIfNotExists-4 3.783µ 3.616µ ~ 0.100
DiskTxMap_SetIfNotExists_Parallel-4 3.374µ 3.591µ ~ 0.400
DiskTxMap_ExistenceOnly-4 316.0n 316.8n ~ 1.000
Queue-4 195.4n 192.7n ~ 0.200
AtomicPointer-4 3.230n 3.270n ~ 0.100
TxMapSetIfNotExists-4 50.29n 50.98n ~ 0.200
TxMapSetIfNotExistsDuplicate-4 42.01n 42.38n ~ 0.100
ChannelSendReceive-4 555.8n 569.5n ~ 0.100
DirectSubtreeAdd/4_per_subtree-4 57.05n 57.24n ~ 0.700
DirectSubtreeAdd/64_per_subtree-4 35.68n 32.82n ~ 0.100
DirectSubtreeAdd/256_per_subtree-4 32.09n 31.97n ~ 0.400
DirectSubtreeAdd/1024_per_subtree-4 30.48n 30.49n ~ 1.000
DirectSubtreeAdd/2048_per_subtree-4 30.70n 30.06n ~ 0.700
SubtreeProcessorAdd/4_per_subtree-4 166.3n 166.5n ~ 0.700
SubtreeProcessorAdd/64_per_subtree-4 171.6n 175.6n ~ 0.400
SubtreeProcessorAdd/256_per_subtree-4 177.5n 175.7n ~ 0.700
SubtreeProcessorAdd/1024_per_subtree-4 192.0n 195.9n ~ 0.700
SubtreeProcessorAdd/2048_per_subtree-4 189.0n 190.5n ~ 0.400
SubtreeProcessorRotate/4_per_subtree-4 184.8n 187.6n ~ 0.700
SubtreeProcessorRotate/64_per_subtree-4 185.9n 190.6n ~ 0.500
SubtreeProcessorRotate/256_per_subtree-4 191.9n 182.9n ~ 0.100
SubtreeProcessorRotate/1024_per_subtree-4 185.9n 186.0n ~ 1.000
SubtreeNodeAddOnly/4_per_subtree-4 66.61n 66.61n ~ 1.000
SubtreeNodeAddOnly/64_per_subtree-4 49.09n 51.96n ~ 0.100
SubtreeNodeAddOnly/256_per_subtree-4 53.21n 48.65n ~ 0.400
SubtreeNodeAddOnly/1024_per_subtree-4 47.60n 47.62n ~ 1.000
SubtreeCreationOnly/4_per_subtree-4 122.1n 124.8n ~ 0.600
SubtreeCreationOnly/64_per_subtree-4 522.5n 488.8n ~ 0.700
SubtreeCreationOnly/256_per_subtree-4 1.786µ 1.568µ ~ 0.100
SubtreeCreationOnly/1024_per_subtree-4 5.320µ 5.045µ ~ 0.100
SubtreeCreationOnly/2048_per_subtree-4 9.398µ 9.313µ ~ 0.200
SubtreeProcessorOverheadBreakdown/64_per_subtree-4 183.0n 184.8n ~ 1.000
SubtreeProcessorOverheadBreakdown/1024_per_subtree-4 183.1n 182.3n ~ 1.000
ParallelGetAndSetIfNotExists/1k_nodes-4 13.64m 15.25m ~ 0.100
ParallelGetAndSetIfNotExists/10k_nodes-4 16.28m 17.02m ~ 0.200
ParallelGetAndSetIfNotExists/50k_nodes-4 17.09m 17.04m ~ 1.000
ParallelGetAndSetIfNotExists/100k_nodes-4 18.62m 18.85m ~ 0.700
SequentialGetAndSetIfNotExists/1k_nodes-4 15.40m 15.28m ~ 1.000
SequentialGetAndSetIfNotExists/10k_nodes-4 14.32m 15.37m ~ 0.700
SequentialGetAndSetIfNotExists/50k_nodes-4 19.67m 19.64m ~ 0.700
SequentialGetAndSetIfNotExists/100k_nodes-4 25.72m 25.92m ~ 0.400
ProcessOwnBlockSubtreeNodesParallel/1k_nodes-4 12.51m 12.38m ~ 0.700
ProcessOwnBlockSubtreeNodesParallel/10k_nodes-4 17.72m 18.15m ~ 0.400
ProcessOwnBlockSubtreeNodesParallel/100k_nodes-4 21.80m 20.94m ~ 0.400
ProcessOwnBlockSubtreeNodesSequential/1k_nodes-4 12.92m 12.41m ~ 1.000
ProcessOwnBlockSubtreeNodesSequential/10k_nodes-4 17.43m 18.49m ~ 0.700
ProcessOwnBlockSubtreeNodesSequential/100k_nodes-4 51.56m 52.23m ~ 0.400
CalcBlockWork-4 523.8n 511.2n ~ 1.000
CalculateWork-4 701.0n 706.4n ~ 0.300
CheckOldBlockIDs/on-chain-prefetch/1000-4 66.11µ 66.98µ ~ 0.400
CheckOldBlockIDs/in-memory-chain-check/1000-4 1.317m 1.311m ~ 1.000
CheckOldBlockIDs/on-chain-prefetch/10000-4 422.2µ 431.0µ ~ 0.100
CheckOldBlockIDs/in-memory-chain-check/10000-4 2.022m 2.003m ~ 0.100
BuildBlockLocatorString_Helpers/Size_10-4 1.395µ 1.391µ ~ 0.400
BuildBlockLocatorString_Helpers/Size_100-4 13.39µ 13.44µ ~ 0.200
BuildBlockLocatorString_Helpers/Size_1000-4 132.1µ 133.4µ ~ 0.100
CatchupWithHeaderCache-4 106.8m 106.8m ~ 1.000
_prepareTxsPerLevel-4 414.7m 407.5m ~ 1.000
_prepareTxsPerLevelOrdered-4 3.892m 4.070m ~ 0.700
_prepareTxsPerLevel_Comparison/Original-4 415.6m 418.7m ~ 0.700
_prepareTxsPerLevel_Comparison/Optimized-4 4.118m 3.815m ~ 0.100
SubtreeSizes/10k_tx_4_per_subtree-4 1.304m 1.301m ~ 1.000
SubtreeSizes/10k_tx_16_per_subtree-4 307.5µ 307.6µ ~ 0.700
SubtreeSizes/10k_tx_64_per_subtree-4 74.60µ 72.67µ ~ 0.100
SubtreeSizes/10k_tx_256_per_subtree-4 18.31µ 18.19µ ~ 1.000
SubtreeSizes/10k_tx_512_per_subtree-4 9.021µ 9.039µ ~ 1.000
SubtreeSizes/10k_tx_1024_per_subtree-4 4.470µ 4.456µ ~ 0.700
SubtreeSizes/10k_tx_2k_per_subtree-4 2.213µ 2.257µ ~ 0.100
BlockSizeScaling/10k_tx_64_per_subtree-4 71.32µ 71.80µ ~ 0.700
BlockSizeScaling/10k_tx_256_per_subtree-4 18.26µ 18.05µ ~ 0.100
BlockSizeScaling/10k_tx_1024_per_subtree-4 4.533µ 4.505µ ~ 1.000
BlockSizeScaling/50k_tx_64_per_subtree-4 383.3µ 384.7µ ~ 0.400
BlockSizeScaling/50k_tx_256_per_subtree-4 91.92µ 90.56µ ~ 0.200
BlockSizeScaling/50k_tx_1024_per_subtree-4 22.51µ 22.38µ ~ 0.700
SubtreeAllocations/small_subtrees_exists_check-4 156.2µ 154.0µ ~ 0.100
SubtreeAllocations/small_subtrees_data_fetch-4 162.9µ 162.6µ ~ 0.700
SubtreeAllocations/small_subtrees_full_validation-4 316.9µ 319.0µ ~ 0.400
SubtreeAllocations/medium_subtrees_exists_check-4 9.015µ 9.087µ ~ 0.200
SubtreeAllocations/medium_subtrees_data_fetch-4 9.589µ 9.420µ ~ 0.200
SubtreeAllocations/medium_subtrees_full_validation-4 18.32µ 18.36µ ~ 0.400
SubtreeAllocations/large_subtrees_exists_check-4 2.161µ 2.167µ ~ 0.300
SubtreeAllocations/large_subtrees_data_fetch-4 2.324µ 2.306µ ~ 1.000
SubtreeAllocations/large_subtrees_full_validation-4 4.701µ 4.607µ ~ 0.400
_BufferPoolAllocation/16KB-4 2.877µ 2.956µ ~ 0.200
_BufferPoolAllocation/32KB-4 6.397µ 6.092µ ~ 1.000
_BufferPoolAllocation/64KB-4 12.74µ 12.93µ ~ 0.700
_BufferPoolAllocation/128KB-4 24.57µ 25.17µ ~ 0.100
_BufferPoolAllocation/512KB-4 93.45µ 106.62µ ~ 0.100
_BufferPoolConcurrent/32KB-4 16.42µ 15.20µ ~ 0.400
_BufferPoolConcurrent/64KB-4 23.21µ 23.43µ ~ 0.400
_BufferPoolConcurrent/512KB-4 119.2µ 122.5µ ~ 0.200
_SubtreeDeserializationWithBufferSizes/16KB-4 527.4µ 542.3µ ~ 0.400
_SubtreeDeserializationWithBufferSizes/32KB-4 465.2µ 483.6µ ~ 0.100
_SubtreeDeserializationWithBufferSizes/64KB-4 464.3µ 484.8µ ~ 0.100
_SubtreeDeserializationWithBufferSizes/128KB-4 459.5µ 480.2µ ~ 0.100
_SubtreeDeserializationWithBufferSizes/512KB-4 465.9µ 475.8µ ~ 0.700
_SubtreeDataDeserializationWithBufferSizes/16KB-4 28.11m 28.05m ~ 0.700
_SubtreeDataDeserializationWithBufferSizes/32KB-4 27.99m 28.26m ~ 0.400
_SubtreeDataDeserializationWithBufferSizes/64KB-4 28.44m 27.99m ~ 0.400
_SubtreeDataDeserializationWithBufferSizes/128KB-4 28.24m 27.77m ~ 0.100
_SubtreeDataDeserializationWithBufferSizes/512KB-4 28.17m 27.93m ~ 0.400
_PooledVsNonPooled/Pooled-4 648.4n 644.0n ~ 0.100
_PooledVsNonPooled/NonPooled-4 5.745µ 5.566µ ~ 1.000
_MemoryFootprint/Current_512KB_32concurrent-4 6.046µ 6.441µ ~ 0.100
_MemoryFootprint/Proposed_32KB_32concurrent-4 7.626µ 7.996µ ~ 0.100
_MemoryFootprint/Alternative_64KB_32concurrent-4 7.860µ 7.884µ ~ 0.700
StoreBlock_Sequential/BelowCSVHeight-4 295.0µ 295.6µ ~ 1.000
StoreBlock_Sequential/AboveCSVHeight-4 345.3µ 345.5µ ~ 0.700
GetUtxoHashes-4 168.7n 169.1n ~ 1.000
GetUtxoHashes_ManyOutputs-4 26.85µ 29.02µ ~ 0.400
MetaBytes-4 44.95n 46.09n ~ 0.100
_NewMetaDataFromBytes-4 141.3n 137.8n ~ 0.100
_Bytes-4 208.2n 212.5n ~ 0.100
_MetaBytes-4 70.65n 71.15n ~ 0.400

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

@ctnguyen ctnguyen left a comment

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.

Review at commit ed85310

This is a tidy fix for a genuinely dead code path. I confirmed the premise in the tree: ReportValidBlock really does write the counter (services/p2p/handle_catchup_metrics.go:399 calls RecordBlockReceived), peerTierCache.refresh really does gate tierMiner on BlocksReceived > 0 (services/asset/httpimpl/peer_auth_middleware.go:116-127), and before this PR the value had no wire field to travel on — so the exemption was unreachable over gRPC. Adding the field rather than weakening the predicate is the right call. I also checked the regenerated p2p_api.pb.go byte-for-byte rather than taking it on trust: the rawDesc message length goes \xe7\b (1127) to \xf2\t (1266), a delta of 139, which is exactly the 41 + 45 + 53 bytes the three new FieldDescriptorProto entries occupy; the tag bytes \x1e/\x1f/\x20, the \x03 int64 type and the R-prefixed json names all line up. The descriptor is therefore byte-consistent with what protoc-gen-go emits for these three fields, so a reader can take the generated file at face value instead of re-deriving it. The Interface.go doc correction is a nice touch — replacing a "complete information" promise that was never true with an explicit list of what does not cross the wire is more useful than the fix itself in the long run. Two non-blocking notes below, both about durability rather than correctness of this change.

Non-blocking issues

ChiR1 — The new reflection guard checks that a wire field exists, not that it is mapped

Problem: TestServer_PeerRegistryInfo_CarriesEveryPeerInfoField (services/p2p/server_handler_test.go:117-158) only asserts that a p2p_api.PeerRegistryInfo struct field named like each exported p2p.PeerInfo field exists. It never exercises peerInfoToP2PProto or convertFromAPIPeerInfo. The bug this PR fixes had two halves — no proto field, and no converter mapping — and the guard closes only the first. Add Foo int64 to p2p.PeerInfo, add foo = 33 to the proto, forget both converters, and the guard still passes while every gRPC consumer reads zero: the identical failure mode. The converter half is currently covered only by the hand-maintained assertion list in TestServer_PeerInfoToP2PProto_RoundTripFields, which asserts 18 of the 30 fields peerInfoToP2PProto sets, omitting twelve (BytesReceived, LastBlockTime, LastMessageTime, the three Interaction* counters, the three LastInteraction* timestamps, ReputationScore, MaliciousCount, LastCatchupErrorTime) and, despite its name, never runs the client-side converter at all. Separately, wireFields is built from all struct fields including the unexported state, unknownFields and sizeCache, so a future p2p.PeerInfo.State would satisfy the guard against the protobuf bookkeeping field rather than against a real wire field.

Why it matters: The PR body presents this test as turning "the class of bug behind this issue into a test failure". As written it catches the schema half only, so the regression it is meant to prevent can still land silently. I verified both converters do map all 30 wire fields today (services/p2p/Server.go:3115-3146, services/p2p/Client.go:851-882), so this is about the guard's future value, not a live defect.

Fix: Skip unexported wire fields, and make the guard value-based so it covers the converters end to end:

for i := 0; i < wt.NumField(); i++ {
    if !wt.Field(i).IsExported() {
        continue
    }
    wireFields[strings.ToLower(wt.Field(i).Name)] = true
}

// ... after the existing name check, assert the value actually survives both hops.
src := &blockchain.PeerInfo{ /* every non-allowlisted field set to a distinct non-zero value */ }
got, err := convertFromAPIPeerInfo(peerInfoToP2PProto(src))
require.NoError(t, err)

gv := reflect.ValueOf(*got)
for i := 0; i < dt.NumField(); i++ {
    f := dt.Field(i)
    if !f.IsExported() {
        continue
    }
    if _, skip := notOnWire[f.Name]; skip {
        continue
    }
    require.False(t, gv.Field(i).IsZero(),
        "p2p.PeerInfo.%s is zero after peerInfoToP2PProto/convertFromAPIPeerInfo; add the mapping or list it in notOnWire", f.Name)
}

That also retires the hand-maintained assertion list, since the new check subsumes it.

ChiR2 — Retired field numbers 12 and 13 are still not reserved

Problem: PeerRegistryInfo jumps from last_message_time = 11 to interaction_attempts = 14 (services/p2p/p2p_api/p2p_api.proto:262-265) with no reserved declaration. git log -L on the message shows the gap is not decorative: bool url_responsive = 12 and int64 last_url_check = 13 existed and were deleted in d07f154 ("Remove URLResponsive checks, all handled with reputation score"). Nothing marks them as spent, so a later change can reuse 12 or 13 for an unrelated type and a mixed-version reader will misparse the varint.

Why it matters: The repo already treats this as a convention — services/validator/validator_api/validator_api.proto:72-73, services/blockassembly/blockassembly_api/blockassembly_api.proto:158-159 and services/blockchain/blockchain_api/blockchain_api.proto:660-661 all carry reserved plus the retired name, and this very file spells out the reasoning in prose at line 336 for an RPC name that protobuf cannot reserve. This PR is the natural place to close it: it is already doing field-number housekeeping in exactly this message, and appending at 30 is otherwise correct.

Fix: Two lines in the message body:

  // Removed in "Remove URLResponsive checks": url_responsive (bool) and
  // last_url_check (int64). Do not reuse - a mixed-version reader would
  // misinterpret a new field at these numbers.
  reserved 12, 13;
  reserved "url_responsive", "last_url_check";

Then make gen to refresh p2p_api.pb.go.

Recap

ID Description Required Criticality
ChiR1 Guard checks names not mappings 35%
ChiR2 Field numbers 12 and 13 unreserved 25%

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.

2 participants