Repository navigation
new interleaving prover/circuit matching the compiler/runtime - #232
Conversation
rvolosatovs
left a comment
There was a problem hiding this comment.
How can one try this out? Is it at a point where it could make sense to integrate into the ledger runtime?
(possibly just logging errors for now)
Well, right now only the tests (plus there will be a demo soonish)
Good question, maybe? I'll try it. In theory it should not be that far away. The only thing I'm still not really sure about is the Wizer part. This branch currently has support for inputs technically, in the circuit and instrumentation. But it's driven entirely though the So to verify the proof, the ledger needs to be able to provide the same thing that is passed to set-storage, and check that it is the current storage (somehow). (Also, while technically we support data segments in neo-wasm, there are some details that need to be sorted out because how it could be implemented for this depends on still unimplemented apis in Nightstream) |
98020f9 to
d744cf1
Compare
d744cf1 to
6f75056
Compare
| code.instructions() | ||
| .i32_const(0) | ||
| .return_call(self.current_resource.as_ref().unwrap().resource_new_fn) | ||
| .call(self.current_resource.as_ref().unwrap().resource_new_fn) |
There was a problem hiding this comment.
@SpaceManiac thoughts on this?
I could technically get this to work, but it's less trivial than I originally I thought for a few reasons (tail calls into host functions rather than guest). Not really about proving it, but about tracing it. And for proving it I think the way to do it is to re-write it into this anyway, so it also doesn't change that much in that regard I think.
|
|
||
| use bytes::{Bytes, BytesMut}; | ||
| use sha2::{Digest as _, Sha256}; | ||
| #[cfg(feature = "proving-instrumentation")] |
There was a problem hiding this comment.
@rvolosatovs could you take a quick look at this (the file, not this line)? there are still many things to do, but I wanted to at least sanity check this (and also since you asked about it)
There was a problem hiding this comment.
I will do a deeper look tomorrow, but is there a particular reason we don't want to always enable this and just integrate this directly into call_coordination_script?
There was a problem hiding this comment.
it could be by default I guess... mainly it was to not pull Nightstream unnecessarily, since it's not the smallest dep, but I guess for most people it will be already there
also partially because the ledger probably supports more things than the instrumentation/interleaving circuit (for example, I don't think the sha256 circuit would work right now), and I'm almost sure it will lag behind eventually, so I didn't want to block things
that said, we could remove the feature and just route things through a normal runtime flag I guess
There was a problem hiding this comment.
that said, we could remove the feature and just route things through a normal runtime flag I guess
and which could be on by default and just provide a bypass
because I think tracing errors shouldn't stop the execution, but missing things on the setup side could
There was a problem hiding this comment.
I fear that with the feature sprinkled around the file we will inevitably break stuff, could we have just one call_coordination_script with a boolean flag?
There was a problem hiding this comment.
I unified them, and made all the instrumentation + interleaving checking have its own result type instead (which also makes guest_debug that boolean flag, technically)
rvolosatovs
left a comment
There was a problem hiding this comment.
On the first glance, this looks good to me overall, and I am wondering if we could just have one call_coordination_script function always producing the trace
|
|
||
| use bytes::{Bytes, BytesMut}; | ||
| use sha2::{Digest as _, Sha256}; | ||
| #[cfg(feature = "proving-instrumentation")] |
There was a problem hiding this comment.
I will do a deeper look tomorrow, but is there a particular reason we don't want to always enable this and just integrate this directly into call_coordination_script?
| inputs, | ||
| outputs: tx_outputs, | ||
| events, | ||
| proof: Box::default(), // TODO: add proof |
There was a problem hiding this comment.
could we encode the proof and submit it to the ledger?
There was a problem hiding this comment.
not right now, or at least not in a way it makes sense
first of all currently this integration is not proving anything, it's just generating the witnesses and running them through the circuit equations
if there is something wrong it will fail (and so that thing is not provable right now)
but even if we do run the prover, the nightstream version we are pointing to right now doesn't expose an easy way of serializing the proof
and even if we do that (which is quite hardware intensive), it's not the actual proof we'd want here (it's not a succinct folding proof, that's currently not supported on main)
There was a problem hiding this comment.
Is there something we could just debug-print here with format!("{:?}")?
There was a problem hiding this comment.
Is there something we could just debug-print here with
format!("{:?}")?
I was thinking we may want Transaction<CapturedExecution> and Transaction<Proof> tbh (plus there may be other things that could be parametrized like whether it is signed or something)
Or we can just have UnprovenTransaction and Transaction. With fn prove(UnprovenTransaction) -> Transaction.
The midnight codebase has something like this for example (Transaction<ProofPreimage> I think?), since it's not that rare to have an unproven transaction (for example, you can send it to a proof server, or you could merge it with another or idk).
Right now call_coordination_script returns wasmtime::Result<(Transaction, wasmtime::Result<CapturedExecution>)>
But I was considering putting the second element of the tuple in there directly. I didn't do it because I'd need to derive all the cbor traits for it, but I could do it if you think that could be useful.
|
|
||
| use bytes::{Bytes, BytesMut}; | ||
| use sha2::{Digest as _, Sha256}; | ||
| #[cfg(feature = "proving-instrumentation")] |
There was a problem hiding this comment.
I fear that with the feature sprinkled around the file we will inevitably break stuff, could we have just one call_coordination_script with a boolean flag?
| | Type::Float32 | ||
| | Type::Float64 |
There was a problem hiding this comment.
I don't think we support floats
There was a problem hiding this comment.
we don't, but this is checking for scalar types... I guess I could remove them, the error would be misleading but it won't happen anyway
| let [root, rest @ ..] = components.as_slice() else { | ||
| unreachable!("root component is always registered"); | ||
| }; |
There was a problem hiding this comment.
Ideally, I'd like to remove the need for this
There was a problem hiding this comment.
maybe done? or well... not sure, the split is gone, the unreachable is there, although I could just let it panic without message, unless I don't use a vec?
|
|
||
| /// Register the contract's core module and create a single-step tracing store. | ||
| /// Neo-Wasm discovers each instance and captures its entry inputs automatically. | ||
| pub fn new_tracing_wasmtime_store<T: neo_wasm::WasmTraceSink + Send + 'static>( |
There was a problem hiding this comment.
could we rename this function to something like enable_tracing and take &mut Store<T> as parameter instead of calling Store::new?
| // Direct scalar/resource parameters each lower to _one_ core local. | ||
| // TODO: Support composite and indirectly lowered coordinator arguments. | ||
| // (the indexes of utxo inputs will be invalid as computed right now otherwise) | ||
| ensure!( |
There was a problem hiding this comment.
could we move this check to the top of the function to fail early?
| if let ResolvedCoordinationScriptArg::Utxo(input) = arg { | ||
| input_locals.push(index); | ||
| input_methods.push( | ||
| input | ||
| .output | ||
| .methods | ||
| .iter() | ||
| .map(|&(a, b, c, d)| { | ||
| starstream_proving_runtime::MethodHash::from_u64_words([a, b, c, d]) | ||
| }) | ||
| .collect::<Vec<_>>(), | ||
| ); | ||
| let (_, instrumented) = wizer.instrument_component(&input.wasm)?; | ||
| let scripts = input | ||
| .contract | ||
| .coordination_scripts() | ||
| .map(|(name, export)| export.map(|_| name)) | ||
| .collect::<wasmtime::Result<Vec<_>>>()?; | ||
| let templates = | ||
| build_component_templates(&instrumented, &scripts).map_err(|error| { | ||
| wasmtime::format_err!("failed to build input trace templates: {error}") | ||
| })?; | ||
| components.push(TracingComponent { | ||
| instrumented, | ||
| templates, | ||
| }); | ||
| } |
There was a problem hiding this comment.
could we do this while iterating the args during resolution?
If we unify all of this in call_coordination_script we probably also would not need the ResolvedCoordinationScriptArg enum
| script: &str, | ||
| export: &CoordinationScriptExport, |
There was a problem hiding this comment.
could we add a TODO here to only require one of these?
There was a problem hiding this comment.
well, I put the name in CoordinationScriptExport now... I don't see another easy way of recovering it otherwise
| // tracked for the proving instrumentation | ||
| // | ||
| // we use this as a key into the core-wasm function namespace, since | ||
| // wasmtime doesn't expose enough information to get the core module id for | ||
| // the export otherwise | ||
| name: String, |
There was a problem hiding this comment.
| // tracked for the proving instrumentation | |
| // | |
| // we use this as a key into the core-wasm function namespace, since | |
| // wasmtime doesn't expose enough information to get the core module id for | |
| // the export otherwise | |
| name: String, | |
| name: Arc<str>, |
| #[allow(dead_code)] | ||
| mod common; |
There was a problem hiding this comment.
| #[allow(dead_code)] | |
| mod common; | |
| pub mod common; |
| enum ResolvedCoordinationScriptArg { | ||
| Val(Val), | ||
| Utxo(ResolvedUtxoInput), | ||
| } | ||
|
|
||
| struct ResolvedUtxoInput { | ||
| input: TransactionInput, | ||
| output: TransactionOutput, | ||
| contract: starstream_runtime_next::Contract<Ctx>, | ||
| external_id: Option<Arc<str>>, | ||
| wasm: Vec<u8>, | ||
| } |
There was a problem hiding this comment.
l still feel like these should not be necessary, if we just inline everything in call_coordination_script, at least to begin with, we can always refactor later.
That would also minimize the diff in the PR making it easier to review
rvolosatovs
left a comment
There was a problem hiding this comment.
The ledger/runtime part looks good to me, and I am happy to refactor things later
Signed-off-by: Enzo Cioppettini <48031343+ecioppettini@users.noreply.github.com>
Signed-off-by: Enzo Cioppettini <48031343+ecioppettini@users.noreply.github.com>
…ected) Signed-off-by: Enzo Cioppettini <48031343+ecioppettini@users.noreply.github.com>
Signed-off-by: Enzo Cioppettini <48031343+ecioppettini@users.noreply.github.com>
Signed-off-by: Enzo Cioppettini <48031343+ecioppettini@users.noreply.github.com>
Signed-off-by: Enzo Cioppettini <48031343+ecioppettini@users.noreply.github.com>
Signed-off-by: Enzo Cioppettini <48031343+ecioppettini@users.noreply.github.com>
add a few more tests in test.qnt Signed-off-by: Enzo Cioppettini <48031343+ecioppettini@users.noreply.github.com>
Signed-off-by: Enzo Cioppettini <48031343+ecioppettini@users.noreply.github.com>
Signed-off-by: Enzo Cioppettini <48031343+ecioppettini@users.noreply.github.com>
Signed-off-by: Enzo Cioppettini <48031343+ecioppettini@users.noreply.github.com>
Signed-off-by: Enzo Cioppettini <48031343+ecioppettini@users.noreply.github.com>
Signed-off-by: Enzo Cioppettini <48031343+ecioppettini@users.noreply.github.com>
Signed-off-by: Enzo Cioppettini <48031343+ecioppettini@users.noreply.github.com>
Signed-off-by: Enzo Cioppettini <48031343+ecioppettini@users.noreply.github.com>
it makes it much easier to the circuit to just ignore a duplicated registration than to forbid it, and it shouldn't change things semantically (although performance may degrade) Signed-off-by: Enzo Cioppettini <48031343+ecioppettini@users.noreply.github.com>
…call-method) Signed-off-by: Enzo Cioppettini <48031343+ecioppettini@users.noreply.github.com>
Signed-off-by: Enzo Cioppettini <48031343+ecioppettini@users.noreply.github.com>
Signed-off-by: Enzo Cioppettini <48031343+ecioppettini@users.noreply.github.com>
…ents (limited to at most one coord script) Signed-off-by: Enzo Cioppettini <48031343+ecioppettini@users.noreply.github.com>
Signed-off-by: Enzo Cioppettini <48031343+ecioppettini@users.noreply.github.com>
Signed-off-by: Enzo Cioppettini <48031343+ecioppettini@users.noreply.github.com>
Signed-off-by: Enzo Cioppettini <48031343+ecioppettini@users.noreply.github.com>
…o mapping Signed-off-by: Enzo Cioppettini <48031343+ecioppettini@users.noreply.github.com>
provides instrumentation on top of the runtime, plus host-abi interleaving semantics/embeddings Signed-off-by: Enzo Cioppettini <48031343+ecioppettini@users.noreply.github.com>
Signed-off-by: Enzo Cioppettini <48031343+ecioppettini@users.noreply.github.com>
… zeroing from the templates Signed-off-by: Enzo Cioppettini <48031343+ecioppettini@users.noreply.github.com>
Signed-off-by: Enzo Cioppettini <48031343+ecioppettini@users.noreply.github.com>
switch the proving paths to the audit path (there is no NIFS path currently implemented in main) Signed-off-by: Enzo Cioppettini <48031343+ecioppettini@users.noreply.github.com>
instrumenting tail calls into host functions has quite a few corner cases (for neo-wasm), this should eventually be supported (although it may very well be by re-writing into call . return), but in the meantime this is a much simpler change (and it shouldn't change much in terms of performance at least) Signed-off-by: Enzo Cioppettini <48031343+ecioppettini@users.noreply.github.com>
Signed-off-by: Enzo Cioppettini <48031343+ecioppettini@users.noreply.github.com>
…habetically Signed-off-by: Enzo Cioppettini <48031343+ecioppettini@users.noreply.github.com>
and make it receive a mutable Store also remove the implicit root component registration Signed-off-by: Enzo Cioppettini <48031343+ecioppettini@users.noreply.github.com>
… functions rather than adding a flag, the instrumentation + tracing + circuit checks have an independent error from the normal execution, which can be ignored by the caller Signed-off-by: Enzo Cioppettini <48031343+ecioppettini@users.noreply.github.com>
Signed-off-by: Enzo Cioppettini <48031343+ecioppettini@users.noreply.github.com>
Signed-off-by: Enzo Cioppettini <48031343+ecioppettini@users.noreply.github.com>
…xport Signed-off-by: Enzo Cioppettini <48031343+ecioppettini@users.noreply.github.com>
baa3b8b to
cf1b145
Compare
Signed-off-by: Enzo Cioppettini <48031343+ecioppettini@users.noreply.github.com>
Address the review comments on #232 that were left open and bring the ledger and runtime back close to their shape before that PR: - drop the single-use `resolve_coordination_script_args`, `prepare_tracing`, `postprocess_trace`, `run_resolved_coordination_script` and `tracing_components` helpers together with the `ResolvedCoordinationScriptArg`, `ResolvedUtxoInput`, `TracingComponent` and `TracingSetup` types; the original argument loop now registers each input component and records its parameter index and method hashes in place, so the input Wasm is no longer copied - keep one `register_tracing` helper shared by the root contract, its imports and the loaded inputs - remove the `unreachable!` on the root component and the float types from the direct-argument check - use `.context` instead of `format_err!` closures and fix the literal `{error}` in the trace capture context - only do the tracing work when `enable_tracing` succeeds; its error is surfaced through the returned execution result as before - `CoordinationScriptExport::name` is an `Arc<str>` and `name()` returns a reference to it Assisted-by: anthropic:claude-fable-5-1 Signed-off-by: Roman Volosatovs <rvolosatovs@riseup.net>
Address the review comments on #232 that were left open and bring the ledger and runtime back close to their shape before that PR: - drop the single-use `resolve_coordination_script_args`, `prepare_tracing`, `postprocess_trace`, `run_resolved_coordination_script` and `tracing_components` helpers together with the `ResolvedCoordinationScriptArg`, `ResolvedUtxoInput`, `TracingComponent` and `TracingSetup` types; the original argument loop now registers each input component and records its parameter index and method hashes in place, so the input Wasm is no longer copied - keep one `register_tracing` helper shared by the root contract, its imports and the loaded inputs - remove the `unreachable!` on the root component and the float types from the direct-argument check - use `.context` instead of `format_err!` closures and fix the literal `{error}` in the trace capture context - only do the tracing work when `enable_tracing` succeeds; its error is surfaced through the returned execution result as before - `CoordinationScriptExport::name` is an `Arc<str>` and `name()` returns a reference to it Assisted-by: anthropic:claude-fable-5-1 Signed-off-by: Roman Volosatovs <rvolosatovs@riseup.net>
Address the review comments on #232 that were left open and bring the ledger and runtime back close to their shape before that PR: - drop the single-use `resolve_coordination_script_args`, `prepare_tracing`, `postprocess_trace`, `run_resolved_coordination_script` and `tracing_components` helpers together with the `ResolvedCoordinationScriptArg`, `ResolvedUtxoInput`, `TracingComponent` and `TracingSetup` types; the original argument loop now registers each input component and records its parameter index and method hashes in place, so the input Wasm is no longer copied - keep one `register_tracing` helper shared by the root contract, its imports and the loaded inputs - build the root trace templates before enabling tracing; input and import templates are built while tracing is already enabled and a failure there leaves the store single-stepping for the rest of the call, which is fine since every transaction will produce a trace eventually - register the imports resolved for the root together with it, and the imports each input pulls in before that input is loaded, since the registry refuses a module that already executed untraced - remove the `unreachable!` on the root component - use `.context` instead of `format_err!` closures and fix the literal `{error}` in the trace capture context - only do the tracing work when `enable_tracing` succeeds; its error is surfaced through the returned execution result as before, which the doc comment now explains - `CoordinationScriptExport::name` is an `Arc<str>` and `name()` returns a reference to it Assisted-by: anthropic:claude-fable-5-1 Signed-off-by: Roman Volosatovs <rvolosatovs@riseup.net>
Simplify the ledger tracing integration, bringing the ledger client runtime closer to pre-#232 state and minimizing the diff ```diff diff --git a/starstream-ledger/src/client/runtime.rs b/starstream-ledger/src/client/runtime.rs index 64295f7..66cf74e 100644 --- a/starstream-ledger/src/client/runtime.rs +++ b/starstream-ledger/src/client/runtime.rs @@ -1,4 +1,5 @@ use core::mem; +use core::ops::Deref; use core::pin::Pin; use std::collections::{BTreeSet, HashMap, HashSet}; @@ -6,6 +7,11 @@ use std::sync::Arc; use bytes::{Bytes, BytesMut}; use sha2::{Digest as _, Sha256}; +use starstream_proving_runtime::{ + CapturedExecution, ComponentTemplates, WasmTraceSink, WasmtimeTraceRegistry, + build_component_templates, check_captured_transaction, enable_tracing, + register_tracing_component, +}; use starstream_runtime::bindings::starstream; use starstream_runtime::{ CoordinationScriptExport, CoordinationScriptImport, Token, Utxo, UtxoExport, UtxoImport, @@ -25,22 +31,24 @@ use crate::{ Transaction, TransactionEvent, TransactionInput, TransactionOutput, encode_digest, parse_digest, }; -pub fn compile_component( - engine: &Engine, - wizer: &Wizer, - wasm: &[u8], -) -> wasmtime::Result<Component> { - let (_wizer_cx, instrumented) = wizer - .instrument_component(wasm) - .context("failed to instrument component")?; - let component = wasmtime::component::Component::from_binary(engine, &instrumented) - .context("failed to compile component")?; - Ok(component) +/// Contract compiled from the instrumented component bytes +#[derive(Clone)] +pub struct CompiledContract { + pub contract: starstream_runtime::Contract<Ctx>, + pub instrumented: Bytes, +} + +impl Deref for CompiledContract { + type Target = starstream_runtime::Contract<Ctx>; + + fn deref(&self) -> &Self::Target { + &self.contract + } } #[derive(Clone)] pub struct Contract { - pub contract: Option<starstream_runtime::Contract<Ctx>>, + pub contract: Option<CompiledContract>, pub wasm: Bytes, } @@ -53,13 +61,15 @@ pub trait Client { ) -> impl Future<Output = anyhow::Result<TransactionOutput>>; } -pub async fn new_contract( +/// Instrument and compile `wasm`, compiling the imports missing from `imports` via `client` +pub async fn compile_contract( client: &(impl Client + ?Sized), + engine: &Engine, wizer: &Wizer, - component: &Component, + wasm: &[u8], external_id: Option<&str>, imports: &mut HashMap<[u8; 32], Contract>, -) -> wasmtime::Result<starstream_runtime::Contract<Ctx>> { +) -> wasmtime::Result<CompiledContract> { struct ContractLookup<'a>(pub &'a HashMap<[u8; 32], Contract>); impl starstream_runtime::ContractLookup<Ctx> for ContractLookup<'_> { fn get_contract( @@ -71,14 +81,19 @@ pub async fn new_contract( error!(external_id, "unresolved contract import"); format!("contract identified by `external-id` `{external_id}` not found") })?; - contract.contract.clone().with_context(|| { + let contract = contract.contract.as_ref().with_context(|| { error!(external_id, "uncompiled contract import"); format!("contract identified by `external-id` `{external_id}` was not compiled") - }) + })?; + Ok(contract.contract.clone()) } } - let engine = component.engine(); + let (.., instrumented) = wizer + .instrument_component(wasm) + .context("failed to instrument component")?; + let component = + Component::from_binary(engine, &instrumented).context("failed to compile component")?; let ty = component.component_type(); let script_instance = get_coordination_script_instance_import(engine, &ty); let script_external_ids = script_instance.as_ref().map(|instance| { @@ -106,11 +121,11 @@ pub async fn new_contract( .await .map_err(wasmtime::Error::from_anyhow)?, }; - let component = compile_component(engine, wizer, wasm.as_ref())?; - let contract = Box::pin(new_contract( + let contract = Box::pin(compile_contract( client, + engine, wizer, - &component, + &wasm, Some(external_id), imports, )) @@ -123,27 +138,112 @@ pub async fn new_contract( }, ); } - starstream_runtime::Contract::new(component, external_id, ContractLookup(imports)) + let contract = + starstream_runtime::Contract::new(&component, external_id, ContractLookup(imports))?; + Ok(CompiledContract { + contract, + instrumented: instrumented.into(), + }) +} + +fn build_templates(contract: &CompiledContract) -> wasmtime::Result<ComponentTemplates> { + let scripts = contract + .contract + .coordination_script_names() + .collect::<Vec<_>>(); + build_component_templates(&contract.instrumented, &scripts) + .context("failed to build trace templates") +} + +fn register_tracing(cx: &mut Ctx, contract: &CompiledContract) -> wasmtime::Result<()> { + let templates = build_templates(contract)?; + register_tracing_component(cx, &contract.instrumented, &templates.bindings) } +/// Register compiled imports that have not been registered yet +fn register_imports( + cx: &mut Ctx, + imports: &HashMap<[u8; 32], Contract>, + registered: &mut HashSet<[u8; 32]>, +) -> wasmtime::Result<()> { + for (digest, Contract { contract, .. }) in imports { + let Some(contract) = contract else { + continue; + }; + if registered.insert(*digest) { + register_tracing(cx, contract)?; + } + } + Ok(()) +} + +/// Call the coordination script `export` exported by `contract` with `args`, +/// loading UTXO arguments through `client`. +/// +/// `wasm` must be the original component bytes `contract` was compiled from. +/// +/// The execution check is best-effort for now, mainly to not block execution +/// on configurations not supported by the current instrumentation, so its +/// result is returned next to the transaction and it is up to the caller to +/// report the error, if any. #[allow(clippy::too_many_arguments)] pub async fn call_coordination_script( store: &mut Store<Ctx>, client: &(impl Client + ?Sized), wizer: &Wizer, - contract: &starstream_runtime::Contract<Ctx>, + contract: &CompiledContract, wasm: &[u8], export: &CoordinationScriptExport, imports: &mut HashMap<[u8; 32], Contract>, args: impl IntoIterator<Item = CoordinationScriptArg>, results: &mut [Val], utxos: &mut Vec<Utxo<Arc<std::sync::Mutex<UtxoCtx>>>>, -) -> wasmtime::Result<Transaction> { +) -> wasmtime::Result<(Transaction, wasmtime::Result<CapturedExecution>)> { let engine = contract.component().engine(); + let digest: [u8; 32] = Sha256::digest(wasm).into(); + let args = args.into_iter().collect::<Vec<_>>(); + let mut registered = HashSet::from([digest]); + let mut tracing = (|| { + if args + .iter() + .any(|arg| matches!(arg, CoordinationScriptArg::Utxo(_))) + { + // Direct scalar/resource parameters each lower to _one_ core local. + // TODO: Support composite and indirectly lowered coordinator arguments. + ensure!( + export.ty().params().len() <= 16 + && export.ty().params().all(|(_, ty)| matches!( + ty, + Type::Bool + | Type::S8 + | Type::U8 + | Type::S16 + | Type::U16 + | Type::S32 + | Type::U32 + | Type::S64 + | Type::U64 + | Type::Float32 + | Type::Float64 + | Type::Char + | Type::Own(_) + | Type::Borrow(_) + )), + "traced input loading requires direct scalar/resource arguments" + ); + } + let templates = build_templates(contract)?; + enable_tracing(store)?; + let cx = store.data_mut(); + register_tracing_component(cx, &contract.instrumented, &templates.bindings)?; + register_imports(cx, imports, &mut registered)?; + Ok(templates) + })(); let instance = contract.instantiate(&mut *store).await?; - let digest = Sha256::digest(wasm).into(); let mut inputs = Vec::default(); + let mut input_locals = Vec::default(); + let mut input_methods = Vec::default(); let mut params = Vec::with_capacity(export.ty().params().len()); let mut args = args.into_iter(); for (name, _ty) in export.ty().params() { @@ -163,19 +263,16 @@ pub async fn call_coordination_script( let (external_id, wasm) = if utxo_contract_digest == digest { let wasm = apply_state(wasm, &utxo.state).map_err(wasmtime::Error::from_anyhow)?; - (None, Bytes::from(wasm)) + (None, wasm) } else if let Some(Contract { wasm, .. }) = imports.get(&utxo_contract_digest) { let wasm = apply_state(wasm, &utxo.state).map_err(wasmtime::Error::from_anyhow)?; - (Some(Arc::from(utxo.contract)), Bytes::from(wasm)) + (Some(Arc::from(utxo.contract)), wasm) } else { let wasm = client .get_contract_wasm(utxo_contract_digest) .await .map_err(wasmtime::Error::from_anyhow)?; - let wasm = - apply_state(&wasm, &utxo.state).map_err(wasmtime::Error::from_anyhow)?; - let wasm = Bytes::from(wasm); imports.insert( utxo_contract_digest, Contract { @@ -183,17 +280,34 @@ pub async fn call_coordination_script( wasm: wasm.clone(), }, ); + let wasm = + apply_state(&wasm, &utxo.state).map_err(wasmtime::Error::from_anyhow)?; (Some(Arc::from(utxo.contract)), wasm) }; - let component = compile_component(engine, wizer, &wasm)?; - let contract = new_contract( + let contract = compile_contract( client, + engine, wizer, - &component, + &wasm, external_id.as_deref(), &mut *imports, ) .await?; + tracing = tracing.and_then(|templates| { + let cx = store.data_mut(); + register_tracing(cx, &contract)?; + register_imports(cx, imports, &mut registered)?; + Ok(templates) + }); + input_locals.push(params.len()); + input_methods.push( + utxo.methods + .iter() + .map(|&(a, b, c, d)| { + starstream_proving_runtime::MethodHash::from_u64_words([a, b, c, d]) + }) + .collect::<Vec<_>>(), + ); let utxo_export = contract.get_utxo(&utxo.instance)?; let storage_export = utxo_export.storage().context("UTXO has no storage")?; let mut storage = Val::Record(Vec::default()); @@ -252,7 +366,7 @@ pub async fn call_coordination_script( cx.clone() }; let mut instance = WasmtimeWizerComponent { - store: &mut *store, + store, instance: utxo.instance(), }; let (contract, wasm) = if let Some(external_id) = cx.external_id.as_deref() { @@ -295,12 +409,46 @@ pub async fn call_coordination_script( state, }); } - Ok(Transaction { - inputs, - outputs: tx_outputs, - events, - proof: Box::default(), // TODO: add proof - }) + let execution = tracing.and_then(|templates| { + let mut handles = Vec::with_capacity(input_locals.len()); + if !input_locals.is_empty() { + // The root coordinator is instantiated before input UTXOs; + // the registry iterates in instance-index order. + let (.., coordinator) = store + .data() + .traces + .instances() + .context("failed to capture trace")? + .next() + .context("missing coordinator trace")?; + let fref = templates + .export_fref(export.name()) + .context("missing coordinator export")?; + let entry = coordinator + .steps() + .iter() + .find(|step| step.current_function_ref == Some(fref)) + .context("missing coordinator entry arguments")?; + for local in input_locals { + let &(handle, ..) = entry + .locals_words + .get(local) + .context("missing input handle local")?; + handles.push(starstream_proving_runtime::ResourceHandle(handle)); + } + } + // TODO: Authenticate these input handles against the coordinator argument root. + check_captured_transaction(&store.data().traces, handles, &input_methods) + }); + Ok(( + Transaction { + inputs, + outputs: tx_outputs, + events, + proof: Box::default(), // TODO: add proof + }, + execution, + )) } #[derive(Debug, Default)] @@ -308,6 +456,7 @@ pub struct Ctx { pub table: ResourceTable, pub events: Vec<TransactionEvent>, pub outputs: Vec<starstream_runtime::Utxo<<Self as starstream_runtime::Host>::UtxoContext>>, + pub traces: WasmtimeTraceRegistry, } #[derive(Clone, Debug)] @@ -323,6 +472,16 @@ pub fn lock<T>(mu: &std::sync::Mutex<T>) -> wasmtime::Result<std::sync::MutexGua mu.lock().map_err(|err| format_err!("{err}")) } +impl WasmTraceSink for Ctx { + fn wasm_trace_registry(&self) -> &WasmtimeTraceRegistry { + &self.traces + } + + fn wasm_trace_registry_mut(&mut self) -> &mut WasmtimeTraceRegistry { + &mut self.traces + } +} + impl starstream::std::cardano::Host for Ctx { fn block_height(&mut self) -> wasmtime::Result<i64> { bail!("TODO") ``` --------- Signed-off-by: Roman Volosatovs <rvolosatovs@riseup.net>
I wouldn't merge this yet, mainly because it depends on things in Nightstream that are unmerged yet (and some things that won't even be merged, since they were already rewritten in one of the ongoing branches)
However, I think it can be reviewed right now.
This adds mainly a "replacement" for
starstream-interleaving-proof-legacy.It also adds some missing things to the quint spec, since it was needed to get a few tests going.
Limitations: