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: `discriminator + request_id + response_tag + v1(0)`.
609fn encode_envelope(buf: &mut Vec<u8>, request_id: &str, response_tag: u8) {
610    buf.push(crate::protocol::PROTOCOL_DISCRIMINATOR);
611    encode_string(buf, request_id);
612    encode_tag(buf, response_tag);
613    encode_tag(buf, 0); // v1
614}
615
616/// Write the `GenericError` payload for Result::Err.
617///
618/// Encoding: `0x01 (Err) + 0x00 (Unknown variant) + SCALE string(reason)`.
619fn encode_generic_error(buf: &mut Vec<u8>, reason: &str) {
620    encode_result_err(buf);
621    encode_tag(buf, 0); // Unknown variant
622    encode_string(buf, reason);
623}
624
625// ---------------------------------------------------------------------------
626// Public encode functions
627// ---------------------------------------------------------------------------
628
629/// Encode a `chainHead_v1_followEvent` receive message (tag 79).
630///
631/// Wire format: `request_id + 0x4F (79) + 0x00 (v1) + ChainHeadEvent_SCALE`
632pub fn encode_chain_head_event(request_id: &str, event: &ChainHeadEvent) -> Vec<u8> {
633    let mut buf = Vec::with_capacity(128);
634    encode_envelope(&mut buf, request_id, TAG_CHAIN_HEAD_FOLLOW_RECEIVE);
635    encode_event_payload(&mut buf, event);
636    buf
637}
638
639/// Encode a `Result(void, GenericError)` response.
640///
641/// Used by tags 89 (unpin), 91 (continue), 93 (stop-operation), 103 (tx-stop).
642pub fn encode_chain_response_void(
643    request_id: &str,
644    response_tag: u8,
645    error: Option<&str>,
646) -> Vec<u8> {
647    let mut buf = Vec::with_capacity(32);
648    encode_envelope(&mut buf, request_id, response_tag);
649    match error {
650        None => encode_result_ok_void(&mut buf),
651        Some(reason) => encode_generic_error(&mut buf, reason),
652    }
653    buf
654}
655
656/// Encode a `Result(Nullable(Hex()), GenericError)` response.
657///
658/// Used by tag 81 (header).
659pub fn encode_chain_response_nullable_hex(
660    request_id: &str,
661    response_tag: u8,
662    result: Result<Option<&[u8]>, &str>,
663) -> Vec<u8> {
664    let mut buf = Vec::with_capacity(64);
665    encode_envelope(&mut buf, request_id, response_tag);
666    match result {
667        Ok(maybe_bytes) => {
668            encode_result_ok(&mut buf);
669            match maybe_bytes {
670                None => encode_option_none(&mut buf),
671                Some(bytes) => {
672                    encode_option_some(&mut buf);
673                    encode_var_bytes(&mut buf, bytes);
674                }
675            }
676        }
677        Err(reason) => encode_generic_error(&mut buf, reason),
678    }
679    buf
680}
681
682/// Encode a `Result(OperationStartedResult, GenericError)` response.
683///
684/// `Ok(Some(id))` encodes as `Started { operationId: id }` (variant 0).
685/// `Ok(None)` encodes as `LimitReached` (variant 1).
686///
687/// Used by tags 83 (body), 85 (storage), 87 (call).
688pub fn encode_chain_response_operation_started(
689    request_id: &str,
690    response_tag: u8,
691    result: Result<Option<&str>, &str>,
692) -> Vec<u8> {
693    let mut buf = Vec::with_capacity(64);
694    encode_envelope(&mut buf, request_id, response_tag);
695    match result {
696        Ok(Some(op_id)) => {
697            encode_result_ok(&mut buf);
698            encode_tag(&mut buf, 0); // Started variant
699            encode_string(&mut buf, op_id);
700        }
701        Ok(None) => {
702            encode_result_ok(&mut buf);
703            encode_tag(&mut buf, 1); // LimitReached variant (void)
704        }
705        Err(reason) => encode_generic_error(&mut buf, reason),
706    }
707    buf
708}
709
710/// Encode a `Result(Hex(), GenericError)` response.
711///
712/// Used by tag 95 (genesis hash).
713pub fn encode_chain_response_hex(
714    request_id: &str,
715    response_tag: u8,
716    result: Result<&[u8], &str>,
717) -> Vec<u8> {
718    let mut buf = Vec::with_capacity(64);
719    encode_envelope(&mut buf, request_id, response_tag);
720    match result {
721        Ok(bytes) => {
722            encode_result_ok(&mut buf);
723            encode_var_bytes(&mut buf, bytes);
724        }
725        Err(reason) => encode_generic_error(&mut buf, reason),
726    }
727    buf
728}
729
730/// Encode a `Result(str, GenericError)` response.
731///
732/// Used by tags 97 (chain name), 99 (chain properties).
733pub fn encode_chain_response_string(
734    request_id: &str,
735    response_tag: u8,
736    result: Result<&str, &str>,
737) -> Vec<u8> {
738    let mut buf = Vec::with_capacity(64);
739    encode_envelope(&mut buf, request_id, response_tag);
740    match result {
741        Ok(s) => {
742            encode_result_ok(&mut buf);
743            encode_string(&mut buf, s);
744        }
745        Err(reason) => encode_generic_error(&mut buf, reason),
746    }
747    buf
748}
749
750/// Encode a `Result(Nullable(str), GenericError)` response.
751///
752/// Used by tag 101 (transaction broadcast).
753pub fn encode_chain_response_nullable_string(
754    request_id: &str,
755    response_tag: u8,
756    result: Result<Option<&str>, &str>,
757) -> Vec<u8> {
758    let mut buf = Vec::with_capacity(64);
759    encode_envelope(&mut buf, request_id, response_tag);
760    match result {
761        Ok(maybe_str) => {
762            encode_result_ok(&mut buf);
763            match maybe_str {
764                None => encode_option_none(&mut buf),
765                Some(s) => {
766                    encode_option_some(&mut buf);
767                    encode_string(&mut buf, s);
768                }
769            }
770        }
771        Err(reason) => encode_generic_error(&mut buf, reason),
772    }
773    buf
774}
775
776fn encode_chain_error_for_request(request_id: &str, request_tag: u8, reason: &str) -> Vec<u8> {
777    match request_tag {
778        TAG_CHAIN_HEAD_HEADER_REQ => {
779            encode_chain_response_nullable_hex(request_id, TAG_CHAIN_HEAD_HEADER_RESP, Err(reason))
780        }
781        TAG_CHAIN_HEAD_BODY_REQ => encode_chain_response_operation_started(
782            request_id,
783            TAG_CHAIN_HEAD_BODY_RESP,
784            Err(reason),
785        ),
786        TAG_CHAIN_HEAD_STORAGE_REQ => encode_chain_response_operation_started(
787            request_id,
788            TAG_CHAIN_HEAD_STORAGE_RESP,
789            Err(reason),
790        ),
791        TAG_CHAIN_HEAD_CALL_REQ => encode_chain_response_operation_started(
792            request_id,
793            TAG_CHAIN_HEAD_CALL_RESP,
794            Err(reason),
795        ),
796        TAG_CHAIN_HEAD_UNPIN_REQ => {
797            encode_chain_response_void(request_id, TAG_CHAIN_HEAD_UNPIN_RESP, Some(reason))
798        }
799        TAG_CHAIN_HEAD_CONTINUE_REQ => {
800            encode_chain_response_void(request_id, TAG_CHAIN_HEAD_CONTINUE_RESP, Some(reason))
801        }
802        TAG_CHAIN_HEAD_STOP_OP_REQ => {
803            encode_chain_response_void(request_id, TAG_CHAIN_HEAD_STOP_OP_RESP, Some(reason))
804        }
805        TAG_CHAIN_SPEC_GENESIS_HASH_REQ => {
806            encode_chain_response_hex(request_id, TAG_CHAIN_SPEC_GENESIS_HASH_RESP, Err(reason))
807        }
808        TAG_CHAIN_SPEC_CHAIN_NAME_REQ => {
809            encode_chain_response_string(request_id, TAG_CHAIN_SPEC_CHAIN_NAME_RESP, Err(reason))
810        }
811        TAG_CHAIN_SPEC_PROPERTIES_REQ => {
812            encode_chain_response_string(request_id, TAG_CHAIN_SPEC_PROPERTIES_RESP, Err(reason))
813        }
814        TAG_CHAIN_TX_BROADCAST_REQ => encode_chain_response_nullable_string(
815            request_id,
816            TAG_CHAIN_TX_BROADCAST_RESP,
817            Err(reason),
818        ),
819        TAG_CHAIN_TX_STOP_REQ => {
820            encode_chain_response_void(request_id, TAG_CHAIN_TX_STOP_RESP, Some(reason))
821        }
822        _ => crate::protocol::encode_jsonrpc_send_error(request_id),
823    }
824}
825
826/// Encode a chain-head follow event notification from raw JSON-RPC text.
827pub fn encode_chain_follow_json_event(request_id: &str, json_rpc: &str) -> Option<Vec<u8>> {
828    parse_chain_head_json_rpc(json_rpc).map(|event| encode_chain_head_event(request_id, &event))
829}
830
831/// Encode a synthetic follow stop event for unsupported/disconnected chains.
832pub fn encode_chain_follow_stop(request_id: &str) -> Vec<u8> {
833    encode_chain_head_event(request_id, &ChainHeadEvent::Stop)
834}
835
836/// Encode a chain RPC error using the correct response tag for `request_tag`.
837pub fn encode_chain_rpc_error(request_id: &str, request_tag: u8, reason: &str) -> Vec<u8> {
838    encode_chain_error_for_request(request_id, request_tag, reason)
839}
840
841/// Encode a chain RPC response from raw JSON-RPC text using the correct
842/// chain protocol envelope for `request_tag`.
843pub fn encode_chain_rpc_json_response(
844    request_id: &str,
845    request_tag: u8,
846    json_rpc: &str,
847) -> Vec<u8> {
848    let root: serde_json::Value = match serde_json::from_str(json_rpc) {
849        Ok(value) => value,
850        Err(_) => {
851            return encode_chain_error_for_request(
852                request_id,
853                request_tag,
854                "invalid json-rpc response",
855            )
856        }
857    };
858
859    if let Some(reason) = root
860        .get("error")
861        .and_then(|error| error.get("message"))
862        .and_then(|message| message.as_str())
863    {
864        return encode_chain_error_for_request(request_id, request_tag, reason);
865    }
866
867    let Some(result) = root.get("result") else {
868        return encode_chain_error_for_request(request_id, request_tag, "missing json-rpc result");
869    };
870
871    match request_tag {
872        TAG_CHAIN_HEAD_HEADER_REQ => {
873            if result.is_null() {
874                encode_chain_response_nullable_hex(request_id, TAG_CHAIN_HEAD_HEADER_RESP, Ok(None))
875            } else if let Some(hex) = result.as_str() {
876                let bytes = hex_to_bytes(hex);
877                encode_chain_response_nullable_hex(
878                    request_id,
879                    TAG_CHAIN_HEAD_HEADER_RESP,
880                    Ok(Some(bytes.as_slice())),
881                )
882            } else {
883                encode_chain_error_for_request(request_id, request_tag, "invalid header result")
884            }
885        }
886        TAG_CHAIN_HEAD_BODY_REQ => {
887            if result.is_null() {
888                encode_chain_response_operation_started(
889                    request_id,
890                    TAG_CHAIN_HEAD_BODY_RESP,
891                    Ok(None),
892                )
893            } else if let Some(operation_id) = result.as_str() {
894                encode_chain_response_operation_started(
895                    request_id,
896                    TAG_CHAIN_HEAD_BODY_RESP,
897                    Ok(Some(operation_id)),
898                )
899            } else {
900                encode_chain_error_for_request(request_id, request_tag, "invalid body result")
901            }
902        }
903        TAG_CHAIN_HEAD_STORAGE_REQ => {
904            if result.is_null() {
905                encode_chain_response_operation_started(
906                    request_id,
907                    TAG_CHAIN_HEAD_STORAGE_RESP,
908                    Ok(None),
909                )
910            } else if let Some(operation_id) = result.as_str() {
911                encode_chain_response_operation_started(
912                    request_id,
913                    TAG_CHAIN_HEAD_STORAGE_RESP,
914                    Ok(Some(operation_id)),
915                )
916            } else {
917                encode_chain_error_for_request(request_id, request_tag, "invalid storage result")
918            }
919        }
920        TAG_CHAIN_HEAD_CALL_REQ => {
921            if result.is_null() {
922                encode_chain_response_operation_started(
923                    request_id,
924                    TAG_CHAIN_HEAD_CALL_RESP,
925                    Ok(None),
926                )
927            } else if let Some(operation_id) = result.as_str() {
928                encode_chain_response_operation_started(
929                    request_id,
930                    TAG_CHAIN_HEAD_CALL_RESP,
931                    Ok(Some(operation_id)),
932                )
933            } else {
934                encode_chain_error_for_request(request_id, request_tag, "invalid call result")
935            }
936        }
937        TAG_CHAIN_HEAD_UNPIN_REQ => {
938            encode_chain_response_void(request_id, TAG_CHAIN_HEAD_UNPIN_RESP, None)
939        }
940        TAG_CHAIN_HEAD_CONTINUE_REQ => {
941            encode_chain_response_void(request_id, TAG_CHAIN_HEAD_CONTINUE_RESP, None)
942        }
943        TAG_CHAIN_HEAD_STOP_OP_REQ => {
944            encode_chain_response_void(request_id, TAG_CHAIN_HEAD_STOP_OP_RESP, None)
945        }
946        TAG_CHAIN_SPEC_GENESIS_HASH_REQ => {
947            if let Some(hex) = result.as_str() {
948                let bytes = hex_to_bytes(hex);
949                encode_chain_response_hex(
950                    request_id,
951                    TAG_CHAIN_SPEC_GENESIS_HASH_RESP,
952                    Ok(bytes.as_slice()),
953                )
954            } else {
955                encode_chain_error_for_request(
956                    request_id,
957                    request_tag,
958                    "invalid genesis hash result",
959                )
960            }
961        }
962        TAG_CHAIN_SPEC_CHAIN_NAME_REQ => {
963            if let Some(name) = result.as_str() {
964                encode_chain_response_string(request_id, TAG_CHAIN_SPEC_CHAIN_NAME_RESP, Ok(name))
965            } else {
966                encode_chain_error_for_request(request_id, request_tag, "invalid chain name result")
967            }
968        }
969        TAG_CHAIN_SPEC_PROPERTIES_REQ => {
970            let properties = serde_json::to_string(result).unwrap_or_else(|_| "{}".to_string());
971            encode_chain_response_string(
972                request_id,
973                TAG_CHAIN_SPEC_PROPERTIES_RESP,
974                Ok(properties.as_str()),
975            )
976        }
977        TAG_CHAIN_TX_BROADCAST_REQ => {
978            if result.is_null() {
979                encode_chain_response_nullable_string(
980                    request_id,
981                    TAG_CHAIN_TX_BROADCAST_RESP,
982                    Ok(None),
983                )
984            } else if let Some(operation_id) = result.as_str() {
985                encode_chain_response_nullable_string(
986                    request_id,
987                    TAG_CHAIN_TX_BROADCAST_RESP,
988                    Ok(Some(operation_id)),
989                )
990            } else {
991                encode_chain_error_for_request(
992                    request_id,
993                    request_tag,
994                    "invalid transaction result",
995                )
996            }
997        }
998        TAG_CHAIN_TX_STOP_REQ => {
999            encode_chain_response_void(request_id, TAG_CHAIN_TX_STOP_RESP, None)
1000        }
1001        _ => crate::protocol::encode_jsonrpc_send_error(request_id),
1002    }
1003}