Summary
Two GRPC response fields are populated from incorrect source fields due to copy-and-paste mistakes in the POJO → protobuf conversion layer:
-
NetMetricManager.getNetProtoInfo() assigns UDP inbound traffic to tcpOutTraffic instead of udpInTraffic. As a result, tcpOutTraffic is overwritten with the UDP inbound value, while udpInTraffic is never populated.
-
NodeInfo.transferToProtoEntity() populates needSyncFromPeer from peerInfo.isSyncFlag() instead of peerInfo.isNeedSyncFromPeer().
Both fields are read-only status/statistics data. Neither defect affects node networking, synchronization, or consensus behavior.
Affected API Surface
Both defects occur in the protobuf conversion path and are exposed only through the affected GRPC APIs.
Defect 1 — tcpOutTraffic / udpInTraffic
The defect is in NetMetricManager.getNetProtoInfo() in the framework module and is exposed through GRPC Monitor.GetStatsInfo (Protocol.MetricsInfo).
The HTTP /monitor/getstatsinfo endpoint is not affected. MetricsServlet serializes the POJO populated by NetMetricManager.setNetInfo(), which maps all four traffic directions correctly.
Defect 2 — needSyncFromPeer
The defect is in NodeInfo.transferToProtoEntity() in the common module and is exposed through GRPC Wallet.GetNodeInfo.
The HTTP /wallet/getnodeinfo and /monitor/getnodeinfo endpoints are not affected. GetNodeInfoServlet serializes the POJO directly, and NodeInfoService assigns needSyncFromPeer correctly.
Root Cause
1. NetMetricManager.getNetProtoInfo()
In the UDP block, the target field was not updated after copying the TCP block:
// tcp
RateInfo tcpOutTraffic = net.getTcpOutTraffic();
Protocol.MetricsInfo.RateInfo tcpOUTrafficInfo = tcpOutTraffic.toProtoEntity();
netInfo.setTcpOutTraffic(tcpOUTrafficInfo);
// udp
RateInfo udpInTraffic = net.getUdpInTraffic();
Protocol.MetricsInfo.RateInfo udpInTrafficInfo = udpInTraffic.toProtoEntity();
netInfo.setTcpOutTraffic(udpInTrafficInfo); // <-- should be setUdpInTraffic
The second assignment overwrites the first one. Consequently, tcpOutTraffic reports the UDP inbound traffic value, while udpInTraffic remains unset and is therefore absent from the response or represented by its default value, depending on the client/output format.
2. NodeInfo.transferToProtoEntity()
isSyncFlag() was copied into the assignment for needSyncFromPeer:
peerInfoBuilder.setSyncFlag(peerInfo.isSyncFlag());
peerInfoBuilder.setHeadBlockTimeWeBothHave(peerInfo.getHeadBlockTimeWeBothHave());
peerInfoBuilder.setNeedSyncFromPeer(peerInfo.isSyncFlag()); // <-- should be isNeedSyncFromPeer()
peerInfoBuilder.setNeedSyncFromUs(peerInfo.isNeedSyncFromUs());
The POJO already carries the correct value: NodeInfoService assigns needSyncFromPeer from PeerConnection.isNeedSyncFromPeer(). The value is therefore lost only during the POJO → protobuf conversion.
For an active peer, syncFlag is normally false, while needSyncFromPeer can independently be true when the local node needs to synchronize from that peer. Because the protobuf conversion uses isSyncFlag() instead of isNeedSyncFromPeer(), the GRPC response fails to expose this state.
Reproduction
Defect 1 — GRPC Monitor.GetStatsInfo
grpcurl -plaintext 127.0.0.1:50051 protocol.Monitor/GetStatsInfo
Compare the result with the HTTP endpoint backed by the same underlying counters:
curl -s http://127.0.0.1:8090/monitor/getstatsinfo
The HTTP endpoint already reports the correct field mapping, making the discrepancy directly observable on the same node.
Illustrative response fragment (values are indicative only):
GRPC GetStatsInfo — before fix
{
"net": {
"tcpInTraffic": { "count": 120000, "meanRate": 1000.0 },
"tcpOutTraffic": { "count": 3000, "meanRate": 25.0 },
"udpOutTraffic": { "count": 2800, "meanRate": 23.0 }
}
}
Here, tcpOutTraffic actually contains the UDP inbound traffic value, while udpInTraffic is not populated.
GRPC GetStatsInfo — after fix
{
"net": {
"tcpInTraffic": { "count": 120000, "meanRate": 1000.0 },
"tcpOutTraffic": { "count": 98000, "meanRate": 820.0 },
"udpInTraffic": { "count": 3000, "meanRate": 25.0 },
"udpOutTraffic": { "count": 2800, "meanRate": 23.0 }
}
}
Defect 2 — GRPC Wallet.GetNodeInfo
grpcurl -plaintext -emit-defaults 127.0.0.1:50051 protocol.Wallet/GetNodeInfo
Compare with:
curl -s http://127.0.0.1:8090/wallet/getnodeinfo
For each entry in peerInfoList, the GRPC response currently sets needSyncFromPeer to the same value as syncFlag, while the HTTP response reports the two fields independently.
For example, consider an active peer that the local node currently needs to synchronize from:
GRPC GetNodeInfo — before fix
{
"syncFlag": false,
"needSyncFromPeer": false,
"needSyncFromUs": false
}
GRPC GetNodeInfo — after fix
{
"syncFlag": false,
"needSyncFromPeer": true,
"needSyncFromUs": false
}
In this case, the peer is actively connected and needSyncFromPeer is true. The current GRPC conversion incorrectly reports false because it uses syncFlag as the source field.
Impact
- Statistics returned by GRPC
Monitor.GetStatsInfo and Wallet.GetNodeInfo are inaccurate and may mislead monitoring, capacity planning, and troubleshooting.
- UDP inbound traffic is not reported through the affected GRPC field, while
tcpOutTraffic reports the UDP inbound value instead of the actual TCP outbound traffic.
- For connected peers, the GRPC
needSyncFromPeer field can incorrectly remain false, making the actual per-peer synchronization state unobservable through this API.
- The same node can report different values over HTTP and GRPC for the same underlying counters/state, which can be confusing for consumers using both APIs.
- These are read-only reporting defects. Traffic accounting, peer synchronization, and consensus behavior are unaffected because the underlying counters and
PeerConnection state are correct.
Backwards Compatibility
The fix changes values returned by two existing GRPC responses. Operators consuming these fields may observe a step change after upgrading:
tcpOutTraffic will change from the previously reported UDP inbound value to the actual TCP outbound traffic.
udpInTraffic will change from being unset/default to reporting the actual UDP inbound traffic.
needSyncFromPeer will stop mirroring syncFlag and will report the actual per-peer synchronization state.
Dashboards or alert rules that rely on historical values of tcpOutTraffic, udpInTraffic, or needSyncFromPeer should be reviewed before upgrading. In particular, consumers that implicitly relied on udpInTraffic being unset or zero may need to update their assumptions.
Consumers using the HTTP endpoints require no changes because those values are already correct.
No protobuf schema change is involved. Protocol.MetricsInfo.NetInfo and Protocol.NodeInfo.PeerInfo already declare all affected fields, so the generated client interfaces remain unchanged and no client recompilation is required.
Suggested Fix
1. Fix the UDP inbound traffic mapping in NetMetricManager.getNetProtoInfo()
- netInfo.setTcpOutTraffic(udpInTrafficInfo);
+ netInfo.setUdpInTraffic(udpInTrafficInfo);
2. Fix the needSyncFromPeer mapping in NodeInfo.transferToProtoEntity()
- peerInfoBuilder.setNeedSyncFromPeer(peerInfo.isSyncFlag());
+ peerInfoBuilder.setNeedSyncFromPeer(peerInfo.isNeedSyncFromPeer());
3. Add unit tests for the POJO → protobuf conversion
Add unit tests that verify each source field is mapped to the corresponding protobuf field.
For the traffic conversion, build a NetInfo object with distinct values for all four traffic directions and assert each target field independently.
For the peer information conversion, use different values for syncFlag and needSyncFromPeer and verify that both values are preserved independently.
Distinct test values are important: tests that use the same value for multiple source fields would not have caught either of these mapping defects.
Summary
Two GRPC response fields are populated from incorrect source fields due to copy-and-paste mistakes in the POJO → protobuf conversion layer:
NetMetricManager.getNetProtoInfo()assigns UDP inbound traffic totcpOutTrafficinstead ofudpInTraffic. As a result,tcpOutTrafficis overwritten with the UDP inbound value, while udpInTraffic is never populated.NodeInfo.transferToProtoEntity()populates needSyncFromPeer from peerInfo.isSyncFlag() instead ofpeerInfo.isNeedSyncFromPeer().Both fields are read-only status/statistics data. Neither defect affects node networking, synchronization, or consensus behavior.
Affected API Surface
Both defects occur in the protobuf conversion path and are exposed only through the affected GRPC APIs.
Defect 1 — tcpOutTraffic / udpInTraffic
The defect is in
NetMetricManager.getNetProtoInfo()in the framework module and is exposed through GRPCMonitor.GetStatsInfo (Protocol.MetricsInfo).The HTTP
/monitor/getstatsinfoendpoint is not affected. MetricsServlet serializes the POJO populated byNetMetricManager.setNetInfo(), which maps all four traffic directions correctly.Defect 2 — needSyncFromPeer
The defect is in
NodeInfo.transferToProtoEntity()in the common module and is exposed through GRPCWallet.GetNodeInfo.The HTTP
/wallet/getnodeinfoand/monitor/getnodeinfoendpoints are not affected.GetNodeInfoServletserializes the POJO directly, andNodeInfoServiceassignsneedSyncFromPeercorrectly.Root Cause
1. NetMetricManager.getNetProtoInfo()
In the UDP block, the target field was not updated after copying the TCP block:
The second assignment overwrites the first one. Consequently,
tcpOutTrafficreports the UDP inbound traffic value, whileudpInTrafficremains unset and is therefore absent from the response or represented by its default value, depending on the client/output format.2. NodeInfo.transferToProtoEntity()
isSyncFlag()was copied into the assignment forneedSyncFromPeer:The POJO already carries the correct value:
NodeInfoServiceassignsneedSyncFromPeerfromPeerConnection.isNeedSyncFromPeer(). The value is therefore lost only during the POJO → protobuf conversion.For an active peer,
syncFlagis normally false, whileneedSyncFromPeercan independently be true when the local node needs to synchronize from that peer. Because the protobuf conversion usesisSyncFlag()instead ofisNeedSyncFromPeer(), the GRPC response fails to expose this state.Reproduction
Defect 1 — GRPC Monitor.GetStatsInfo
grpcurl -plaintext 127.0.0.1:50051 protocol.Monitor/GetStatsInfoCompare the result with the HTTP endpoint backed by the same underlying counters:
curl -s http://127.0.0.1:8090/monitor/getstatsinfoThe HTTP endpoint already reports the correct field mapping, making the discrepancy directly observable on the same node.
Illustrative response fragment (values are indicative only):
GRPC GetStatsInfo — before fix
Here,
tcpOutTrafficactually contains the UDP inbound traffic value, whileudpInTrafficis not populated.GRPC GetStatsInfo — after fix
Defect 2 — GRPC Wallet.GetNodeInfo
grpcurl -plaintext -emit-defaults 127.0.0.1:50051 protocol.Wallet/GetNodeInfoCompare with:
curl -s http://127.0.0.1:8090/wallet/getnodeinfoFor each entry in
peerInfoList, the GRPC response currently setsneedSyncFromPeerto the same value assyncFlag, while the HTTP response reports the two fields independently.For example, consider an active peer that the local node currently needs to synchronize from:
GRPC
GetNodeInfo— before fixGRPC
GetNodeInfo— after fixIn this case, the peer is actively connected and
needSyncFromPeeris true. The current GRPC conversion incorrectly reports false because it usessyncFlagas the source field.Impact
Monitor.GetStatsInfoandWallet.GetNodeInfoare inaccurate and may mislead monitoring, capacity planning, and troubleshooting.tcpOutTrafficreports the UDP inbound value instead of the actual TCP outbound traffic.needSyncFromPeerfield can incorrectly remain false, making the actual per-peer synchronization state unobservable through this API.PeerConnectionstate are correct.Backwards Compatibility
The fix changes values returned by two existing GRPC responses. Operators consuming these fields may observe a step change after upgrading:
tcpOutTrafficwill change from the previously reported UDP inbound value to the actual TCP outbound traffic.udpInTrafficwill change from being unset/default to reporting the actual UDP inbound traffic.needSyncFromPeerwill stop mirroring syncFlag and will report the actual per-peer synchronization state.Dashboards or alert rules that rely on historical values of
tcpOutTraffic,udpInTraffic, orneedSyncFromPeershould be reviewed before upgrading. In particular, consumers that implicitly relied onudpInTrafficbeing unset or zero may need to update their assumptions.Consumers using the HTTP endpoints require no changes because those values are already correct.
No protobuf schema change is involved.
Protocol.MetricsInfo.NetInfoandProtocol.NodeInfo.PeerInfoalready declare all affected fields, so the generated client interfaces remain unchanged and no client recompilation is required.Suggested Fix
1. Fix the UDP inbound traffic mapping in NetMetricManager.getNetProtoInfo()
2. Fix the needSyncFromPeer mapping in NodeInfo.transferToProtoEntity()
3. Add unit tests for the POJO → protobuf conversion
Add unit tests that verify each source field is mapped to the corresponding protobuf field.
For the traffic conversion, build a NetInfo object with distinct values for all four traffic directions and assert each target field independently.
For the peer information conversion, use different values for
syncFlagandneedSyncFromPeerand verify that both values are preserved independently.Distinct test values are important: tests that use the same value for multiple source fields would not have caught either of these mapping defects.