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/4";
10pub const VERSION: u32 = 4;
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 at or below `scanned_through` in range.
90#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
91#[serde(deny_unknown_fields)]
92pub struct InventoryPage {
93    pub scanned_through: Option<u64>,
94    pub entries: Vec<Placement>,
95    pub more: bool,
96}
97
98#[derive(Clone, Debug, Serialize, Deserialize)]
99#[serde(deny_unknown_fields)]
100pub struct Request {
101    pub version: u32,
102    pub chain_genesis: BlockHash,
103    pub kind: Kind,
104}
105
106#[derive(Clone, Debug, Serialize, Deserialize)]
107#[serde(deny_unknown_fields)]
108pub struct Response {
109    pub version: u32,
110    pub chain_genesis: BlockHash,
111    pub kind: Kind,
112    pub result: Reply,
113}
114
115#[derive(Clone, Debug, Serialize, Deserialize)]
116#[serde(
117    tag = "status",
118    content = "package",
119    rename_all = "kebab-case",
120    deny_unknown_fields
121)]
122pub enum Reply {
123    Record(Box<RecordPackage>),
124    /// Lowercase hex of at most PARENT_LIMIT raw bytes.
125    Parent(String),
126    Inventory(InventoryPage),
127    Peers(Vec<GossipPeer>),
128    /// Unknown in this peer's selected retained snapshot, never global absence.
129    Missing,
130    WrongNetwork,
131    UnsupportedVersion,
132    Busy,
133    TooLarge,
134    /// Malformed request parameters, such as an inventory range or limit.
135    Refused,
136}