Skip to main content

urma_node/
peer_protocol.rs

1//! Untrusted selective exchange, separate from the authenticated local control
2//! API. Transport identity proves which peer answered, not chain membership,
3//! record validity or completeness. URMA V0 records and proofs are unchanged.
4use crate::model::RecordPackage;
5use bitcoin::{BlockHash, Txid, Wtxid};
6use libp2p::{Multiaddr, PeerId};
7use serde::{Deserialize, Serialize};
8
9pub const PROTOCOL: &str = "/urma/node/record/5";
10pub const VERSION: u32 = 5;
11pub const REQUEST_LIMIT: usize = 1024;
12pub const RESPONSE_LIMIT: usize = 32 * 1024 * 1024;
13/// Raw parent bytes, matching the local RPC 16M-hex admission cap.
14pub const PARENT_LIMIT: usize = 8 * 1024 * 1024;
15/// Widest inventory request, in heights (to_height - from_height).
16pub const INVENTORY_SPAN: u64 = 4096;
17/// Most placements in one inventory page.
18pub const INVENTORY_ENTRIES: u32 = 512;
19/// Encoded inventory response frame, enforced by both writer and reader.
20pub const INVENTORY_LIMIT: usize = 256 * 1024;
21/// Most peers in one gossip reply; `limit: 0` is a bare chain probe.
22pub const PEERS_MAX: u32 = 32;
23/// Most addresses gossiped per peer.
24pub const PEER_ADDRESSES_MAX: usize = 4;
25/// Encoded peers response frame, enforced by both writer and reader.
26pub const PEERS_LIMIT: usize = 32 * 1024;
27
28#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
29#[serde(tag = "kind", rename_all = "kebab-case", deny_unknown_fields)]
30pub enum Kind {
31    Record {
32        txid: Txid,
33    },
34    /// Retained raw transaction by TXID; untrusted, never a placement claim.
35    Parent {
36        txid: Txid,
37    },
38    /// Placements in fully scanned selected blocks, (height, position) order.
39    Inventory {
40        from_height: u64,
41        to_height: u64,
42        after: Option<Placement>,
43        limit: u32,
44    },
45    /// Routing hints the answering node has recently reached; untrusted.
46    Peers {
47        limit: u32,
48    },
49}
50
51/// A gossiped routing hint: base58 peer identity, its addresses and the Unix
52/// second the answering node last reached it. Never chain or record evidence.
53#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
54#[serde(deny_unknown_fields)]
55pub struct GossipPeer {
56    #[serde(with = "base58_peer")]
57    pub peer_id: PeerId,
58    pub addrs: Vec<Multiaddr>,
59    pub last_seen: u64,
60}
61
62/// A PeerId on the wire is its base58 string, parsed strictly on receipt.
63mod base58_peer {
64    use libp2p::PeerId;
65    use serde::{Deserialize, Deserializer, Serializer, de::Error};
66
67    pub fn serialize<S: Serializer>(peer: &PeerId, serializer: S) -> Result<S::Ok, S::Error> {
68        serializer.collect_str(peer)
69    }
70
71    pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<PeerId, D::Error> {
72        String::deserialize(deserializer)?
73            .parse()
74            .map_err(D::Error::custom)
75    }
76}
77
78/// One transaction's position in a block the answering node fully scanned.
79#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
80#[serde(deny_unknown_fields)]
81pub struct Placement {
82    pub height: u64,
83    pub block_hash: BlockHash,
84    pub position: u32,
85    pub txid: Txid,
86    pub wtxid: Wtxid,
87}
88
89/// Complete only for scanned blocks in [`scanned_from`, `scanned_through`]
90/// within the requested range. `scanned_from` is the answering node's
91/// application start: below it the node never scanned, so an empty page there
92/// is no claim at all.
93#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
94#[serde(deny_unknown_fields)]
95pub struct InventoryPage {
96    pub scanned_from: u64,
97    pub scanned_through: Option<u64>,
98    pub entries: Vec<Placement>,
99    pub more: bool,
100}
101
102#[derive(Clone, Debug, Serialize, Deserialize)]
103#[serde(deny_unknown_fields)]
104pub struct Request {
105    pub version: u32,
106    pub chain_genesis: BlockHash,
107    pub kind: Kind,
108}
109
110#[derive(Clone, Debug, Serialize, Deserialize)]
111#[serde(deny_unknown_fields)]
112pub struct Response {
113    pub version: u32,
114    pub chain_genesis: BlockHash,
115    pub kind: Kind,
116    pub result: Reply,
117}
118
119#[derive(Clone, Debug, Serialize, Deserialize)]
120#[serde(
121    tag = "status",
122    content = "package",
123    rename_all = "kebab-case",
124    deny_unknown_fields
125)]
126pub enum Reply {
127    Record(Box<RecordPackage>),
128    /// Lowercase hex of at most PARENT_LIMIT raw bytes.
129    Parent(String),
130    Inventory(InventoryPage),
131    Peers(Vec<GossipPeer>),
132    /// Unknown in this peer's selected retained snapshot, never global absence.
133    Missing,
134    WrongNetwork,
135    UnsupportedVersion,
136    Busy,
137    TooLarge,
138    /// Malformed request parameters, such as an inventory range or limit.
139    Refused,
140}