Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/workflows/regression.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ jobs:
uses: actions/checkout@v4

- name: Install repository Rust toolchain
run: rustup toolchain install nightly-2025-02-14 --profile minimal --target thumbv7em-none-eabihf --target wasm32-unknown-unknown
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
target: thumbv7em-none-eabihf,wasm32-unknown-unknown
rustflags: ""

- name: Compile the production no-std library
run: cargo check -p tx-types --no-default-features --lib --target thumbv7em-none-eabihf
Expand Down
81 changes: 32 additions & 49 deletions BUILDING.md
Original file line number Diff line number Diff line change
@@ -1,74 +1,57 @@
# Building tx-types

This document explains how to build the tx-types library with different dependency configurations.
`tx-types` can be built against two different Nockchain runtime sources:

## Default Build (Git Dependencies)
- `embedded` (default): the vendored `nockchain-minimal` crates in this
repository. Use this for WASM, embedded targets, and offline builds.
- `native`: upstream `SWPSCO/nockchain` at the pinned revision. Use this for
server-side native builds that already depend on the full runtime.

By default, the project uses nockchain dependencies from the git repository:
Both dependency sets are declared in `tx-types/Cargo.toml` and aliased onto the
canonical crate names in `tx-types/src/lib.rs`, so the same source compiles
against either one. No `[patch]` section is needed.

```bash
cargo build
cargo test
```
## Feature matrix

This configuration matches the main branch and is suitable for most development work.
| Consumer | Features |
| --- | --- |
| ESP32 / no_std (crypto only) | `default-features = false` |
| WASM wallet (`wasm32-unknown-unknown`) | `default-features = false, features = ["std", "embedded"]` |
| Native server | `default-features = false, features = ["std", "native"]` |

## WASM Compilation (Local Dependencies)
`embedded` and `native` are mutually exclusive, and `std` requires exactly one
of them. Enabling both, or enabling `std` with neither, produces a
`compile_error!`.

For WASM compilation, you need to use local path dependencies from the `nockchain-minimal` submodule. This is required because WASM targets need access to the full source code.
`pokenoun` is re-exported in every configuration, including the crypto-only
no_std build.

### Switching to Local Dependencies
## Examples

Run the provided script:
Default (embedded + std):

```bash
./use-local-nockchain.sh
cargo build -p tx-types
cargo test -p tx-types --lib
```

This will uncomment the `[patch]` section in `Cargo.toml` to override git dependencies with local paths.

After switching, clean and rebuild:
WASM (embedded):

```bash
cargo clean
cargo build
cargo build -p tx-types \
--no-default-features --features std,embedded \
--target wasm32-unknown-unknown
```

### Switching Back to Git Dependencies

To switch back to git dependencies:
Native upstream runtime:

```bash
./use-git-nockchain.sh
cargo build -p tx-types --no-default-features --features std,native
```

Then clean and rebuild:
Crypto-only no_std:

```bash
cargo clean
cargo build
cargo build -p tx-types --no-default-features
cargo build -p tx-types --no-default-features --target thumbv7em-none-eabihf
```

## How It Works

The `Cargo.toml` file is configured with:

1. **Default dependencies**: Git references to the nockchain repository
```toml
noun-serde = { git = "https://github.com/SWPSCO/nockchain.git", rev = "..." }
```

2. **Patch section (commented by default)**: Local path overrides
```toml
# [patch."https://github.com/SWPSCO/nockchain.git"]
# noun-serde = { path = "./nockchain-minimal/crates/noun-serde" }
```

When the patch section is uncommented, Cargo will use the local paths instead of fetching from git. This allows the same codebase to work in both configurations.

## Why Two Configurations?

- **Git dependencies**: Standard Rust practice, compatible with main branch, easier dependency management
- **Local dependencies**: Required for WASM compilation, allows local modifications to nockchain during development

This approach avoids having separate branches for different build configurations.
26 changes: 15 additions & 11 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,20 @@ edition = "2021"
version = "0.1.0"

