ledger_sim/drivers/
local.rs

1//! Local driver for speculos execution, runs a speculos instance from the
2//! local environment.
3
4use std::{
5    net::{IpAddr, Ipv4Addr, SocketAddr},
6    process::Stdio,
7};
8
9use async_trait::async_trait;
10use tokio::process::{Child, Command};
11use tracing::debug;
12
13use super::Driver;
14use crate::{Handle, Options};
15
16/// Local (child process) based speculos driver
17pub struct LocalDriver;
18
19/// Handle to a speculos instance running locally (as a child process)
20#[derive(Debug)]
21pub struct LocalHandle {
22    /// HTTP API socket address
23    addr: SocketAddr,
24    /// Child task handle
25    child: Child,
26}
27
28impl LocalDriver {
29    /// Create a new [LocalDriver]
30    pub fn new() -> Self {
31        Self
32    }
33}
34
35impl Default for LocalDriver {
36    /// Create a new [LocalDriver]
37    fn default() -> Self {
38        Self
39    }
40}
41
42/// [Driver] implementation for [LocalDriver]
43#[async_trait]
44impl Driver for LocalDriver {
45    type Handle = LocalHandle;
46
47    async fn run(&self, app: &str, opts: Options) -> anyhow::Result<Self::Handle> {
48        // Setup speculos command
49        let mut cmd = Command::new("speculos.py");
50
51        // Kill when object is dropped
52        let mut cmd = cmd.kill_on_drop(true);
53
54        // Bind stdout / stderr
55        // NOTE: for reasons unknown test harnesses don't overwrite stdout so much as hack the `print!` family of functions, so... this always produces a pile of output
56        // TODO: it'd be nice to route this via the captured log output were it one day possible to do so
57        cmd = cmd.stdout(Stdio::inherit()).stderr(Stdio::inherit());
58
59        // Setup speculos arguments
60        for a in opts.args() {
61            cmd = cmd.arg(a);
62        }
63
64        if let Some(root) = opts.root {
65            // Fetch existing path
66            let (_, path) = std::env::vars()
67                .find(|(k, _v)| k == "PATH")
68                .unwrap_or(("PATH".to_string(), "".to_string()));
69
70            cmd = cmd.env("PATH", format!("{path}:{root}"));
71        }
72
73        // Set application to execute
74        cmd = cmd.arg(app);
75
76        debug!("Command: {:?}", cmd);
77
78        // Launch speculos and return
79        let child = cmd.spawn()?;
80
81        let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), opts.http_port);
82        Ok(LocalHandle { child, addr })
83    }
84
85    async fn wait(&self, handle: &mut Self::Handle) -> anyhow::Result<()> {
86        let _status = handle.child.wait().await?;
87
88        // TODO: match on status / return errors
89
90        Ok(())
91    }
92
93    async fn exit(&self, mut handle: Self::Handle) -> anyhow::Result<()> {
94        handle.child.kill().await?;
95        Ok(())
96    }
97}
98
99#[async_trait]
100impl Handle for LocalHandle {
101    fn addr(&self) -> SocketAddr {
102        self.addr
103    }
104}