Skip to main content

nub_flat/
local.rs

1//! [`FlatLocal`] — the in-process half of the flat personality.
2//!
3//! A map from content hash to published program, and an `invoke` that
4//! lowers onto the PVM2 interpreter. That is the entire host-side
5//! obligation: compare with `javm::JavmLocal`, which additionally
6//! resolves a capability graph.
7//!
8//! Everything real is already in `nub-arch-local`
9//! ([`PreparedProgram`]); this type only stores bytes and looks them up.
10
11use std::collections::HashMap;
12
13use anyhow::{Result, anyhow};
14use nub::{InvocationResult, LocalKernel, ObjHash};
15use nub_arch_local::PreparedProgram;
16use nub_program::ProgramBlob;
17
18use crate::hash::content_hash;
19
20/// Host-side store: published programs, keyed by content hash.
21#[derive(Default)]
22pub struct FlatLocal {
23    /// Publication is permanent — see [`LocalKernel::put_object`]. This
24    /// map is only ever inserted into.
25    programs: HashMap<ObjHash, ProgramBlob>,
26    /// No state transition to record: a flat invocation mutates nothing
27    /// the host can observe, so the root stays zero. A personality with
28    /// persistent state would hash it here.
29    state_root: ObjHash,
30}
31
32impl FlatLocal {
33    /// Decode and validate a published program.
34    fn decode(bytes: &[u8]) -> Result<ProgramBlob> {
35        ProgramBlob::from_bytes(bytes).map_err(|e| anyhow!("decode program: {e}"))
36    }
37
38    /// Number of published programs. Handy in tests.
39    pub fn len(&self) -> usize {
40        self.programs.len()
41    }
42
43    pub fn is_empty(&self) -> bool {
44        self.programs.is_empty()
45    }
46}
47
48impl LocalKernel for FlatLocal {
49    fn put_object(&mut self, bytes: &[u8]) -> Result<ObjHash> {
50        let program = Self::decode(bytes)?;
51        let hash = content_hash(bytes);
52        self.programs.insert(hash, program);
53        Ok(hash)
54    }
55
56    fn put_object_with_hash(&mut self, hash: ObjHash, bytes: &[u8]) -> Result<()> {
57        let program = Self::decode(bytes)?;
58        debug_assert_eq!(
59            hash,
60            content_hash(bytes),
61            "claimed hash does not match the content"
62        );
63        self.programs.insert(hash, program);
64        Ok(())
65    }
66
67    fn invoke(
68        &mut self,
69        root: ObjHash,
70        endpoint: u32,
71        args: [u64; 4],
72        initial_gas: u64,
73    ) -> Result<InvocationResult> {
74        let program = self
75            .programs
76            .get(&root)
77            .ok_or_else(|| anyhow!("no program published under {}", hex(&root)))?;
78        let endpoint = u8::try_from(endpoint)
79            .map_err(|_| anyhow!("endpoint {endpoint} out of range (flat programs use u8)"))?;
80        let prepared = PreparedProgram::new(program, endpoint, args)
81            .map_err(|e| anyhow!("prepare endpoint {endpoint}: {e}"))?;
82        let mut handler = nub_arch_local::ExitingEcallHandler;
83        Ok(nub_arch_local::run_program(
84            &prepared.spec(),
85            &mut handler,
86            initial_gas,
87        ))
88    }
89
90    fn state_root(&self) -> ObjHash {
91        self.state_root
92    }
93}
94
95fn hex(h: &ObjHash) -> String {
96    h.iter().map(|b| format!("{b:02x}")).collect()
97}