[workspace.dependencies]
# Nockchain crates supplied by this repository
noun-serde = { path = "./nockchain-minimal/crates/noun-serde" }
noun-serde-derive = { path = "./nockchain-minimal/crates/noun-serde-derive" }
nockapp = { path = "./nockchain-minimal/crates/nockapp", default-features = false }
nockvm = { path = "./nockchain-minimal/crates/nockvm/rust/nockvm" }
nockchain-libp2p-io = { path = "./nockchain-minimal/crates/nockchain-libp2p-io", default-features = false }
nockchain-math = { path = "./nockchain-minimal/crates/nockchain-math" }
zkvm-jetpack = { path = "./nockchain-minimal/crates/zkvm-jetpack" }
ibig = { path = "./nockchain-minimal/crates/nockvm/rust/ibig", default-features = false }
murmur3 = { path = "./nockchain-minimal/crates/nockvm/rust/murmur3" }
nockvm_macros = { path = "./nockchain-minimal/crates/nockvm/rust/nockvm_macros" }
# Upstream Nockchain crates. tx-types itself declares both the upstream and
# vendored (nockchain-minimal) dependency sets and selects between them with
# the `native` / `embedded` features; these workspace entries remain for
# other workspace members and for reference.
noun-serde = { git = "https://github.com/SWPSCO/nockchain.git", rev = "73877be29bd1fafeeade1be7b95aebb0f64d8382" }
noun-serde-derive = { git = "https://github.com/SWPSCO/nockchain.git", rev = "73877be29bd1fafeeade1be7b95aebb0f64d8382" }
nockapp = { git = "https://github.com/SWPSCO/nockchain.git", rev = "73877be29bd1fafeeade1be7b95aebb0f64d8382", default-features = false }
nockvm = { git = "https://github.com/SWPSCO/nockchain.git", rev = "73877be29bd1fafeeade1be7b95aebb0f64d8382" }
nockchain-libp2p-io = { git = "https://github.com/SWPSCO/nockchain.git", rev = "73877be29bd1fafeeade1be7b95aebb0f64d8382", default-features = false }
nockchain-math = { git = "https://github.com/SWPSCO/nockchain.git", rev = "73877be29bd1fafeeade1be7b95aebb0f64d8382" }
zkvm-jetpack = { git = "https://github.com/SWPSCO/nockchain.git", rev = "73877be29bd1fafeeade1be7b95aebb0f64d8382" }
ibig = { git = "https://github.com/SWPSCO/nockchain.git", rev = "73877be29bd1fafeeade1be7b95aebb0f64d8382", default-features = false }
murmur3 = { git = "https://github.com/SWPSCO/nockchain.git", rev = "73877be29bd1fafeeade1be7b95aebb0f64d8382" }
nockvm_macros = { git = "https://github.com/SWPSCO/nockchain.git", rev = "73877be29bd1fafeeade1be7b95aebb0f64d8382" }

# Standard dependencies (matching nockchain-minimal workspace)
aes = { version = "0.8.3", default-features = false }
Expand Down Expand Up @@ -82,3 +85,4 @@ pokenoun = { path = "./pokenoun" }
tracy-client-sys = { path = "./dummy-tracy/tracy-client-sys" }
tracy-client = { path = "./dummy-tracy/tracy-client" }
tracing-tracy = { path = "./dummy-tracy/tracing-tracy" }

2 changes: 1 addition & 1 deletion nockchain-minimal/crates/nockapp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ pub mod noun;
pub mod utils;

pub use bytes::*;
pub use nockvm::noun::Noun;
pub use nockvm::noun::{Noun, NounAllocator, NounSpace};
pub use noun::{AtomExt, JammedNoun, NounExt};
pub use utils::bytes::{ToBytes, ToBytesExt};
pub use utils::error::{CrownError, NockAppError, Result};
74 changes: 72 additions & 2 deletions nockchain-minimal/crates/nockapp/src/noun/extensions.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,86 @@
use core::str;
use std::iter::Iterator;
use std::ptr::copy_nonoverlapping;

use bincode::{Decode, Encode};
use bytes::Bytes;
use either::Either;
use intmap::IntMap;
use nockvm::interpreter::Error;
use nockvm::mem::NockStack;
use nockvm::noun::{Atom, IndirectAtom, NounAllocator, D};
use nockvm::noun::{Atom, Cell, IndirectAtom, NounAllocator, NounSpace, D};
use nockvm::serialization::{cue, jam};

use crate::noun::slab::NounSlab;
use crate::{Noun, Result, ToBytes, ToBytesExt};

const CACHED_MUG_METADATA_MASK: u64 = 0x7fff_ffff;

/// Copy a foreign noun into an allocator using the upstream space-aware
/// signature. The minimal runtime is single-arena, so `space` is ignored.
pub trait NounAllocatorExt {
fn copy_into(&mut self, noun: Noun, space: &NounSpace) -> Noun;
}

