Skip to content

[BOUNTY: 25 RTC] _handle_get_state calls _signed_content with wrong arity (TypeError when STATE requested) #2288

@Scottcjn

Description

@Scottcjn

Discovered during the Phase F #2260 rebase (not introduced by it — this is a pre-existing latent bug).

Problem

node/rustchain_p2p_gossip.py::_handle_get_state calls self._signed_content() with 3 positional args (msg_type, node_id, payload), but the function signature has been 5 positional args (msg_type, sender_id, msg_id, ttl, payload) since PR #2274 (2026-04-17) extended signature coverage to msg_id and ttl for the #2272 hardening.

# node/rustchain_p2p_gossip.py (current main, ~line 893):
def _handle_get_state(self, msg: GossipMessage) -> Dict:
    ...
    payload = {"state": state_data}
    content = self._signed_content(MessageType.STATE.value, self.node_id, payload)  # 3-arg, bug
    signature, timestamp = self._sign_message(content)
    ...

vs

# node/rustchain_p2p_gossip.py (~line 441):
@staticmethod
def _signed_content(msg_type: str, sender_id: str, msg_id: str, ttl: int, payload: Dict) -> str:
    return f"{msg_type}:{sender_id}:{msg_id}:{ttl}:{json.dumps(payload, sort_keys=True)}"

Calling the 5-arg function with 3 args raises TypeError: _signed_content() missing 2 required positional arguments: 'msg_id' and 'ttl' the moment any peer sends a GET_STATE message.

Why this hasn't been caught yet

  • No unit test exercises the full _handle_get_state path end-to-end.
  • The STATE-sync path is covered by the request_full_sync hotfix from PR fix: bind STATE sync sender_id to responder's canonical node_id (Phase 2 regression) #2259, but that code handles the response side on the requester; the responder-side _handle_get_state is the one with the buggy arity.
  • Production nodes may be silently raising and swallowing this exception inside the message-handler dispatcher, with the state request appearing to time out on the requester.

Proposed fix (small)

Generate a synthetic msg_id and use a static ttl (e.g. 0 since STATE responses aren't re-forwarded) for the signature:

state_msg_id = hashlib.sha256(
    f"STATE:{self.node_id}:{json.dumps(payload, sort_keys=True)}:{time.time()}".encode()
).hexdigest()[:24]
content = self._signed_content(MessageType.STATE.value, self.node_id, state_msg_id, 0, payload)
signature, timestamp = self._sign_message(content)

Then the response dict should also include msg_id and ttl so the requester's verify_message can reconstruct the signed content.

Bounty tier

Small (10-25 RTC) — straightforward 1-method fix + a regression test asserting _handle_get_state runs without raising and the response verifies under verify_message.

Labels

  • bug
  • p2p
  • security-adjacent (affects attestation sync integrity)

Metadata

Metadata

Assignees

No one assigned

    Labels

    bountyRTC bounty rewardbugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions