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
62 changes: 62 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ resolver = "2"
members = [
"cli",
"core",
"jet_plugin_abi",
"plugin_tests/script_jets",
"service",
]

Expand Down
5 changes: 5 additions & 0 deletions core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,8 @@ hex-literal = "1.1.0"
simplicity-lang = { workspace = true }
thiserror = { workspace = true }
hex = { workspace = true }
dlopen2 = "0.8.2"
jet_plugin_abi = { path = "../jet_plugin_abi" }

[dev-dependencies]
script_jets = { path = "../plugin_tests/script_jets" }
87 changes: 65 additions & 22 deletions core/src/jets/base.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
//! Runs base jets (`Core`, `Elements`) on an [`UnchainedEnv`].
//! Runs base jets on an environment that wraps their own.
//!
//! `JetEnvironment::c_jet_ptr` must return a plain `fn` pointer, so a base jet can't be wrapped in
//! a closure that swaps the unchained env for the base C env. Instead, every base jet gets its own
//! a closure that swaps the outer env for the base C env. Instead, every base jet gets its own
//! trampoline, a `fn` monomorphized over the jet's index in `ALL`.
use simplicity::ffi::CFrameItem;
use simplicity::jet::{Core, CoreEnv, Elements, ElementsTxEnv, Jet, JetEnvironment};

use super::environments::UnchainedEnv;
use super::bitcoin::CoreExtension;
use super::elements::ElementsExtension;
use super::environments::{BitcoinUnchainedEnv, ElementsUnchainedEnv, UnchainedEnv};

/// C jet function running on an [`UnchainedEnv`].
pub type UnchainedJetFn<E> = fn(&mut CFrameItem, CFrameItem, &UnchainedEnv<E>) -> bool;
/// C jet function running on an environment of type `W`.
pub type JetFn<W> = fn(&mut CFrameItem, CFrameItem, &W) -> bool;

/// Upper bound on the number of jets in a base jet set, see [`fn_table`].
const MAX_BASE_JETS: usize = 512;
Expand All @@ -25,6 +27,17 @@ pub trait BaseJet: Jet + Copy {
fn index(&self) -> usize;
}

/// An environment that holds the environment of a base jet set.
pub trait AsBaseEnv<E> {
fn base_env(&self) -> &E;
}

impl<E> AsBaseEnv<E> for UnchainedEnv<E> {
fn base_env(&self) -> &E {
&self.env
}
}

impl BaseJet for Core {
type Env = CoreEnv;
const ALL: &'static [Self] = &Core::ALL;
Expand All @@ -43,23 +56,49 @@ impl BaseJet for Elements {
}
}