impl<A: NounAllocator> NounAllocatorExt for A {
fn copy_into(&mut self, noun: Noun, _space: &NounSpace) -> Noun {
let mut copied: IntMap<u64, Noun> = IntMap::new();
let mut stack = Vec::with_capacity(32);
let mut res = D(0);
stack.push((noun, &mut res as *mut Noun));
while let Some((noun, dest)) = stack.pop() {
match noun.as_either_direct_allocated() {
Either::Left(direct) => unsafe {
*dest = direct.as_noun();
},
Either::Right(allocated) => match allocated.as_either() {
Either::Left(indirect) => {
let source = unsafe { indirect.to_raw_pointer() };
let source_key = source as u64;
if let Some(copied_noun) = copied.get(source_key) {
unsafe { *dest = *copied_noun };
continue;
}
let copied_mem = unsafe { self.alloc_indirect(indirect.size()) };
unsafe {
copy_nonoverlapping(source, copied_mem, indirect.raw_size());
*copied_mem &= CACHED_MUG_METADATA_MASK;
}
let copied_noun = unsafe {
IndirectAtom::from_raw_pointer(copied_mem)
.as_atom()
.as_noun()
};
copied.insert(source_key, copied_noun);
unsafe { *dest = copied_noun };
}
Either::Right(cell) => {
let source = unsafe { cell.to_raw_pointer() };
let source_key = source as u64;
if let Some(copied_noun) = copied.get(source_key) {
unsafe { *dest = *copied_noun };
continue;
}
let copied_mem = unsafe { self.alloc_cell() };
unsafe {
copy_nonoverlapping(source, copied_mem, 1);
(*copied_mem).metadata &= CACHED_MUG_METADATA_MASK;
}
let copied_noun = unsafe { Cell::from_raw_pointer(copied_mem).as_noun() };
copied.insert(source_key, copied_noun);
unsafe { *dest = copied_noun };
unsafe {
stack.push((cell.tail(), &mut (*copied_mem).tail));
stack.push((cell.head(), &mut (*copied_mem).head));
}
}
},
}
}
res
}
}

