Skip to main content

host_api/
chain.rs

1//! Remote chain protocol methods (tags 76-103).
2//!
3//! Translates between SCALE-encoded binary messages from the product-SDK
4//! and JSON-RPC for smoldot.  Each function either:
5//! - Parses a smoldot JSON-RPC notification and encodes the result as SCALE, or
6//! - Encodes a SCALE response for a given chain request tag.
7//!
8//! All response messages follow the envelope:
9//!   `request_id (SCALE str) + response_tag (u8) + v1 (0x00) + payload`
10
11use crate::codec::*;
12
13// ---------------------------------------------------------------------------
14// Protocol tag constants (76-103)
15// ---------------------------------------------------------------------------
16
17pub const TAG_CHAIN_HEAD_FOLLOW_START: u8 = 76;
18pub const TAG_CHAIN_HEAD_FOLLOW_STOP: u8 = 77;
19pub const TAG_CHAIN_HEAD_FOLLOW_INTERRUPT: u8 = 78;
20pub const TAG_CHAIN_HEAD_FOLLOW_RECEIVE: u8 = 79;
21
22pub const TAG_CHAIN_HEAD_HEADER_REQ: u8 = 80;
23pub const TAG_CHAIN_HEAD_HEADER_RESP: u8 = 81;
24
25pub const TAG_CHAIN_HEAD_BODY_REQ: u8 = 82;
26pub const TAG_CHAIN_HEAD_BODY_RESP: u8 = 83;
27
28pub const TAG_CHAIN_HEAD_STORAGE_REQ: u8 = 84;
29pub const TAG_CHAIN_HEAD_STORAGE_RESP: u8 = 85;
30
31pub const TAG_CHAIN_HEAD_CALL_REQ: u8 = 86;
32pub const TAG_CHAIN_HEAD_CALL_RESP: u8 = 87;
33
34pub const TAG_CHAIN_HEAD_UNPIN_REQ: u8 = 88;
35pub const TAG_CHAIN_HEAD_UNPIN_RESP: u8 = 89;
36
37pub const TAG_CHAIN_HEAD_CONTINUE_REQ: u8 = 90;
38pub const TAG_CHAIN_HEAD_CONTINUE_RESP: u8 = 91;
39
40pub const TAG_CHAIN_HEAD_STOP_OP_REQ: u8 = 92;
41pub const TAG_CHAIN_HEAD_STOP_OP_RESP: u8 = 93;
42
43pub const TAG_CHAIN_SPEC_GENESIS_HASH_REQ: u8 = 94;
44pub const TAG_CHAIN_SPEC_GENESIS_HASH_RESP: u8 = 95;
45
46pub const TAG_CHAIN_SPEC_CHAIN_NAME_REQ: u8 = 96;
47pub const TAG_CHAIN_SPEC_CHAIN_NAME_RESP: u8 = 97;
48
49pub const TAG_CHAIN_SPEC_PROPERTIES_REQ: u8 = 98;
50pub const TAG_CHAIN_SPEC_PROPERTIES_RESP: u8 = 99;
51
52pub const TAG_CHAIN_TX_BROADCAST_REQ: u8 = 100;
53pub const TAG_CHAIN_TX_BROADCAST_RESP: u8 = 101;
54
55pub const TAG_CHAIN_TX_STOP_REQ: u8 = 102;
56pub const TAG_CHAIN_TX_STOP_RESP: u8 = 103;
57
58// ---------------------------------------------------------------------------
59// Data types
60// ---------------------------------------------------------------------------
61
62/// Runtime information attached to Initialized / NewBlock events.
63#[derive(Debug, Clone)]
64pub enum RuntimeInfo {
65    /// A valid runtime with its specification.
66    Valid(RuntimeSpec),
67    /// A runtime that could not be decoded.
68    Invalid { error: String },
69}
70
71/// Parsed runtime specification fields.
72#[derive(Debug, Clone)]
73pub struct RuntimeSpec {
74    pub spec_name: String,
75    pub impl_name: String,
76    pub spec_version: u32,
77    pub impl_version: u32,
78    /// `None` when the chain does not expose a transaction version.
79    pub transaction_version: Option<u32>,
80    /// List of `(apiName, version)` tuples.
81    pub apis: Vec<(String, u32)>,
82}
83
84/// A single storage result item returned in OperationStorageItems.
85#[derive(Debug, Clone)]
86pub struct StorageResultItem {
87    pub key: Vec<u8>,
88    /// Nullable storage value at `key`.
89    pub value: Option<Vec<u8>>,
90    /// Nullable hash of the storage value.
91    pub hash: Option<Vec<u8>>,
92    /// Nullable closest-descendant Merkle value.
93    pub closest_descendant_merkle_value: Option<Vec<u8>>,
94}
95
96/// Strongly-typed representation of every `chainHead_v1_followEvent` variant.
97#[derive(Debug, Clone)]
98pub enum ChainHeadEvent {
99    /// tag 0 — initial state delivered immediately after follow subscription starts.
100    Initialized {
101        finalized_block_hashes: Vec<Vec<u8>>,
102        finalized_block_runtime: Option<RuntimeInfo>,
103    },
104    /// tag 1 — a new block has been added to the chain.
105    NewBlock {
106        block_hash: Vec<u8>,
107        parent_block_hash: Vec<u8>,
108        new_runtime: Option<RuntimeInfo>,
109    },
110    /// tag 2 — the best block has changed.
111    BestBlockChanged { best_block_hash: Vec<u8> },
112    /// tag 3 — one or more blocks have been finalized.
113    Finalized {
114        finalized_block_hashes: Vec<Vec<u8>>,
115        pruned_block_hashes: Vec<Vec<u8>>,
116    },
117    /// tag 4 — body operation completed.
118    OperationBodyDone {
119        operation_id: String,
120        value: Vec<Vec<u8>>,
121    },
122    /// tag 5 — call operation completed.
123    OperationCallDone {
124        operation_id: String,
125        output: Vec<u8>,
126    },
127    /// tag 6 — storage operation delivered a batch of items.
128    OperationStorageItems {
129        operation_id: String,
130        items: Vec<StorageResultItem>,
131    },
132    /// tag 7 — storage operation finished (no more items).
133    OperationStorageDone { operation_id: String },
134    /// tag 8 — operation is paused; call `continue` to resume.
135    OperationWaitingForContinue { operation_id: String },
136    /// tag 9 — operation could not be completed (block inaccessible).
137    OperationInaccessible { operation_id: String },
138    /// tag 10 — operation failed with an error message.
139    OperationError { operation_id: String, error: String },
140    /// tag 11 — the follow subscription has been forcibly stopped by the server.
141    Stop,
142}
143
144// ---------------------------------------------------------------------------
145// Hex helpers
146// ---------------------------------------------------------------------------
147
148/// Convert a `"0xabcd"` hex string to raw bytes `[0xab, 0xcd]`.
149///
150/// The `"0x"` prefix is optional; an empty string returns an empty `Vec`.
151pub fn hex_to_bytes(hex: &str) -> Vec<u8> {
152    let s = hex.strip_prefix("0x").unwrap_or(hex);
153    if s.is_empty() {
154        return Vec::new();
155    }
156    // Decode two hex digits at a time; silently drop trailing nibble on odd length.
157    (0..s.len() / 2)
158        .filter_map(|i| u8::from_str_radix(&s[i * 2..i * 2 + 2], 16).ok())
159        .collect()
160}
161
162/// Convert raw bytes `[0xab, 0xcd]` to `"0xabcd"`.
163pub fn bytes_to_hex(bytes: &[u8]) -> String {
164    let mut s = String::with_capacity(2 + bytes.len() * 2);
165    s.push_str("0x");
166    for b in bytes {
167        s.push(char::from_digit((b >> 4) as u32, 16).unwrap_or('0'));
168        s.push(char::from_digit((b & 0xf) as u32, 16).unwrap_or('0'));
169    }
170    s
171}
172
173// ---------------------------------------------------------------------------
174// JSON parsing helpers
175// ---------------------------------------------------------------------------
176
177/// Extract the `"0x..."` string from a JSON value and decode to bytes.
178fn json_hex(v: &serde_json::Value) -> Option<Vec<u8>> {
179    Some(hex_to_bytes(v.as_str()?))
180}
181
182/// Parse a JSON `runtimeSpec` object into a `RuntimeSpec`.
183fn parse_runtime_spec(obj: &serde_json::Value) -> Option<RuntimeSpec> {
184    let spec_name = obj.get("specName")?.as_str()?.to_string();
185    let impl_name = obj.get("implName")?.as_str()?.to_string();
186    let spec_version = obj.get("specVersion")?.as_u64()? as u32;
187    let impl_version = obj.get("implVersion")?.as_u64()? as u32;
188    let transaction_version = obj
189        .get("transactionVersion")
190        .and_then(|v| v.as_u64())
191        .map(|v| v as u32);
192
193    // apis: array of [name, version] tuples (JSON spec uses arrays, not objects)
194    let apis = obj
195        .get("apis")
196        .and_then(|a| a.as_array())
197        .map(|arr| {
198            arr.iter()
199                .filter_map(|item| {
200                    let pair = item.as_array()?;
201                    if pair.len() < 2 {
202                        return None;
203                    }
204                    let name = pair[0].as_str()?.to_string();
205                    let version = pair[1].as_u64()? as u32;
206                    Some((name, version))
207                })
208                .collect()
209        })
210        .unwrap_or_default();
211
212    Some(RuntimeSpec {
213        spec_name,
214        impl_name,
215        spec_version,
216        impl_version,
217        transaction_version,
218        apis,
219    })
220}
221
222/// Parse a JSON `runtime` field (may be `null`, `{ "type": "valid", ... }`, or `{ "type": "invalid", ... }`).
223fn parse_runtime_info(v: &serde_json::Value) -> Option<RuntimeInfo> {
224    if v.is_null() {
225        return None;
226    }
227    let ty = v.get("type")?.as_str()?;
228    match ty {
229        "valid" => {
230            let spec = parse_runtime_spec(v.get("spec")?)?;
231            Some(RuntimeInfo::Valid(spec))
232        }
233        "invalid" => {
234            let error = v
235                .get("error")
236                .and_then(|e| e.as_str())
237                .unwrap_or("")
238                .to_string();
239            Some(RuntimeInfo::Invalid { error })
240        }
241        _ => None,
242    }
243}
244
245/// Parse a JSON array of hex strings into `Vec<Vec<u8>>`.
246fn parse_hex_array(arr: &serde_json::Value) -> Vec<Vec<u8>> {
247    arr.as_array()
248        .map(|a| a.iter().filter_map(json_hex).collect())
249        .unwrap_or_default()
250}
251
252/// Parse a JSON `storageResultItem` object.
253fn parse_storage_item(obj: &serde_json::Value) -> Option<StorageResultItem> {
254    let key = json_hex(obj.get("key")?)?;
255
256    // Each of these three fields is nullable (may be absent or null).
257    let value = obj
258        .get("value")
259        .and_then(|v| if v.is_null() { None } else { json_hex(v) });
260    let hash = obj
261        .get("hash")
262        .and_then(|v| if v.is_null() { None } else { json_hex(v) });
263    let closest_descendant_merkle_value = obj.get("closestDescendantMerkleValue").and_then(|v| {
264        if v.is_null() {
265            None
266        } else {
267            json_hex(v)
268        }
269    });
270
271    Some(StorageResultItem {
272        key,
273        value,
274        hash,
275        closest_descendant_merkle_value,
276    })
277}
278
279// ---------------------------------------------------------------------------
280// Public: parse a chainHead_v1_followEvent JSON-RPC notification
281// ---------------------------------------------------------------------------
282
283/// Parse a smoldot `chainHead_v1_followEvent` notification into a `ChainHeadEvent`.
284///
285/// Expected JSON shape:
286/// ```json
287/// {
288///   "jsonrpc": "2.0",
289///   "method": "chainHead_v1_followEvent",
290///   "params": {
291///     "subscription": "...",
292///     "result": { "event": "...", ... }
293///   }
294/// }
295/// ```
296///
297/// Returns `None` if the message is not a follow-event or cannot be parsed.
298pub fn parse_chain_head_json_rpc(json_rpc: &str) -> Option<ChainHeadEvent> {
299    let root: serde_json::Value = serde_json::from_str(json_rpc).ok()?;
300
301    // Verify method name.
302    if root.get("method")?.as_str()? != "chainHead_v1_followEvent" {
303        return None;
304    }
305
306    let result = root.get("params")?.get("result")?;
307    let event = result.get("event")?.as_str()?;
308
309    match event {
310        "initialized" => {
311            let finalized_block_hashes = parse_hex_array(
312                result
313                    .get("finalizedBlockHashes")
314                    .unwrap_or(&serde_json::Value::Null),
315            );
316            let finalized_block_runtime = result
317                .get("finalizedBlockRuntime")
318                .and_then(parse_runtime_info);
319            Some(ChainHeadEvent::Initialized {
320                finalized_block_hashes,
321                finalized_block_runtime,
322            })
323        }
324
325        "newBlock" => {
326            let block_hash = json_hex(result.get("blockHash")?)?;
327            let parent_block_hash = json_hex(result.get("parentBlockHash")?)?;
328            let new_runtime = result.get("newRuntime").and_then(parse_runtime_info);
329            Some(ChainHeadEvent::NewBlock {
330                block_hash,
331                parent_block_hash,
332                new_runtime,
333            })
334        }
335
336        "bestBlockChanged" => {
337            let best_block_hash = json_hex(result.get("bestBlockHash")?)?;
338            Some(ChainHeadEvent::BestBlockChanged { best_block_hash })
339        }
340
341        "finalized" => {
342            let finalized_block_hashes = parse_hex_array(
343                result
344                    .get("finalizedBlockHashes")
345                    .unwrap_or(&serde_json::Value::Null),
346            );
347            let pruned_block_hashes = parse_hex_array(
348                result
349                    .get("prunedBlockHashes")
350                    .unwrap_or(&serde_json::Value::Null),
351            );
352            Some(ChainHeadEvent::Finalized {
353                finalized_block_hashes,
354                pruned_block_hashes,
355            })
356        }
357
358        "operationBodyDone" => {
359            let operation_id = result.get("operationId")?.as_str()?.to_string();
360            let value = parse_hex_array(result.get("value").unwrap_or(&serde_json::Value::Null));
361            Some(ChainHeadEvent::OperationBodyDone {
362                operation_id,
363                value,
364            })
365        }
366
367        "operationCallDone" => {
368            let operation_id = result.get("operationId")?.as_str()?.to_string();
369            let output = json_hex(result.get("output")?)?;
370            Some(ChainHeadEvent::OperationCallDone {
371                operation_id,
372                output,
373            })
374        }
375
376        "operationStorageItems" => {
377            let operation_id = result.get("operationId")?.as_str()?.to_string();
378            let items = result
379                .get("items")
380                .and_then(|a| a.as_array())
381                .map(|arr| arr.iter().filter_map(parse_storage_item).collect())
382                .unwrap_or_default();
383            Some(ChainHeadEvent::OperationStorageItems {
384                operation_id,
385                items,
386            })
387        }
388
389        "operationStorageDone" => {
390            let operation_id = result.get("operationId")?.as_str()?.to_string();
391            Some(ChainHeadEvent::OperationStorageDone { operation_id })
392        }
393
394        "operationWaitingForContinue" => {
395            let operation_id = result.get("operationId")?.as_str()?.to_string();
396            Some(ChainHeadEvent::OperationWaitingForContinue { operation_id })
397        }
398
399        "operationInaccessible" => {
400            let operation_id = result.get("operationId")?.as_str()?.to_string();
401            Some(ChainHeadEvent::OperationInaccessible { operation_id })
402        }
403
404        "operationError" => {
405            let operation_id = result.get("operationId")?.as_str()?.to_string();
406            let error = result
407                .get("error")
408                .and_then(|e| e.as_str())
409                .unwrap_or("")
410                .to_string();
411            Some(ChainHeadEvent::OperationError {
412                operation_id,
413                error,
414            })
415        }
416
417        "stop" => Some(ChainHeadEvent::Stop),
418
419        _ => None,
420    }
421}
422
423// ---------------------------------------------------------------------------
424// SCALE encoding helpers
425// ---------------------------------------------------------------------------
426
427/// Encode a `RuntimeInfo` option as SCALE Option(RuntimeType).
428///
429/// None     → 0x00
430/// Some(v)  → 0x01 + RuntimeType_SCALE
431fn encode_runtime_info_option(buf: &mut Vec<u8>, rt: &Option<RuntimeInfo>) {
432    match rt {
433        None => encode_option_none(buf),
434        Some(info) => {
435            encode_option_some(buf);
436            encode_runtime_info(buf, info);
437        }
438    }
439}
440
441/// Encode a `RuntimeInfo` as the SCALE `RuntimeType` enum.
442///
443/// RuntimeType = Enum { 0: Valid(RuntimeSpec), 1: Invalid({ error: str }) }
444fn encode_runtime_info(buf: &mut Vec<u8>, info: &RuntimeInfo) {
445    match info {
446        RuntimeInfo::Valid(spec) => {
447            encode_tag(buf, 0); // Valid variant
448            encode_string(buf, &spec.spec_name);
449            encode_string(buf, &spec.impl_name);
450            buf.extend_from_slice(&spec.spec_version.to_le_bytes());
451            buf.extend_from_slice(&spec.impl_version.to_le_bytes());
452            // transactionVersion: Option(u32)
453            match spec.transaction_version {
454                None => encode_option_none(buf),
455                Some(v) => {
456                    encode_option_some(buf);
457                    buf.extend_from_slice(&v.to_le_bytes());
458                }
459            }
460            // apis: Vector(Tuple(str, u32))
461            encode_vector_len(buf, spec.apis.len() as u32);
462            for (name, version) in &spec.apis {
463                encode_string(buf, name);
464                buf.extend_from_slice(&version.to_le_bytes());
465            }
466        }
467        RuntimeInfo::Invalid { error } => {
468            encode_tag(buf, 1); // Invalid variant
469            encode_string(buf, error);
470        }
471    }
472}
473
474/// Encode a `ChainHeadEvent` as its SCALE representation (without the outer envelope).
475fn encode_event_payload(buf: &mut Vec<u8>, event: &ChainHeadEvent) {
476    match event {
477        ChainHeadEvent::Initialized {
478            finalized_block_hashes,
479            finalized_block_runtime,
480        } => {
481            encode_tag(buf, 0);
482            encode_vector_len(buf, finalized_block_hashes.len() as u32);
483            for hash in finalized_block_hashes {
484                encode_var_bytes(buf, hash);
485            }
486            encode_runtime_info_option(buf, finalized_block_runtime);
487        }
488
489        ChainHeadEvent::NewBlock {
490            block_hash,
491            parent_block_hash,
492            new_runtime,
493        } => {
494            encode_tag(buf, 1);
495            encode_var_bytes(buf, block_hash);
496            encode_var_bytes(buf, parent_block_hash);
497            encode_runtime_info_option(buf, new_runtime);
498        }
499
500        ChainHeadEvent::BestBlockChanged { best_block_hash } => {
501            encode_tag(buf, 2);
502            encode_var_bytes(buf, best_block_hash);
503        }
504
505        ChainHeadEvent::Finalized {
506            finalized_block_hashes,
507            pruned_block_hashes,
508        } => {
509            encode_tag(buf, 3);
510            encode_vector_len(buf, finalized_block_hashes.len() as u32);
511            for hash in finalized_block_hashes {
512                encode_var_bytes(buf, hash);
513            }
514            encode_vector_len(buf, pruned_block_hashes.len() as u32);
515            for hash in pruned_block_hashes {
516                encode_var_bytes(buf, hash);
517            }
518        }
519
520        ChainHeadEvent::OperationBodyDone {
521            operation_id,
522            value,
523        } => {
524            encode_tag(buf, 4);
525            encode_string(buf, operation_id);
526            encode_vector_len(buf, value.len() as u32);
527            for item in value {
528                encode_var_bytes(buf, item);
529            }
530        }
531
532        ChainHeadEvent::OperationCallDone {
533            operation_id,
534            output,
535        } => {
536            encode_tag(buf, 5);
537            encode_string(buf, operation_id);
538            encode_var_bytes(buf, output);
539        }
540
541        ChainHeadEvent::OperationStorageItems {
542            operation_id,
543            items,
544        } => {
545            encode_tag(buf, 6);
546            encode_string(buf, operation_id);
547            encode_vector_len(buf, items.len() as u32);
548            for item in items {
549                encode_var_bytes(buf, &item.key);
550                // value: Nullable(Hex())
551                match &item.value {
552                    None => encode_option_none(buf),
553                    Some(v) => {
554                        encode_option_some(buf);
555                        encode_var_bytes(buf, v);
556                    }
557                }
558                // hash: Nullable(Hex())
559                match &item.hash {
560                    None => encode_option_none(buf),
561                    Some(h) => {
562                        encode_option_some(buf);
563                        encode_var_bytes(buf, h);
564                    }
565                }
566                // closestDescendantMerkleValue: Nullable(Hex())
567                match &item.closest_descendant_merkle_value {
568                    None => encode_option_none(buf),
569                    Some(v) => {
570                        encode_option_some(buf);
571                        encode_var_bytes(buf, v);
572                    }
573                }
574            }
575        }
576
577        ChainHeadEvent::OperationStorageDone { operation_id } => {
578            encode_tag(buf, 7);
579            encode_string(buf, operation_id);
580        }
581
582        ChainHeadEvent::OperationWaitingForContinue { operation_id } => {
583            encode_tag(buf, 8);
584            encode_string(buf, operation_id);
585        }
586
587        ChainHeadEvent::OperationInaccessible { operation_id } => {
588            encode_tag(buf, 9);
589            encode_string(buf, operation_id);
590        }
591
592        ChainHeadEvent::OperationError {
593            operation_id,
594            error,
595        } => {
596            encode_tag(buf, 10);
597            encode_string(buf, operation_id);
598            encode_string(buf, error);
599        }
600
601        ChainHeadEvent::Stop => {
602            encode_tag(buf, 11);
603            // void — no payload
604        }
605    }
606}
607
608/// Write the standard message envelope: `request_id + response_tag + v1(0)`.
609fn encode_envelope(buf: &mut Vec<u8>, request_id: &str, response_tag: u8) {
610    encode_string(buf, request_id);
611    encode_tag(buf, response_tag);
612    encode_tag(buf, 0); // v1
613}
614
615/// Write the `GenericError` payload for Result::Err.
616///
617/// Encoding: `0x01 (Err) + 0x00 (Unknown variant) + SCALE string(reason)`.
618fn encode_generic_error(buf: &mut Vec<u8>, reason: &str) {
619    encode_result_err(buf);
620    encode_tag(buf, 0); // Unknown variant
621    encode_string(buf, reason);
622}
623
624// ---------------------------------------------------------------------------
625// Public encode functions
626// ---------------------------------------------------------------------------
627
628/// Encode a `chainHead_v1_followEvent` receive message (tag 79).
629///
630/// Wire format: `request_id + 0x4F (79) + 0x00 (v1) + ChainHeadEvent_SCALE`
631pub fn encode_chain_head_event(request_id: &str, event: &ChainHeadEvent) -> Vec<u8> {
632    let mut buf = Vec::with_capacity(128);
633    encode_envelope(&mut buf, request_id, TAG_CHAIN_HEAD_FOLLOW_RECEIVE);
634    encode_event_payload(&mut buf, event);
635    buf
636}
637
638/// Encode a `Result(void, GenericError)` response.
639///
640/// Used by tags 89 (unpin), 91 (continue), 93 (stop-operation), 103 (tx-stop).
641pub fn encode_chain_response_void(
642    request_id: &str,
643    response_tag: u8,
644    error: Option<&str>,
645) -> Vec<u8> {
646    let mut buf = Vec::with_capacity(32);
647    encode_envelope(&mut buf, request_id, response_tag);
648    match error {
649        None => encode_result_ok_void(&mut buf),
650        Some(reason) => encode_generic_error(&mut buf, reason),
651    }
652    buf
653}
654
655/// Encode a `Result(Nullable(Hex()), GenericError)` response.
656///
657/// Used by tag 81 (header).
658pub fn encode_chain_response_nullable_hex(
659    request_id: &str,
660    response_tag: u8,
661    result: Result<Option<&[u8]>, &str>,
662) -> Vec<u8> {
663    let mut buf = Vec::with_capacity(64);
664    encode_envelope(&mut buf, request_id, response_tag);
665    match result {
666        Ok(maybe_bytes) => {
667            encode_result_ok(&mut buf);
668            match maybe_bytes {
669                None => encode_option_none(&mut buf),
670                Some(bytes) => {
671                    encode_option_some(&mut buf);
672                    encode_var_bytes(&mut buf, bytes);
673                }
674            }
675        }
676        Err(reason) => encode_generic_error(&mut buf, reason),
677    }
678    buf
679}
680
681/// Encode a `Result(OperationStartedResult, GenericError)` response.
682///
683/// `Ok(Some(id))` encodes as `Started { operationId: id }` (variant 0).
684/// `Ok(None)` encodes as `LimitReached` (variant 1).
685///
686/// Used by tags 83 (body), 85 (storage), 87 (call).
687pub fn encode_chain_response_operation_started(
688    request_id: &str,
689    response_tag: u8,
690    result: Result<Option<&str>, &str>,
691) -> Vec<u8> {
692    let mut buf = Vec::with_capacity(64);
693    encode_envelope(&mut buf, request_id, response_tag);
694    match result {
695        Ok(Some(op_id)) => {
696            encode_result_ok(&mut buf);
697            encode_tag(&mut buf, 0); // Started variant
698            encode_string(&mut buf, op_id);
699        }
700        Ok(None) => {
701            encode_result_ok(&mut buf);
702            encode_tag(&mut buf, 1); // LimitReached variant (void)
703        }
704        Err(reason) => encode_generic_error(&mut buf, reason),
705    }
706    buf
707}
708
709/// Encode a `Result(Hex(), GenericError)` response.
710///
711/// Used by tag 95 (genesis hash).
712pub fn encode_chain_response_hex(
713    request_id: &str,
714    response_tag: u8,
715    result: Result<&[u8], &str>,
716) -> Vec<u8> {
717    let mut buf = Vec::with_capacity(64);
718    encode_envelope(&mut buf, request_id, response_tag);
719    match result {
720        Ok(bytes) => {
721            encode_result_ok(&mut buf);
722            encode_var_bytes(&mut buf, bytes);
723        }
724        Err(reason) => encode_generic_error(&mut buf, reason),
725    }
726    buf
727}
728
729/// Encode a `Result(str, GenericError)` response.
730///
731/// Used by tags 97 (chain name), 99 (chain properties).
732pub fn encode_chain_response_string(
733    request_id: &str,
734    response_tag: u8,
735    result: Result<&str, &str>,
736) -> Vec<u8> {
737    let mut buf = Vec::with_capacity(64);
738    encode_envelope(&mut buf, request_id, response_tag);
739    match result {
740        Ok(s) => {
741            encode_result_ok(&mut buf);
742            encode_string(&mut buf, s);
743        }
744        Err(reason) => encode_generic_error(&mut buf, reason),
745    }
746    buf
747}
748
749/// Encode a `Result(Nullable(str), GenericError)` response.
750///
751/// Used by tag 101 (transaction broadcast).
752pub fn encode_chain_response_nullable_string(
753    request_id: &str,
754    response_tag: u8,
755    result: Result<Option<&str>, &str>,
756) -> Vec<u8> {
757    let mut buf = Vec::with_capacity(64);
758    encode_envelope(&mut buf, request_id, response_tag);
759    match result {
760        Ok(maybe_str) => {
761            encode_result_ok(&mut buf);
762            match maybe_str {
763                None => encode_option_none(&mut buf),
764                Some(s) => {
765                    encode_option_some(&mut buf);
766                    encode_string(&mut buf, s);
767                }
768            }
769        }
770        Err(reason) => encode_generic_error(&mut buf, reason),
771    }
772    buf
773}
774
775fn encode_chain_error_for_request(request_id: &str, request_tag: u8, reason: &str) -> Vec<u8> {
776    match request_tag {
777        TAG_CHAIN_HEAD_HEADER_REQ => {
778            encode_chain_response_nullable_hex(request_id, TAG_CHAIN_HEAD_HEADER_RESP, Err(reason))
779        }
780        TAG_CHAIN_HEAD_BODY_REQ => encode_chain_response_operation_started(
781            request_id,
782            TAG_CHAIN_HEAD_BODY_RESP,
783            Err(reason),
784        ),
785        TAG_CHAIN_HEAD_STORAGE_REQ => encode_chain_response_operation_started(
786            request_id,
787            TAG_CHAIN_HEAD_STORAGE_RESP,
788            Err(reason),
789        ),
790        TAG_CHAIN_HEAD_CALL_REQ => encode_chain_response_operation_started(
791            request_id,
792            TAG_CHAIN_HEAD_CALL_RESP,
793            Err(reason),
794        ),
795        TAG_CHAIN_HEAD_UNPIN_REQ => {
796            encode_chain_response_void(request_id, TAG_CHAIN_HEAD_UNPIN_RESP, Some(reason))
797        }
798        TAG_CHAIN_HEAD_CONTINUE_REQ => {
799            encode_chain_response_void(request_id, TAG_CHAIN_HEAD_CONTINUE_RESP, Some(reason))
800        }
801        TAG_CHAIN_HEAD_STOP_OP_REQ => {
802            encode_chain_response_void(request_id, TAG_CHAIN_HEAD_STOP_OP_RESP, Some(reason))
803        }
804        TAG_CHAIN_SPEC_GENESIS_HASH_REQ => {
805            encode_chain_response_hex(request_id, TAG_CHAIN_SPEC_GENESIS_HASH_RESP, Err(reason))
806        }
807        TAG_CHAIN_SPEC_CHAIN_NAME_REQ => {
808            encode_chain_response_string(request_id, TAG_CHAIN_SPEC_CHAIN_NAME_RESP, Err(reason))
809        }
810        TAG_CHAIN_SPEC_PROPERTIES_REQ => {
811            encode_chain_response_string(request_id, TAG_CHAIN_SPEC_PROPERTIES_RESP, Err(reason))
812        }
813        TAG_CHAIN_TX_BROADCAST_REQ => encode_chain_response_nullable_string(
814            request_id,
815            TAG_CHAIN_TX_BROADCAST_RESP,
816            Err(reason),
817        ),
818        TAG_CHAIN_TX_STOP_REQ => {
819            encode_chain_response_void(request_id, TAG_CHAIN_TX_STOP_RESP, Some(reason))
820        }
821        _ => crate::protocol::encode_jsonrpc_send_error(request_id),
822    }
823}
824
825/// Encode a chain-head follow event notification from raw JSON-RPC text.
826pub fn encode_chain_follow_json_event(request_id: &str, json_rpc: &str) -> Option<Vec<u8>> {
827    parse_chain_head_json_rpc(json_rpc).map(|event| encode_chain_head_event(request_id, &event))
828}
829
830/// Encode a synthetic follow stop event for unsupported/disconnected chains.
831pub fn encode_chain_follow_stop(request_id: &str) -> Vec<u8> {
832    encode_chain_head_event(request_id, &ChainHeadEvent::Stop)
833}
834
835/// Encode a chain RPC error using the correct response tag for `request_tag`.
836pub fn encode_chain_rpc_error(request_id: &str, request_tag: u8, reason: &str) -> Vec<u8> {
837    encode_chain_error_for_request(request_id, request_tag, reason)
838}
839
840/// Encode a chain RPC response from raw JSON-RPC text using the correct
841/// chain protocol envelope for `request_tag`.
842pub fn encode_chain_rpc_json_response(
843    request_id: &str,
844    request_tag: u8,
845    json_rpc: &str,
846) -> Vec<u8> {
847    let root: serde_json::Value = match serde_json::from_str(json_rpc) {
848        Ok(value) => value,
849        Err(_) => {
850            return encode_chain_error_for_request(
851                request_id,
852                request_tag,
853                "invalid json-rpc response",
854            )
855        }
856    };
857
858    if let Some(reason) = root
859        .get("error")
860        .and_then(|error| error.get("message"))
861        .and_then(|message| message.as_str())
862    {
863        return encode_chain_error_for_request(request_id, request_tag, reason);
864    }
865
866    let Some(result) = root.get("result") else {
867        return encode_chain_error_for_request(request_id, request_tag, "missing json-rpc result");
868    };
869
870    match request_tag {
871        TAG_CHAIN_HEAD_HEADER_REQ => {
872            if result.is_null() {
873                encode_chain_response_nullable_hex(request_id, TAG_CHAIN_HEAD_HEADER_RESP, Ok(None))
874            } else if let Some(hex) = result.as_str() {
875                let bytes = hex_to_bytes(hex);
876                encode_chain_response_nullable_hex(
877                    request_id,
878                    TAG_CHAIN_HEAD_HEADER_RESP,
879                    Ok(Some(bytes.as_slice())),
880                )
881            } else {
882                encode_chain_error_for_request(request_id, request_tag, "invalid header result")
883            }
884        }
885        TAG_CHAIN_HEAD_BODY_REQ => {
886            if result.is_null() {
887                encode_chain_response_operation_started(
888                    request_id,
889                    TAG_CHAIN_HEAD_BODY_RESP,
890                    Ok(None),
891                )
892            } else if let Some(operation_id) = result.as_str() {
893                encode_chain_response_operation_started(
894                    request_id,
895                    TAG_CHAIN_HEAD_BODY_RESP,
896                    Ok(Some(operation_id)),
897                )
898            } else {
899                encode_chain_error_for_request(request_id, request_tag, "invalid body result")
900            }
901        }
902        TAG_CHAIN_HEAD_STORAGE_REQ => {
903            if result.is_null() {
904                encode_chain_response_operation_started(
905                    request_id,
906                    TAG_CHAIN_HEAD_STORAGE_RESP,
907                    Ok(None),
908                )
909            } else if let Some(operation_id) = result.as_str() {
910                encode_chain_response_operation_started(
911                    request_id,
912                    TAG_CHAIN_HEAD_STORAGE_RESP,
913                    Ok(Some(operation_id)),
914                )
915            } else {
916                encode_chain_error_for_request(request_id, request_tag, "invalid storage result")
917            }
918        }
919        TAG_CHAIN_HEAD_CALL_REQ => {
920            if result.is_null() {
921                encode_chain_response_operation_started(
922                    request_id,
923                    TAG_CHAIN_HEAD_CALL_RESP,
924                    Ok(None),
925                )
926            } else if let Some(operation_id) = result.as_str() {
927                encode_chain_response_operation_started(
928                    request_id,
929                    TAG_CHAIN_HEAD_CALL_RESP,
930                    Ok(Some(operation_id)),
931                )
932            } else {
933                encode_chain_error_for_request(request_id, request_tag, "invalid call result")
934            }
935        }
936        TAG_CHAIN_HEAD_UNPIN_REQ => {
937            encode_chain_response_void(request_id, TAG_CHAIN_HEAD_UNPIN_RESP, None)
938        }
939        TAG_CHAIN_HEAD_CONTINUE_REQ => {
940            encode_chain_response_void(request_id, TAG_CHAIN_HEAD_CONTINUE_RESP, None)
941        }
942        TAG_CHAIN_HEAD_STOP_OP_REQ => {
943            encode_chain_response_void(request_id, TAG_CHAIN_HEAD_STOP_OP_RESP, None)
944        }
945        TAG_CHAIN_SPEC_GENESIS_HASH_REQ => {
946            if let Some(hex) = result.as_str() {
947                let bytes = hex_to_bytes(hex);
948                encode_chain_response_hex(
949                    request_id,
950                    TAG_CHAIN_SPEC_GENESIS_HASH_RESP,
951                    Ok(bytes.as_slice()),
952                )
953            } else {
954                encode_chain_error_for_request(
955                    request_id,
956                    request_tag,
957                    "invalid genesis hash result",
958                )
959            }
960        }
961        TAG_CHAIN_SPEC_CHAIN_NAME_REQ => {
962            if let Some(name) = result.as_str() {
963                encode_chain_response_string(request_id, TAG_CHAIN_SPEC_CHAIN_NAME_RESP, Ok(name))
964            } else {
965                encode_chain_error_for_request(request_id, request_tag, "invalid chain name result")
966            }
967        }
968        TAG_CHAIN_SPEC_PROPERTIES_REQ => {
969            let properties = serde_json::to_string(result).unwrap_or_else(|_| "{}".to_string());
970            encode_chain_response_string(
971                request_id,
972                TAG_CHAIN_SPEC_PROPERTIES_RESP,
973                Ok(properties.as_str()),
974            )
975        }
976        TAG_CHAIN_TX_BROADCAST_REQ => {
977            if result.is_null() {
978                encode_chain_response_nullable_string(
979                    request_id,
980                    TAG_CHAIN_TX_BROADCAST_RESP,
981                    Ok(None),
982                )
983            } else if let Some(operation_id) = result.as_str() {
984                encode_chain_response_nullable_string(
985                    request_id,
986                    TAG_CHAIN_TX_BROADCAST_RESP,
987                    Ok(Some(operation_id)),
988                )
989            } else {
990                encode_chain_error_for_request(
991                    request_id,
992                    request_tag,
993                    "invalid transaction result",
994                )
995            }
996        }
997        TAG_CHAIN_TX_STOP_REQ => {
998            encode_chain_response_void(request_id, TAG_CHAIN_TX_STOP_RESP, None)
999        }
1000        _ => crate::protocol::encode_jsonrpc_send_error(request_id),
1001    }
1002}