/// Returns the C implementation of a base jet, adapted to run on an [`UnchainedEnv`].
pub fn base_c_jet_ptr<J: BaseJet>(jet: &J) -> UnchainedJetFn<J::Env> {
impl BaseJet for CoreExtension {
type Env = BitcoinUnchainedEnv;
const ALL: &'static [Self] = &CoreExtension::ALL;

fn index(&self) -> usize {
match self {
CoreExtension::Core(inner) => inner.index(),
CoreExtension::GetOpcodeFromScript => Core::ALL.len(),
CoreExtension::GetPubkeyFromScript => Core::ALL.len() + 1,
}
}
}

impl BaseJet for ElementsExtension {
type Env = ElementsUnchainedEnv;
const ALL: &'static [Self] = &ElementsExtension::ALL;

fn index(&self) -> usize {
match self {
ElementsExtension::Elements(inner) => inner.index(),
ElementsExtension::GetOpcodeFromScript => Elements::ALL.len(),
ElementsExtension::GetPubkeyFromScript => Elements::ALL.len() + 1,
}
}
}

/// Returns the C implementation of a base jet, adapted to run on `W`.
pub fn base_c_jet_ptr<J: BaseJet, W: AsBaseEnv<J::Env>>(jet: &J) -> JetFn<W> {
const { assert!(J::ALL.len() <= MAX_BASE_JETS) };

Trampolines::<J>::TABLE.as_flattened().as_flattened()[jet.index()]
Trampolines::<J, W>::TABLE.as_flattened().as_flattened()[jet.index()]
}

fn trampoline<J: BaseJet, const I: usize>(
fn trampoline<J: BaseJet, W: AsBaseEnv<J::Env>, const I: usize>(
dst: &mut CFrameItem,
src: CFrameItem,
env: &UnchainedEnv<J::Env>,
env: &W,
) -> bool {
let c_jet = <J::Env as JetEnvironment>::c_jet_ptr(&J::ALL[I]);
c_jet(dst, src, env.env.c_jet_env())
c_jet(dst, src, env.base_env().c_jet_env())
}

/// Builds a `[[[_; 8]; 8]; 8]` table of `$f::<J, I>` for every `I` below [`MAX_BASE_JETS`].
/// Builds a `[[[_; 8]; 8]; 8]` table of `$f::<J, W, I>` for every `I` below [`MAX_BASE_JETS`].
// TODO: refactor into something less ugly, e.g. a flat table generated by a proc macro.
macro_rules! fn_table {
($f:ident) => {
Expand All @@ -72,28 +111,32 @@ macro_rules! fn_table {
[$(fn_table!(@c $f; $a; $b; 0 1 2 3 4 5 6 7)),*]
};
(@c $f:ident; $a:literal; $b:literal; $($c:literal)*) => {
[$($f::<J, { $a * 64 + $b * 8 + $c }> as UnchainedJetFn<J::Env>),*]
[$($f::<J, W, { $a * 64 + $b * 8 + $c }> as JetFn<W>),*]
};
}

struct Trampolines<J>(J);
struct Trampolines<J, W>(J, W);

impl<J: BaseJet> Trampolines<J> {
impl<J: BaseJet, W: AsBaseEnv<J::Env>> Trampolines<J, W> {
#[allow(clippy::type_complexity)]
const TABLE: [[[UnchainedJetFn<J::Env>; 8]; 8]; 8] = fn_table!(trampoline);
const TABLE: [[[JetFn<W>; 8]; 8]; 8] = fn_table!(trampoline);
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn index_matches_position_in_all() {
for (i, jet) in Core::ALL.iter().enumerate() {
assert_eq!(jet.index(), i, "{jet}");
}
for (i, jet) in Elements::ALL.iter().enumerate() {
fn assert_indices<J: BaseJet>() {
for (i, jet) in J::ALL.iter().enumerate() {
assert_eq!(jet.index(), i, "{jet}");
}
}

#[test]
fn index_matches_position_in_all() {
assert_indices::<Core>();
assert_indices::<Elements>();
assert_indices::<CoreExtension>();
assert_indices::<ElementsExtension>();
}
}
4 changes: 2 additions & 2 deletions core/src/jets/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ pub mod environments;
pub mod exec;

#[cfg(test)]
mod tests {
pub(crate) mod tests {
use std::ptr;
use std::sync::Arc;

Expand Down Expand Up @@ -141,7 +141,7 @@ mod tests {
}
}

fn dummy_elements_env() -> ElementsEnv<Arc<Transaction>> {
pub(crate) fn dummy_elements_env() -> ElementsEnv<Arc<Transaction>> {
let ctrl_blk: [u8; 33] = [
0xc0, 0xeb, 0x04, 0xb6, 0x8e, 0x9a, 0x26, 0xd1, 0x16, 0x04, 0x6c, 0x76, 0xe8, 0xff,
0x47, 0x33, 0x2f, 0xb7, 0x1d, 0xda, 0x90, 0xff, 0x4b, 0xef, 0x53, 0x70, 0xf2, 0x52,
Expand Down
1 change: 1 addition & 0 deletions core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use simplicity::bitcoin::NetworkKind;
use simplicity::elements::BlockHash;

pub mod jets;
pub mod plugin;
pub mod program;
pub mod runner;
pub mod utils;
Expand Down
15 changes: 15 additions & 0 deletions core/src/plugin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Custom jet plugins

## General plugin loading flow

1. The plugin provider writes its jets as `unsafe extern "C"` functions and lists them in
`jet_plugin_abi::register_jets!`. The macro creates a static, plain-data descriptor
(ABI version, `simplicity-sys` version, base jet set) with an array of jets, each holding
its name, types, cost, encoding, CMR and function pointer, and exports a function
returning it from the dynamic library.
2. The host uses `declare_plugin!` to declare a handle type and generate its `PluginTrait`
impl. `Handle::load(path, sha256)` then loads the library, reads its descriptor, validates
it and stores the result in the handle for the rest of the process.
3. Programs are parsed and run with `PluginJet<Handle>` and `PluginEnv<Handle>`. Base jets run
through the host's own implementation; custom jets are called through the function
pointers from the descriptor.
Loading
Loading