pub trait NounExt {
fn cue_bytes(stack: &mut NockStack, bytes: &Bytes) -> std::result::Result<Noun, Error>;
fn cue_bytes_slice(stack: &mut NockStack, bytes: &[u8]) -> std::result::Result<Noun, Error>;
Expand Down Expand Up @@ -244,7 +314,7 @@ impl AsSlabVec for Noun {
let mut slab_vec = Vec::new();
for noun in noun_list.list_iter() {
let mut new_slab = NounSlab::new();
new_slab.copy_into(noun);
new_slab.copy_into(noun, &NounSpace::empty());
slab_vec.push(new_slab);
}
slab_vec
Expand Down
28 changes: 18 additions & 10 deletions nockchain-minimal/crates/nockapp/src/noun/slab.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ use either::Either;
use intmap::IntMap;
use nockvm::mem::NockStack;
use nockvm::mug::{calc_atom_mug_u32, calc_cell_mug_u32, get_mug, set_mug};
use nockvm::noun::{Atom, Cell, CellMemory, DirectAtom, IndirectAtom, Noun, NounAllocator, D};
use nockvm::noun::{
Atom, Cell, CellMemory, DirectAtom, IndirectAtom, Noun, NounAllocator, NounSpace, D,
};
use nockvm::serialization::{met0_u64_to_usize, met0_usize};
use thiserror::Error;

Expand Down Expand Up @@ -71,7 +73,7 @@ impl<J> NounSlab<J> {
.list_iter()
.map(|n| {
let mut slab = Self::new();
slab.copy_into(n);
slab.copy_into(n, &NounSpace::empty());
slab
})
.collect()
Expand All @@ -93,9 +95,10 @@ impl<J> NounSlab<J> {
f: F,
imports: (Noun, Noun, Noun),
) {
self.copy_into(imports.0);
self.copy_into(imports.1);
self.copy_into(imports.2);
let space = NounSpace::empty();
self.copy_into(imports.0, &space);
self.copy_into(imports.1, &space);
self.copy_into(imports.2, &space);
let new_root_base = f(imports, self.root);
let new_root = nockvm::noun::T(self, &new_root_base);
self.set_root(new_root);
Expand All @@ -105,7 +108,8 @@ impl<J> NounSlab<J> {
impl<J> Clone for NounSlab<J> {
fn clone(&self) -> Self {
let mut slab = Self::new();
slab.copy_into(self.root);
let space = self.noun_space();
slab.copy_into(self.root, &space);
slab
}
}
Expand Down Expand Up @@ -218,7 +222,7 @@ impl<J> Default for NounSlab<J> {
impl<J> From<Noun> for NounSlab<J> {
fn from(noun: Noun) -> Self {
let mut slab = Self::new();
slab.copy_into(noun);
slab.copy_into(noun, &NounSpace::empty());
slab
}
}
Expand Down Expand Up @@ -251,12 +255,16 @@ impl<J> NounSlab<J> {

/// Copy the root from another slab into this slab, set this slab's root to the copied root
pub fn copy_from_slab(&mut self, other: &NounSlab) {
self.copy_into(other.root);
let space = other.noun_space();
self.copy_into(other.root, &space);
}

/// Copy a noun into this slab, only leaving references into the PMA. Set that noun as the root
/// noun.
pub fn copy_into(&mut self, copy_root: Noun) -> Noun {
///
/// The minimal runtime is single-arena, so `space` is accepted for API
/// compatibility with upstream and ignored.
pub fn copy_into(&mut self, copy_root: Noun, _space: &NounSpace) -> Noun {
let mut copied: IntMap<u64, Noun> = IntMap::new();
// let mut copy_stack = vec![(copy_root, &mut self.root as *mut Noun)];
let mut copy_stack = vec![(copy_root, std::ptr::addr_of_mut!(self.root))];
Expand Down Expand Up @@ -1065,7 +1073,7 @@ mod tests {
let test_noun = T(&mut slab, &[D(5), D(23)]);
slab.set_root(test_noun);
let mut copy_slab: NounSlab = NounSlab::new();
copy_slab.copy_into(test_noun);
copy_slab.copy_into(test_noun, &NounSpace::empty());
}

// Fails in Miri
Expand Down
9 changes: 5 additions & 4 deletions nockchain-minimal/crates/nockchain-libp2p-io/src/tip5_util.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use bs58;
use ibig::{ubig, Stack, UBig};
use nockapp::NockAppError;
use nockvm::noun::Noun;
use nockvm::noun::{Noun, NounSpace};
use noun_serde::prelude::*;

//TODO all this stuff would be useful as jets, which mostly just requires
Expand All @@ -20,8 +20,8 @@ const P: u64 = 0xffffffff00000001;
///
/// # Returns
/// The Noun as a Base58 string
pub fn tip5_hash_to_base58(noun: Noun) -> Result<String, NockAppError> {
let tuple: [u64; 5] = noun.decode()?;
pub fn tip5_hash_to_base58(noun: Noun, space: &NounSpace) -> Result<String, NockAppError> {
let tuple: [u64; 5] = noun.decode(space)?;
let decimal_value = base_p_to_decimal(tuple)?;
let base58_string = ubig_to_base58(decimal_value);

Expand All @@ -32,8 +32,9 @@ pub fn tip5_hash_to_base58(noun: Noun) -> Result<String, NockAppError> {
pub fn tip5_hash_to_base58_stack<S: Stack>(
stack: &mut S,
noun: Noun,
space: &NounSpace,
) -> Result<String, NockAppError> {
let tuple: [u64; 5] = noun.decode()?;
let tuple: [u64; 5] = noun.decode(space)?;
let decimal_value = base_p_to_decimal_stack(stack, tuple)?;
let base58_string = ubig_to_base58(decimal_value);

Expand Down
4 changes: 2 additions & 2 deletions nockchain-minimal/crates/nockchain-math/src/belt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::ops::{Add, Div, Mul, Neg, Sub};

use nockvm::jets::util::BAIL_EXIT;
use nockvm::jets::JetErr;
use nockvm::noun::Noun;
use nockvm::noun::{Noun, NounSpace};
use noun_serde::{NounDecode, NounEncode};
use num_traits::Pow;
use rkyv::{Archive, Deserialize, Serialize};
Expand Down Expand Up @@ -85,7 +85,7 @@ impl NounEncode for Belt {
}

impl NounDecode for Belt {
fn from_noun(noun: &nockvm::noun::Noun) -> Result<Self, noun_serde::NounDecodeError> {
fn from_noun(noun: &nockvm::noun::Noun, _space: &NounSpace) -> Result<Self, noun_serde::NounDecodeError> {
let atom = noun
.as_atom()
.map_err(|_| noun_serde::NounDecodeError::ExpectedAtom)?;
Expand Down
Loading
Loading