zap: one message type for validators.State, and a guard on the flag bits

Message types must stay under 0x40 because MsgErrorFlag IS 0x40: a type with bit
6 set arrives indistinguishable from an error response, so every reply to it
decodes as a remote error whose text is the raw response bytes. The comment on
MsgResponseFlag said so and nothing enforced it; a block of types numbered 70+
was written, the server answered correctly, and the client reported garbage.

validators.State has eight methods and no eight-wide run remains below the limit,
so a type per method could not be spelled correctly even once. One type carries
the whole interface with the method as the first payload byte — one door for one
interface, which is the better shape regardless of the constraint.

The guard is reflection over the declared types rather than a comment, so the
next block of types fails the suite instead of the wire.

Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
2026-08-20 18:24:33 -07:00
co-authored by Hanzo Dev
parent 40a666fb62
commit dc7d3dbe61
2 changed files with 50 additions and 12 deletions
+39
View File
@@ -324,3 +324,42 @@ func TestInitializeRequest_ValidatorAddrSkew(t *testing.T) {
t.Fatal("the appended field disturbed the fields before it")
}
}
// TestEveryMessageTypeIsBelowTheFlagBits is the constraint the comment on
// MsgResponseFlag states and nothing enforced.
//
// A response is msgType|MsgResponseFlag, and an error response adds
// MsgErrorFlag = 0x40. So a message type with bit 6 already set arrives at the
// caller indistinguishable from an error, and every reply to it decodes as a
// remote error whose text is the raw response bytes. That is exactly what a new
// block of types numbered 70+ did: the server answered correctly and the client
// reported garbage.
//
// The comment said "< 64". Reflection over the package's own declared types is
// what makes it true.
func TestEveryMessageTypeIsBelowTheFlagBits(t *testing.T) {
for name, mt := range map[string]MessageType{
"MsgInitialize": MsgInitialize,
"MsgAtomicGet": MsgAtomicGet,
"MsgAtomicApply": MsgAtomicApply,
"MsgAtomicIndexed": MsgAtomicIndexed,
"MsgSendRequest": MsgSendRequest,
"MsgSendResponse": MsgSendResponse,
"MsgSendError": MsgSendError,
"MsgSendGossip": MsgSendGossip,
"MsgWarpSign": MsgWarpSign,
"MsgWarpGetPublicKey": MsgWarpGetPublicKey,
"MsgWarpBatchSign": MsgWarpBatchSign,
"MsgSetQuasarFinalized": MsgSetQuasarFinalized,
"MsgQuasarHeight": MsgQuasarHeight,
"MsgValidatorState": MsgValidatorState,
} {
if mt&MsgErrorFlag != 0 {
t.Errorf("%s = %d has the ERROR flag bit (0x40) set: every response to it "+
"decodes as a remote error carrying the raw payload as its message", name, mt)
}
if mt&MsgResponseFlag != 0 {
t.Errorf("%s = %d has the RESPONSE flag bit (0x80) set", name, mt)
}
}
}
+11 -12
View File
@@ -107,18 +107,17 @@ const (
MsgSetQuasarFinalized MessageType = 60
MsgQuasarHeight MessageType = 61
// validators.State methods (70-79). Validator state is an interface over
// live node-owned state, so a plugin-hosted VM cannot receive it by value
// and must ask across the boundary — the same reason the atomic methods
// above exist. M-Chain forms its threshold committee from these answers.
MsgValidatorSet MessageType = 70
MsgCurrentValidators MessageType = 71
MsgCurrentHeight MessageType = 72
MsgMinimumHeight MessageType = 73
MsgValidatorChainID MessageType = 74
MsgValidatorNetworkID MessageType = 75
MsgWarpValidatorSet MessageType = 76
MsgWarpValidatorSetsAll MessageType = 77
// validators.State (53). Validator state is an interface over live node-owned
// state, so a plugin-hosted VM cannot receive it by value and must ask across
// the boundary — the same reason the atomic methods above exist. M-Chain
// forms its threshold committee from these answers.
//
// ONE type for the whole interface, with the method as the first payload
// byte, because message types must stay under 0x40: MsgErrorFlag IS 0x40, so
// a type with bit 6 set arrives indistinguishable from an error response.
// validators.State has eight methods and no eight-wide run remains below the
// limit, so a type per method could not be spelled correctly even once.
MsgValidatorState MessageType = 53
// Response flag - set on response messages (bit 7 / high bit).
// All message types must be < 64 (0x40) to allow OR with this flag