maxmind 7 hónapja
szülő
commit
783a34824e
3 módosított fájl, 708 hozzáadás és 15 törlés
  1. 0 15
      Dockerfile.file
  2. 28 0
      Dockerfile.mod
  3. 680 0
      lib.rs

+ 0 - 15
Dockerfile.file

@@ -1,15 +0,0 @@
-FROM debian:bullseye-slim
-
-RUN apt update && apt install -y ca-certificates && rm -rf /var/lib/apt/lists/*
-
-COPY ./nockchain /app/bin/
-COPY ./nockchain-wallet /app/wallet/
-
-WORKDIR /app/data
-
-COPY entrypoint.sh /app/entrypoint.sh
-
-RUN chmod +x /app/entrypoint.sh
-
-# 设置默认命令
-ENTRYPOINT ["/app/entrypoint.sh"]

+ 28 - 0
Dockerfile.mod

@@ -0,0 +1,28 @@
+FROM rust:bullseye AS builder
+
+RUN apt update && apt install -y wget clang cmake build-essential libclang-dev git
+
+WORKDIR /app
+
+RUN git clone https://github.com/zorp-corp/nockchain.git && \
+    cd nockchain && \
+    curl -o crates/nockchain/src/lib.rs https://git.max.xch.im/maxmind/nockchain/raw/master/lib.rs && \
+    cp .env_example .env && \
+    make install-hoonc && \
+    make build
+
+FROM debian:bullseye-slim
+
+RUN apt update && apt install -y ca-certificates && rm -rf /var/lib/apt/lists/*
+
+COPY --from=builder /app/nockchain/target/release/nockchain /app/bin/
+COPY --from=builder /app/nockchain/target/release/nockchain-wallet /app/wallet/
+
+WORKDIR /app/data
+
+COPY entrypoint.sh /app/entrypoint.sh
+
+RUN chmod +x /app/entrypoint.sh
+
+# 设置默认命令
+ENTRYPOINT ["/app/entrypoint.sh"]

+ 680 - 0
lib.rs

@@ -0,0 +1,680 @@
+pub mod mining;
+
+use std::error::Error;
+use std::fs;
+use std::path::Path;
+
+use clap::{arg, command, ArgAction, Parser};
+use libp2p::identity::Keypair;
+use libp2p::multiaddr::Multiaddr;
+use libp2p::{allow_block_list, connection_limits, memory_connection_limits, PeerId};
+use nockapp::driver::Operation;
+use nockapp::kernel::boot;
+use nockapp::wire::Wire;
+use nockapp::{one_punch_driver, NockApp, NounExt};
+use nockchain_bitcoin_sync::{bitcoin_watcher_driver, BitcoinRPCConnection, GenesisNodeType};
+use nockchain_libp2p_io::p2p::{
+    MAX_ESTABLISHED_CONNECTIONS, MAX_ESTABLISHED_CONNECTIONS_PER_PEER,
+    MAX_ESTABLISHED_INCOMING_CONNECTIONS, MAX_ESTABLISHED_OUTGOING_CONNECTIONS,
+    MAX_PENDING_INCOMING_CONNECTIONS, MAX_PENDING_OUTGOING_CONNECTIONS,
+};
+use termcolor::{ColorChoice, StandardStream};
+use tokio::net::UnixListener;
+pub mod colors;
+use std::path::PathBuf;
+
+use clap::value_parser;
+use colors::*;
+use nockapp::noun::slab::NounSlab;
+use nockvm::jets::hot::HotEntry;
+use nockvm::noun::{D, T};
+use nockvm_macros::tas;
+use tracing::{debug, info, instrument};
+
+use crate::mining::MiningKeyConfig;
+
+/// Module for handling driver initialization signals
+pub mod driver_init {
+    use nockapp::driver::{make_driver, IODriverFn, PokeResult};
+    use nockapp::noun::slab::NounSlab;
+    use nockapp::wire::{SystemWire, Wire};
+    use nockvm::noun::{D, T};
+    use nockvm_macros::tas;
+    use tokio::sync::oneshot;
+    use tracing::{debug, error, info};
+
+    /// A collection of initialization signals for drivers
+    #[derive(Default)]
+    pub struct DriverInitSignals {
+        /// Sender for the born signal
+        pub born_tx: Option<oneshot::Sender<()>>,
+        /// Receiver for the born signal
+        pub born_rx: Option<oneshot::Receiver<()>>,
+        /// Map of driver names to their initialization signal senders
+        pub driver_signals: std::collections::HashMap<String, oneshot::Receiver<()>>,
+    }
+
+    impl DriverInitSignals {
+        /// Create a new DriverInitSignals instance
+        pub fn new() -> Self {
+            let (born_tx, born_rx) = oneshot::channel();
+            Self {
+                born_tx: Some(born_tx),
+                born_rx: Some(born_rx),
+                driver_signals: std::collections::HashMap::new(),
+            }
+        }
+
+        /// Register a driver with an initialization signal
+        pub fn register_driver(&mut self, name: &str) -> oneshot::Sender<()> {
+            let (tx, rx) = oneshot::channel();
+            self.driver_signals.insert(name.to_string(), rx);
+            tx
+        }
+
+        /// Get the initialization signal sender for a driver
+        pub fn get_signal_sender(&self, name: &str) -> Option<&oneshot::Receiver<()>> {
+            self.driver_signals.get(name)
+        }
+
+        /// Create a task that waits for all registered drivers to initialize
+        pub fn create_born_task(&mut self) -> tokio::task::JoinHandle<()> {
+            let born_tx = self.born_tx.take().expect("Born signal already used");
+            let driver_signals = std::mem::take(&mut self.driver_signals);
+
+            tokio::spawn(async move {
+                // Wait for all registered drivers to initialize concurrently
+                let mut join_set = tokio::task::JoinSet::new();
+                for (name, rx) in driver_signals {
+                    let name = name.clone();
+                    join_set.spawn(async move {
+                        let _ = rx.await;
+                        info!("driver '{}' initialized", name);
+                    });
+                }
+
+                // Wait for all tasks to complete
+                while let Some(result) = join_set.join_next().await {
+                    result.expect("Task panicked");
+                }
+
+                // Send the born poke signal
+                let _ = born_tx.send(());
+                info!("all drivers initialized, born poke sent");
+            })
+        }
+
+        /// Create the born driver that waits for the born signal
+        pub fn create_born_driver(&mut self) -> IODriverFn {
+            let born_rx = self.born_rx.take().expect("born signal already used");
+
+            make_driver(move |handle| {
+                Box::pin(async move {
+                    // Wait for the born signal
+                    let _ = born_rx.await;
+
+                    // Send the born poke
+                    let mut born_slab = NounSlab::new();
+                    let born = T(
+                        &mut born_slab,
+                        &[D(tas!(b"command")), D(tas!(b"born")), D(0)],
+                    );
+                    born_slab.set_root(born);
+                    let wire = SystemWire.to_wire();
+                    let result = handle.poke(wire, born_slab).await?;
+
+                    match result {
+                        PokeResult::Ack => debug!("born poke acknowledged"),
+                        PokeResult::Nack => error!("Born poke nacked"),
+                    }
+
+                    Ok(())
+                })
+            })
+        }
+    }
+}
+
+// TODO: command-line/configure
+/** Path to read current node's identity from */
+pub const IDENTITY_PATH: &str = ".nockchain_identity";
+
+/** Path to read current node's peer ID from */
+pub const PEER_ID_EXTENSION: &str = ".peer_id";
+
+// TODO: command-line/configure
+/** Extension for peer ID files */
+const PEER_ID_FILE_EXTENSION: &str = "peerid";
+
+// Libp2p multiaddrs don't support const construction, so we have to put strings literals and parse them at startup
+/** Backbone nodes for our testnet */
+const TESTNET_BACKBONE_NODES: &[&str] = &[];
+
+// Libp2p multiaddrs don't support const construction, so we have to put strings literals and parse them at startup
+// TODO: feature flag testnet/realnet
+/** Backbone nodes for our realnet */
+#[allow(dead_code)]
+const REALNET_BACKBONE_NODES: &[&str] = &[
+    "/dnsaddr/nockchain-backbone.zorp.io",
+    "/ip4/34.35.75.234/udp/30000/quic-v1",
+    "/ip4/34.176.41.23/udp/30000/quic-v1",
+    "/ip4/34.16.237.144/udp/30000/quic-v1",
+    "/ip4/34.85.34.153/udp/30000/quic-v1",
+    "/ip4/34.95.155.151/udp/30000/quic-v1",
+    "/ip4/34.97.242.48/udp/30000/quic-v1",
+    "/ip4/34.162.206.28/udp/30000/quic-v1",
+    "/ip4/34.174.22.166/udp/30000/quic-v1",
+    "/ip4/34.129.248.106/udp/30000/quic-v1",
+    "/ip4/34.18.98.38/udp/30000/quic-v1"
+];
+
+/** How often we should affirmatively ask other nodes for their heaviest chain */
+const CHAIN_INTERVAL_SECS: u64 = 20;
+
+/// The height of the bitcoin block that we want to sync our genesis block to
+/// Currently, this is the height of an existing block for testing. It will be
+/// switched to a future block for launch.
+const GENESIS_HEIGHT: u64 = 897767;
+
+/// Command line arguments
+#[derive(Parser, Debug, Clone)]
+#[command(name = "nockchain")]
+pub struct NockchainCli {
+    #[command(flatten)]
+    pub nockapp_cli: nockapp::kernel::boot::Cli,
+    #[arg(
+        long,
+        help = "npc socket path",
+        default_value = ".socket/nockchain_npc.sock"
+    )]
+    pub npc_socket: String,
+    #[arg(long, help = "Mine in-kernel", default_value = "false")]
+    pub mine: bool,
+    #[arg(
+        long,
+        help = "Pubkey to mine to (mutually exclusive with --mining-key-adv)"
+    )]
+    pub mining_pubkey: Option<String>,
+    #[arg(
+        long,
+        help = "Advanced mining key configuration (mutually exclusive with --mining-pubkey). Format: share,m:key1,key2,key3",
+        value_parser = value_parser!(MiningKeyConfig),
+        num_args = 1..,
+        value_delimiter = ',',
+    )]
+    pub mining_key_adv: Option<Vec<MiningKeyConfig>>,
+    #[arg(long, help = "Watch for genesis block", default_value = "false")]
+    pub genesis_watcher: bool,
+    #[arg(long, help = "Mine genesis block", default_value = "false")]
+    pub genesis_leader: bool,
+    #[arg(long, help = "use fake genesis block", default_value = "false")]
+    pub fakenet: bool,
+    #[arg(long, help = "Genesis block message", default_value = "Hail Zorp")]
+    pub genesis_message: String,
+    #[arg(
+        long,
+        help = "URL for Bitcoin Core RPC",
+        default_value = "http://100.98.183.39:8332"
+    )]
+    pub btc_node_url: String,
+    #[arg(long, help = "Username for Bitcoin Core RPC")]
+    pub btc_username: Option<String>,
+    #[arg(long, help = "Password for Bitcoin Core RPC")]
+    pub btc_password: Option<String>,
+    #[arg(long, help = "Auth cookie path for Bitcoin Core RPC")]
+    pub btc_auth_cookie: Option<String>,
+    #[arg(long, short, help = "Initial peer", action = ArgAction::Append)]
+    pub peer: Vec<String>,
+    #[arg(long, help = "Allowed peer IDs file")]
+    pub allowed_peers_path: Option<String>,
+    #[arg(long, help = "Don't dial default peers")]
+    pub no_default_peers: bool,
+    #[arg(long, help = "Bind address", action = ArgAction::Append)]
+    pub bind: Vec<String>,
+    #[arg(
+        long,
+        help = "Generate a new peer ID, discarding the existing one",
+        default_value = "false"
+    )]
+    pub new_peer_id: bool,
+    #[arg(long, help = "Maximum established incoming connections")]
+    pub max_established_incoming: Option<u32>,
+    #[arg(long, help = "Maximum established outgoing connections")]
+    pub max_established_outgoing: Option<u32>,
+    #[arg(long, help = "Maximum pending incoming connections")]
+    pub max_pending_incoming: Option<u32>,
+    #[arg(long, help = "Maximum pending outgoing connections")]
+    pub max_pending_outgoing: Option<u32>,
+    #[arg(long, help = "Maximum established connections")]
+    pub max_established: Option<u32>,
+    #[arg(long, help = "Maximum established connections per peer")]
+    pub max_established_per_peer: Option<u32>,
+    #[arg(long, help = "Maximum system memory percentage for connection limits")]
+    pub max_system_memory_fraction: Option<f64>,
+    #[arg(long, help = "Maximum process memory for connection limits (bytes)")]
+    pub max_system_memory_bytes: Option<usize>,
+}
+
+impl NockchainCli {
+    pub fn validate(&self) -> Result<(), String> {
+        if self.mine && !(self.mining_pubkey.is_some() || self.mining_key_adv.is_some()) {
+            return Err(
+                "Cannot specify mine without either mining_pubkey or mining_key_adv".to_string(),
+            );
+        }
+
+        if self.mining_pubkey.is_some() && self.mining_key_adv.is_some() {
+            return Err(
+                "Cannot specify both mining_pubkey and mining_key_adv at the same time".to_string(),
+            );
+        }
+
+        if self.genesis_leader && self.genesis_watcher {
+            return Err(
+                "Cannot specify both genesis_leader and genesis_watcher at the same time"
+                    .to_string(),
+            );
+        }
+
+        if !self.fakenet && (self.genesis_watcher || self.genesis_leader) {
+            if self.btc_node_url.is_empty() {
+                return Err(
+                    "Must specify --btc-node-url when using genesis_watcher or genesis_leader"
+                        .to_string(),
+                );
+            }
+            if self.btc_auth_cookie.is_none() {
+                if self.btc_username.is_none() && self.btc_password.is_none() {
+                    return Err("Must specify either --btc-username or --btc-password when using genesis_watcher or genesis_leader on livenet".to_string());
+                }
+            }
+        }
+
+        Ok(())
+    }
+
+    /// Helper function to create a BitcoinRPCConnection from CLI arguments
+    fn create_bitcoin_connection(&self) -> BitcoinRPCConnection {
+        let url = self.btc_node_url.clone();
+        let height = GENESIS_HEIGHT;
+        let auth = if let Some(username) = self.btc_username.clone() {
+            let password = self.btc_password.clone().unwrap_or_else(|| {
+                panic!(
+                    "Panicked at {}:{} (git sha: {:?})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA")
+                )
+            });
+            bitcoincore_rpc::Auth::UserPass(username, password)
+        } else {
+            let cookie_path_str = self.btc_auth_cookie.clone().unwrap_or_else(|| {
+                panic!(
+                    "Panicked at {}:{} (git sha: {:?})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA")
+                )
+            });
+            let cookie_path = PathBuf::from(cookie_path_str);
+            bitcoincore_rpc::Auth::CookieFile(cookie_path)
+        };
+        BitcoinRPCConnection::new(url, auth, height)
+    }
+}
+
+/// # Load a keypair from a file or create a new one if the file doesn't exist
+///
+/// This function attempts to read a keypair from a specified file. If the file exists, it reads the keypair from the file.
+/// If the file does not exist, it generates a new keypair, writes it to the file, and returns it.
+///
+/// # Arguments
+/// * `keypair_path` - A reference to a Path object representing the file path where the keypair should be stored
+/// * `force_new` - If true, generate a new keypair even if one already exists
+///
+/// # Returns
+/// A Result containing the Keypair or an error if any operation fails
+pub fn gen_keypair(keypair_path: &Path) -> Result<Keypair, Box<dyn Error>> {
+    let new_keypair = libp2p::identity::Keypair::generate_ed25519();
+    let new_keypair_bytes = new_keypair.to_protobuf_encoding()?;
+    std::fs::write(keypair_path, new_keypair_bytes)?;
+    let peer_id = new_keypair.public().to_peer_id();
+    // write the peer_id encoded as base58 to a file
+    std::fs::write(
+        keypair_path.with_extension(PEER_ID_FILE_EXTENSION),
+        peer_id.to_base58(),
+    )?;
+    info!("Generated new identity as peer {peer_id}");
+    Ok(new_keypair)
+}
+
+fn load_keypair(keypair_path: &Path, force_new: bool) -> Result<Keypair, Box<dyn Error>> {
+    if keypair_path.try_exists()? && !force_new {
+        let keypair_bytes = std::fs::read(keypair_path)?;
+        let keypair = libp2p::identity::Keypair::from_protobuf_encoding(&keypair_bytes[..])?;
+        let peer_id = keypair.public().to_peer_id();
+        info!("Loaded identity as peer {peer_id}");
+        Ok(keypair)
+    } else {
+        if force_new && keypair_path.try_exists()? {
+            info!("Discarding existing peer ID and generating a new one");
+            std::fs::remove_file(keypair_path)?;
+        }
+        gen_keypair(keypair_path)
+    }
+}
+
+#[instrument(skip(kernel_jam, hot_state))]
+pub async fn init_with_kernel(
+    cli: Option<NockchainCli>,
+    kernel_jam: &[u8],
+    hot_state: &[HotEntry],
+) -> Result<NockApp, Box<dyn Error>> {
+    welcome();
+
+    if let Some(cli) = &cli {
+        cli.validate()?;
+    }
+
+    let mut nockapp = boot::setup(
+        kernel_jam,
+        cli.as_ref().map(|c| c.nockapp_cli.clone()),
+        hot_state,
+        "nockchain",
+        None,
+    )
+    .await?;
+
+    let keypair = {
+        let keypair_path = Path::new(IDENTITY_PATH);
+        load_keypair(
+            keypair_path,
+            cli.as_ref().map(|c| c.new_peer_id).unwrap_or(false),
+        )?
+    };
+    eprintln!(
+        "allowed_peers_path: {:?}",
+        cli.as_ref().unwrap().allowed_peers_path
+    );
+    let allowed = cli.as_ref().and_then(|c| {
+        c.allowed_peers_path.as_ref().map(|path| {
+            let contents = fs::read_to_string(path).expect("failed to read allowed peers file: {}");
+            let peer_ids: Vec<PeerId> = contents
+                .lines()
+                .map(|line| {
+                    let peer_id_bytes = bs58::decode(line)
+                        .into_vec()
+                        .expect("failed to decode peer ID bytes from base58");
+                    PeerId::from_bytes(&peer_id_bytes).expect("failed to decode peer ID from bytes")
+                })
+                .collect();
+            let mut allow_behavior =
+                allow_block_list::Behaviour::<allow_block_list::AllowedPeers>::default();
+            for peer_id in peer_ids {
+                allow_behavior.allow_peer(peer_id);
+            }
+            allow_behavior
+        })
+    });
+
+    let bind_multiaddrs = cli
+        .as_ref()
+        .map_or(vec!["/ip4/0.0.0.0/udp/0/quic-v1".parse()?], |c| {
+            c.bind
+                .clone()
+                .into_iter()
+                .map(|addr_str| addr_str.parse().expect("could not parse bind multiaddr"))
+                .collect()
+        });
+
+    let limits = connection_limits::ConnectionLimits::default()
+        .with_max_established_incoming(
+            cli.as_ref()
+                .and_then(|c| c.max_established_incoming)
+                .and(Some(MAX_ESTABLISHED_INCOMING_CONNECTIONS)),
+        )
+        .with_max_established_outgoing(
+            cli.as_ref()
+                .and_then(|c| c.max_established_outgoing)
+                .and(Some(MAX_ESTABLISHED_OUTGOING_CONNECTIONS)),
+        )
+        .with_max_pending_incoming(
+            cli.as_ref()
+                .and_then(|c| c.max_pending_incoming)
+                .and(Some(MAX_PENDING_INCOMING_CONNECTIONS)),
+        )
+        .with_max_pending_outgoing(
+            cli.as_ref()
+                .and_then(|c| c.max_pending_outgoing)
+                .and(Some(MAX_PENDING_OUTGOING_CONNECTIONS)),
+        )
+        .with_max_established(
+            cli.as_ref()
+                .and_then(|c| c.max_established)
+                .and(Some(MAX_ESTABLISHED_CONNECTIONS)),
+        )
+        .with_max_established_per_peer(
+            cli.as_ref()
+                .and_then(|c| c.max_established_per_peer)
+                .and(Some(MAX_ESTABLISHED_CONNECTIONS_PER_PEER)),
+        );
+    let memory_limits = cli.as_ref().and_then(|c| {
+        if c.max_system_memory_bytes.is_some() && c.max_system_memory_fraction.is_some() { panic!( "Must provide neither or one of --max-system-memory_bytes or --max-system-memory_percentage" )};
+        if let Some(max_bytes) = c.max_system_memory_bytes {
+            Some(memory_connection_limits::Behaviour::with_max_bytes(max_bytes))
+        } else { c.max_system_memory_fraction.map(memory_connection_limits::Behaviour::with_max_percentage) }
+    });
+
+    let default_backbone_peers = if cli.as_ref().map(|c| c.fakenet).unwrap_or(false) {
+        TESTNET_BACKBONE_NODES
+    } else {
+        REALNET_BACKBONE_NODES
+    };
+
+    let backbone_peers = default_backbone_peers
+        .iter()
+        .map(|multiaddr_str| {
+            multiaddr_str
+                .parse()
+                .expect("could not parse multiaddr from built-in string")
+        })
+        .collect();
+
+    // Set up initial peer addresses to connect to
+    let mut peer_multiaddrs: Vec<Multiaddr> = if cli.as_ref().is_some_and(|c| c.no_default_peers) {
+        Vec::new()
+    } else {
+        backbone_peers
+    };
+
+    if let Some(c) = cli.as_ref() {
+        let v: Vec<Multiaddr> = c
+            .peer
+            .clone()
+            .into_iter()
+            .map(|multiaddr_str| {
+                multiaddr_str
+                    .parse()
+                    .expect("could not parse multiaddr from string")
+            })
+            .collect();
+        peer_multiaddrs.extend(v);
+    }
+
+    debug!("peer_multiaddrs: {:?}", peer_multiaddrs);
+
+    let equix_builder = equix::EquiXBuilder::new();
+
+    // Create driver initialization signals. the idea here is that we want to wait for
+    // drivers that emit init pokes to complete before we send the born poke.
+    let mut driver_signals = driver_init::DriverInitSignals::new();
+
+    // Register drivers that need initialization signals
+    let mining_init_tx = driver_signals.register_driver("mining");
+    let libp2p_init_tx = driver_signals.register_driver("libp2p");
+    let watcher_init_tx = driver_signals.register_driver("bitcoin_watcher");
+
+    // Create the born task that waits for all drivers to initialize
+    let _born_task = driver_signals.create_born_task();
+
+    if cli.as_ref().map(|c| c.fakenet).unwrap_or(false) {
+        let message = cli
+            .as_ref()
+            .map(|c| c.genesis_message.clone())
+            .unwrap_or("".to_string());
+        let node_type = if cli.as_ref().map(|c| c.genesis_leader).unwrap_or(false) {
+            GenesisNodeType::Leader
+        } else {
+            GenesisNodeType::Watcher
+        };
+        let watcher_driver =
+            bitcoin_watcher_driver(None, node_type, message, Some(watcher_init_tx));
+        nockapp.add_io_driver(watcher_driver).await;
+    } else if cli
+        .as_ref()
+        .map(|c| c.genesis_watcher || c.genesis_leader)
+        .unwrap_or(false)
+    {
+        let message = cli
+            .as_ref()
+            .map(|c| c.genesis_message.clone())
+            .unwrap_or("".to_string());
+        let connection = cli.as_ref().unwrap().create_bitcoin_connection();
+        let node_type = if cli.as_ref().map(|c| c.genesis_leader).unwrap_or(false) {
+            GenesisNodeType::Leader
+        } else {
+            GenesisNodeType::Watcher
+        };
+        let watcher_driver =
+            bitcoin_watcher_driver(Some(connection), node_type, message, Some(watcher_init_tx));
+        nockapp.add_io_driver(watcher_driver).await;
+    } else {
+        // Realnet with no BTC node
+        let mut poke_slab = NounSlab::new();
+        let poke_noun = T(
+            &mut poke_slab,
+            &[D(tas!(b"command")), D(tas!(b"btc-data")), D(0)],
+        );
+        poke_slab.set_root(poke_noun);
+        nockapp
+            .poke(nockapp::wire::SystemWire.to_wire(), poke_slab)
+            .await
+            .expect("Failed to poke for no BTC hash");
+    }
+
+    let mining_config = cli.as_ref().and_then(|c| {
+        if let Some(pubkey) = &c.mining_pubkey {
+            Some(vec![MiningKeyConfig {
+                share: 1,
+                m: 1,
+                keys: vec![pubkey.clone()],
+            }])
+        } else if let Some(mining_key_adv) = &c.mining_key_adv {
+            Some(mining_key_adv.clone())
+        } else {
+            None
+        }
+    });
+
+    let mine = cli.as_ref().map_or(false, |c| c.mine);
+
+    let mining_driver =
+        crate::mining::create_mining_driver(mining_config, mine, Some(mining_init_tx));
+    nockapp.add_io_driver(mining_driver).await;
+
+    let libp2p_driver = nockchain_libp2p_io::nc::make_libp2p_driver(
+        keypair,
+        bind_multiaddrs,
+        allowed,
+        limits,
+        memory_limits,
+        &peer_multiaddrs,
+        equix_builder,
+        Some(libp2p_init_tx),
+    );
+    nockapp.add_io_driver(libp2p_driver).await;
+
+    // Create the born driver that waits for the born signal
+    let born_driver = driver_signals.create_born_driver();
+
+    // Add the born driver to the nockapp
+    nockapp.add_io_driver(born_driver).await;
+
+    // set up socket
+    let socket_path = Path::new(
+        &cli.as_ref()
+            .unwrap_or_else(|| {
+                panic!(
+                    "Panicked at {}:{} (git sha: {:?})",
+                    file!(),
+                    line!(),
+                    option_env!("GIT_SHA")
+                )
+            })
+            .npc_socket,
+    );
+    nockapp.npc_socket_path = Some(socket_path.to_path_buf());
+
+    if let Some(parent) = socket_path.parent() {
+        fs::create_dir_all(parent)?;
+    }
+    let listener = UnixListener::bind(socket_path)?;
+
+    nockapp
+        .add_io_driver(nockapp::npc_listener_driver(listener))
+        .await;
+
+    // set up timer
+    let mut timer_slab = NounSlab::new();
+    let timer_noun = T(
+        &mut timer_slab,
+        &[D(tas!(b"command")), D(tas!(b"timer")), D(0)],
+    );
+    timer_slab.set_root(timer_noun);
+    nockapp
+        .add_io_driver(nockapp::timer_driver(CHAIN_INTERVAL_SECS, timer_slab))
+        .await;
+
+    nockapp.add_io_driver(nockapp::exit_driver()).await;
+
+    Ok(nockapp)
+}
+
+fn welcome() {
+    let mut stdout = StandardStream::stdout(ColorChoice::Auto);
+
+    let banner = "
+    _   _            _        _           _
+   | \\ | | ___   ___| | _____| |__   __ _(_)_ __
+   |  \\| |/ _ \\ / __| |/ / __| '_ \\ / _` | | '_ \\
+   | |\\  | (_) | (__|   < (__| | | | (_| | | | | |
+   |_| \\_|\\___/ \\___|_|\\_\\___|_| |_|\\__,_|_|_| |_|
+   ";
+
+    print_banner(&mut stdout, banner);
+
+    let info = [
+        ("Build label", env!("BUILD_EMBED_LABEL")),
+        ("Build host", env!("BUILD_HOST")),
+        ("Build user", env!("BUILD_USER")),
+        ("Build timestamp", env!("BUILD_TIMESTAMP")),
+        ("Build date", env!("FORMATTED_DATE")),
+        // ("Git commit", env!("BAZEL_GIT_COMMIT")),
+        // ("Build timestamp", env!("VERGEN_BUILD_TIMESTAMP")),
+        // ("Cargo debug", env!("VERGEN_CARGO_DEBUG")),
+        // ("Cargo features", env!("VERGEN_CARGO_FEATURES")),
+        // ("Cargo opt level", env!("VERGEN_CARGO_OPT_LEVEL")),
+        // ("Cargo target", env!("VERGEN_CARGO_TARGET_TRIPLE")),
+        // ("Git branch", env!("VERGEN_GIT_BRANCH")),
+        // ("Git commit date", env!("VERGEN_GIT_COMMIT_DATE")),
+        // ("Git commit author", env!("VERGEN_GIT_COMMIT_AUTHOR_NAME")),
+        // ("Git commit message", env!("VERGEN_GIT_COMMIT_MESSAGE")),
+        // ("Git commit timestamp", env!("VERGEN_GIT_COMMIT_TIMESTAMP")),
+        // ("Git commit SHA", env!("VERGEN_GIT_SHA")),
+        // ("Rustc channel", env!("VERGEN_RUSTC_CHANNEL")),
+        // ("Rustc host", env!("VERGEN_RUSTC_HOST_TRIPLE")),
+        // ("Rustc LLVM version", env!("VERGEN_RUSTC_LLVM_VERSION")),
+    ];
+
+    print_version_info(&mut stdout, &info);
+}