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
21 changes: 11 additions & 10 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ members = [
"crates/nexum-runtime",
"crates/nexum-sdk",
"crates/nexum-sdk-test",
"crates/nexum-status-body",
"crates/nexum-tasks",
"crates/nexum-world",
"crates/no-std-probe",
Expand All @@ -19,6 +18,7 @@ members = [
"crates/videre-host",
"crates/videre-macros",
"crates/videre-sdk",
"crates/videre-status-body",
"crates/videre-test",
"modules/ethflow-watcher",
"modules/example",
Expand Down
10 changes: 5 additions & 5 deletions crates/nexum-module-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,14 @@ const HANDLERS: [&str; 6] = [
"on_chain_logs",
"on_tick",
"on_message",
"on_intent_status",
"on_custom",
];

/// Generate the per-cdylib glue for a nexum module.
///
/// Apply to an `impl` block whose associated functions are the event
/// handlers (`init`, `on_block`, `on_chain_logs`, `on_tick`,
/// `on_message`, `on_intent_status`). Each handler takes the wit-bindgen
/// `on_message`, `on_custom`). Each handler takes the wit-bindgen
/// payload for its event and returns `Result<(), Fault>`; `init` takes
/// the config table.
/// Handlers left undefined are ignored (their events become no-ops). The
Expand Down Expand Up @@ -138,7 +138,7 @@ pub fn module(attr: TokenStream, item: TokenStream) -> TokenStream {
return syn::Error::new_spanned(
self_ty,
"#[nexum_sdk::module] found no recognised handlers on this impl; define at least one \
of `init`, `on_block`, `on_chain_logs`, `on_tick`, `on_message`, `on_intent_status`",
of `init`, `on_block`, `on_chain_logs`, `on_tick`, `on_message`, `on_custom`",
)
.to_compile_error()
.into();
Expand Down Expand Up @@ -201,7 +201,7 @@ pub fn module(attr: TokenStream, item: TokenStream) -> TokenStream {
let logs_arm = arm("on_chain_logs", "ChainLogs");
let tick_arm = arm("on_tick", "Tick");
let message_arm = arm("on_message", "Message");
let intent_status_arm = arm("on_intent_status", "IntentStatus");
let custom_arm = arm("on_custom", "Custom");

quote! {
// Anchor a rebuild on the manifest and the extension registry:
Expand Down Expand Up @@ -232,7 +232,7 @@ pub fn module(attr: TokenStream, item: TokenStream) -> TokenStream {
#logs_arm
#tick_arm
#message_arm
#intent_status_arm
#custom_arm
}
}
}
Expand Down
3 changes: 0 additions & 3 deletions crates/nexum-sdk/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,6 @@ stderr-echo = []
# calls back into this crate (`bind_host_via_wit_bindgen!`, the host
# trait seam, the tracing facade).
nexum-module-macros = { path = "../nexum-module-macros" }
# Decoder for the opaque status body an `intent-status` event carries;
# re-exported as `nexum_sdk::status_body`.
nexum-status-body = { path = "../nexum-status-body" }
alloy-primitives.workspace = true
# Typed EIP-155 chain id; already in the guest graph via alloy-provider.
alloy-chains.workspace = true
Expand Down
8 changes: 0 additions & 8 deletions crates/nexum-sdk/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,6 @@
//! handle plus [`ChainLogParts`], the WIT-edge `From` input the bind
//! macro fills to rebuild it from the wire record.
//!
//! - [`status_body`] - decoder for the opaque versioned status body an
//! `intent-status` event carries ([`StatusBody`]).
//!
//! - [`config`] - `(key, value)` config-table lookups and decimal
//! scaling ([`get_required`], [`get_optional`], [`scale_decimal`]).
//!
Expand Down Expand Up @@ -110,7 +107,6 @@
//! [`read_latest_answer`]: chain::chainlink::read_latest_answer
//! [`Log`]: events::Log
//! [`ChainLogParts`]: events::ChainLogParts
//! [`StatusBody`]: status_body::StatusBody
//! [`get_required`]: config::get_required
//! [`get_optional`]: config::get_optional
//! [`scale_decimal`]: config::scale_decimal
Expand Down Expand Up @@ -141,10 +137,6 @@ pub mod prelude;
pub mod tracing;
pub mod wit_bindgen_macro;

/// The opaque status-body codec: decode an `intent-status` event's
/// `status` bytes into a typed [`StatusBody`](status_body::StatusBody).
pub use nexum_status_body as status_body;

/// The level vocabulary for every SDK log path: the host logging trait,
/// the guest tracing facade sink, and the module mocks all speak
/// `tracing_core::Level`. Its `Ord` is filter-oriented (`ERROR` is the
Expand Down
2 changes: 1 addition & 1 deletion crates/videre-host/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ workspace = true
nexum-runtime = { path = "../nexum-runtime" }
# Encoder for the opaque status body the host event stream carries; the
# registry lowers an adapter-reported status through it.
nexum-status-body = { path = "../nexum-status-body" }
videre-status-body = { path = "../videre-status-body" }

wasmtime.workspace = true
anyhow.workspace = true
Expand Down
28 changes: 22 additions & 6 deletions crates/videre-host/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ mod registry;
use std::sync::Arc;
use std::time::Duration;

use nexum_runtime::bindings::nexum::host::types::Event;
use nexum_runtime::bindings::nexum::host::types::{CustomEvent, Event};
use nexum_runtime::engine_config::EngineConfig;
use nexum_runtime::host::component::RuntimeTypes;
use nexum_runtime::host::extension::{
Expand All @@ -24,16 +24,15 @@ use nexum_runtime::host::extension::{
use nexum_runtime::host::state::HostState;
use nexum_runtime::manifest::{ExtensionSections, NamespaceCaps};
use tokio::sync::mpsc;
use tracing::warn;
use videre_status_body::INTENT_STATUS_KIND;
use wasmtime::component::{HasSelf, Linker};

pub use registry::{
DuplicateVenue, EgressGuard, GuardContext, GuardVerdict, IntentStatusUpdate, VenueActor,
VenueAdapterKind, VenueId, VenueInvoker, VenueRegistry, VenueRegistryBuilder,
};

/// The subscription kind the platform's status poller emits.
const INTENT_STATUS_KIND: &str = "intent-status";

/// Buffer for the status poll channel; small because the event loop
/// drains in real time.
const STATUS_CHANNEL_BUF: usize = 64;
Expand Down Expand Up @@ -155,10 +154,27 @@ async fn status_poll_task(
loop {
tokio::time::sleep(cadence).await;
for update in registry.poll_status_transitions().await {
let attrs = vec![("venue", update.venue.clone())];
// The transition rides the generic `custom` channel: the
// envelope is borsh, the status body its inner encoding. A
// keeper recovers it through `videre_sdk::event`.
let payload = match update.encode() {
Ok(payload) => payload,
Err(err) => {
warn!(
error = %err,
"intent-status envelope failed to encode - dropping transition",
);
continue;
}
};
let event = ExtensionEvent {
kind: INTENT_STATUS_KIND,
attrs: vec![("venue", update.venue.clone())],
event: Event::IntentStatus(update),
attrs,
event: Event::Custom(CustomEvent {
kind: INTENT_STATUS_KIND.to_owned(),
payload,
}),
};
if tx.send(event).await.is_err() {
// Receiver dropped -> engine shutting down.
Expand Down
13 changes: 7 additions & 6 deletions crates/videre-host/src/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,16 @@ use nexum_runtime::host::extension::{
HostService, Installed, ProviderInstance, ProviderKind, downcast_service,
};
use nexum_runtime::host::state::HostState;
use nexum_status_body::StatusBody;
use tokio::sync::Mutex as AsyncMutex;
use tracing::{info, warn};
use videre_status_body::StatusBody;
use wasmtime::Store;
use wasmtime::component::HasSelf;

/// The registry-observed status transition delivered through the host
/// `event` variant, re-exported at the spelling the registry names.
pub use nexum_runtime::bindings::nexum::host::types::IntentStatusUpdate;
/// The registry-observed status transition, carried in the `custom`
/// event's opaque payload, re-exported at the spelling the registry
/// names.
pub use videre_status_body::IntentStatusUpdate;

use crate::bindings::{
IntentHeader, IntentStatus, Quotation, RateLimit, SubmitOutcome, VenueAdapter, VenueError,
Expand Down Expand Up @@ -291,7 +292,7 @@ fn is_terminal(status: IntentStatus) -> bool {
/// stream carries. The registry attests the lifecycle state alone; proof
/// and failure reason ride the body only when the venue supplies them.
fn status_body(status: IntentStatus) -> StatusBody {
use nexum_status_body::IntentStatus as Lifecycle;
use videre_status_body::IntentStatus as Lifecycle;

let status = match status {
IntentStatus::Pending => Lifecycle::Pending,
Expand Down Expand Up @@ -857,7 +858,7 @@ pub struct DuplicateVenue {
mod tests {
use std::sync::atomic::{AtomicUsize, Ordering};

use nexum_status_body::IntentStatus as Lifecycle;
use videre_status_body::IntentStatus as Lifecycle;

use crate::bindings::value_flow::{Asset, AssetAmount};
use crate::bindings::{AuthScheme, IntentHeader, Settlement, UnsignedTx};
Expand Down
55 changes: 41 additions & 14 deletions crates/videre-host/tests/platform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,12 +99,19 @@ fn block(chain_id: u64) -> nexum::host::types::Block {
}
}

/// Wrap a polled transition as the extension event the platform emits.
/// Wrap a polled transition as the extension event the platform emits:
/// the transition rides the generic `custom` channel, its borsh envelope
/// the opaque payload.
fn status_event(update: videre_host::IntentStatusUpdate) -> ExtensionEvent {
let attrs = vec![("venue", update.venue.clone())];
let payload = update.encode().expect("encode intent-status envelope");
ExtensionEvent {
kind: INTENT_STATUS,
attrs: vec![("venue", update.venue.clone())],
event: nexum::host::types::Event::IntentStatus(update),
attrs,
event: nexum::host::types::Event::Custom(nexum::host::types::CustomEvent {
kind: INTENT_STATUS.to_owned(),
payload,
}),
}
}

Expand Down Expand Up @@ -244,6 +251,26 @@ fn scripted_registry(adapter: ScriptedAdapter) -> VenueRegistry {

/// Write a manifest subscribing the example module to intent-status
/// events from the `cow` venue.
fn echo_client_status_manifest(dir: &Path) -> PathBuf {
let manifest = dir.join("module.toml");
std::fs::write(
&manifest,
r#"
[module]
name = "echo-client"

[capabilities]
required = ["client", "logging"]

[[subscription]]
kind = "intent-status"
venue = "cow"
"#,
)
.expect("write manifest");
manifest
}

fn intent_status_manifest(dir: &Path) -> PathBuf {
let manifest = dir.join("module.toml");
std::fs::write(
Expand Down Expand Up @@ -331,8 +358,8 @@ async fn e2e_intent_status_subscription_receives_polled_transitions() {
let foreign = videre_host::IntentStatusUpdate {
venue: "other".to_owned(),
receipt: b"receipt".to_vec(),
status: nexum_status_body::StatusBody {
status: nexum_status_body::IntentStatus::Open,
status: videre_status_body::StatusBody {
status: videre_status_body::IntentStatus::Open,
proof: None,
reason: None,
}
Expand All @@ -355,11 +382,11 @@ async fn e2e_intent_status_subscription_receives_polled_transitions() {
async fn e2e_intent_status_flows_through_the_event_loop() {
use nexum_tasks::{TaskManager, TaskSet};

let Some(wasm) = module_wasm_or_skip("example") else {
let Some(wasm) = module_wasm_or_skip("echo-client") else {
return;
};
let dir = tempfile::tempdir().expect("tempdir");
let manifest = intent_status_manifest(dir.path());
let manifest = echo_client_status_manifest(dir.path());

let registry = scripted_registry(ScriptedAdapter::new([]));
let videre = Arc::new(Videre::from_registry(registry.clone()));
Expand Down Expand Up @@ -419,14 +446,14 @@ async fn e2e_intent_status_flows_through_the_event_loop() {
.await;

assert_eq!(supervisor.alive_count(), 1, "module must remain alive");
let runs = logs.list_runs("example");
assert_eq!(runs.len(), 1, "one run recorded for the example module");
let runs = logs.list_runs("echo-client");
assert_eq!(runs.len(), 1, "one run recorded for the echo-client module");
let page = logs.read(&runs[0].run, 0);
assert!(
page.records
.iter()
.any(|r| r.message.contains("intent status update from venue cow")),
"the module's on_intent_status handler ran; records were: {:?}",
.any(|r| r.message.contains("intent status from venue cow")),
"the module's on_custom handler decoded the transition; records were: {:?}",
page.records
.iter()
.map(|r| r.message.as_str())
Expand Down Expand Up @@ -536,11 +563,11 @@ async fn e2e_echo_module_registry_adapter_round_trip() {
for _ in 0..2 {
for update in registry.poll_status_transitions().await {
assert_eq!(update.venue, "echo-venue");
let body =
nexum_status_body::StatusBody::decode(&update.status).expect("status body decodes");
let body = videre_status_body::StatusBody::decode(&update.status)
.expect("status body decodes");
assert_eq!(
body.status,
nexum_status_body::IntentStatus::Fulfilled,
videre_status_body::IntentStatus::Fulfilled,
"echo settles instantly",
);
delivered += supervisor
Expand Down
Loading
Loading