Conversation
| /// Provided kernel procedure offset is out of bounds | ||
| pub const ERR_KERNEL_PROCEDURE_OFFSET_OUT_OF_BOUNDS: u32 = 0x20000; | ||
| /// Error Message: "anchor block commitment must not be empty" | ||
| pub const ERR_ACCOUNT_ANCHOR_BLOCK_COMMITMENT_MUST_NOT_BE_EMPTY: MasmError = MasmError::from_static_str("anchor block commitment must not be empty"); |
There was a problem hiding this comment.
I think the goal with 0.14 was that we wouldn't need to duplicate errors defined in masm and rust (and not have to do any build.rs logic). Although I didn't check everywhere, it seems like a bunch of these are only used in tests. Do we really need MasmError, or could those strings be moved directly in tests?
There was a problem hiding this comment.
For example, in the VM, we hardcode the expected strings in the tests
There was a problem hiding this comment.
Thanks for raising this. We don't need the errors extracted from Masm, but the main reason I wanted to keep it is because it makes testing a lot more convenient. I personally would like to avoid maintaining expected error strings and since we already have the build.rs setup, it seems fine to me to keep it for that purpose. I don't think it has any disadvantages other than needing an occasional maintenance update. What I haven't done yet, but should do is only expose these under testing. Previously users could have wanted access to them to map the codes back to the strings, but that should no longer be necessary, so we don't need to make it part of the regular API.
I think that the best way is to use The reason I'm not 100% sure is that the kernel is weird in that all files get built into a single module, and so I'm not sure if that affects source management in weird ways. So I'd give As for the EDIT: I will look into the kernel issue to see if I can reproduce in a simpler environment and report back |
I would have your
The issue here, AFAICT, is that the
Note that you must convert error types implementing |
The problem though is that even when we do get the nice rendering, |
When you say it isn't printing out the right part, do you mean that it is printing completely unrelated code; the span is offset weirdly (e.g. it is off by one line); or the span isn't what you expected (e.g. the span you have is for a related bit of code, but not useful)? If it is the first of those, the problem might arise if you use a It really boils down to how you are using the I'm not sure how much of the above applies to the issues you're seeing without knowing more specifics, but hopefully that provides some intuition for what might be going on. I'm happy to pair up and take a look with you guys too. |
bobbinth
left a comment
There was a problem hiding this comment.
Thank you! Looks good! Not a full review, but I left some comments inline and also some comments below.
I would have your
build.rsalways assemble with debug mode enabled (and thus it will include debug information, including the source files). With debug mode disabled, no source locations will be stored in the resulting library, so there won't be any way to add in source files after the fact. It's all or nothing. At this point in time, there is essentially no benefit to not enabling debug mode IMO.
I think we should just remove the option to assemble w/o debug info from Miden assembler. Basically, all programs would be assembled with debug info in them, and if needed, we can strip them out later (e.g., when loading a program into the processor). @plafer - do we already have an issue for this in miden-vm? I would probably prioritize this for v0.15.0 release.
Something similar applies to
TransactionProver, although I think we could also not expose the source manager here. In my mind, during proving we assume that the transaction witness comes from an already executed transaction, so I wonder if the error reporting is necessary here. For now though, I've added the source manager, but it's easy to remove.
I would remove source manager from the TransactionProver. As you described above, we basically always first execute a program using TransactionExecutor and then prove using TransactionProver - so, if there were any errors, they should be caught by the executor.
That would be great for the UX as well. That is, you no longer have to make sure that the assembler and the processor are in debug mode in order to get better errors or debug information out of the processor. Instead, it is always present (unless explicitly stripped, like when stored on-chain) and if |
Yes, it prints this: where the source code is this one: So the second line is correct, i.e. it references the right source "file", but it should point to the mem_storew line.
Yeah I think there are at least three virtual files in this source manager, (in the test
I gave this a try by introducing an unaligned memory error in api.masm. The following snippets I've added in let source_manager = Arc::new(DefaultSourceManager::default()) as Arc<dyn SourceManager>;
let kernel_path = Path::new(concat!(env!("OUT_DIR"), "/asm/kernels/transaction"));
source_manager.load_file(&kernel_path.join("api.masm")).unwrap();Unfortunately that didn't work. The error did not contain a source snippet: I also tried using the same approach as in let shared_path = concat!(env!("OUT_DIR"), "/asm/shared");
let mut temp_assembler = Assembler::new(Arc::clone(&source_manager))
.with_library(StdLibrary::default())
.unwrap();
let kernel_namespace = miden_objects::assembly::LibraryNamespace::new("kernel")
.expect("namespace should be valid");
temp_assembler
.add_modules_from_dir(kernel_namespace.clone(), &Path::new(shared_path))
.unwrap();
let kernel_path = Path::new(concat!(env!("OUT_DIR"), "/asm/kernels/transaction"));
let _kernel_lib = KernelLibrary::from_dir(
kernel_path.join("api.masm"),
Some(kernel_path.join("lib")),
temp_assembler,
)
.unwrap();The error was the same though. I would somehow like to confirm whether the source manager that ends up being passed to execute does indeed contain these, but I can't find a "list all contents" API. |
I think I see what is causing the issue. Here is where the code is being assembled. There are two things to note about this:
There are a couple things we could do here:
EDIT: I just realized that Option 1 above is a non-starter for a different reason - it would invalidate the source spans of the previously assembled program. The |
bobbinth
left a comment
There was a problem hiding this comment.
Looks good! Thank you! I'm going to merge as is and we can address some of the outstanding things in follow-up PRs. These include:
- Potentially moving
source_managerto be a field inTransactionExecutor. - Figuring out how to fix incorrect source location reference (if it still an issue).
| block_ref: BlockNumber, | ||
| notes: InputNotes<InputNote>, | ||
| tx_args: TransactionArgs, | ||
| source_manager: Arc<dyn SourceManager>, |
There was a problem hiding this comment.
I think this is fine for now, but I would probably change this in the future to make source_manager a field in the TransactionExecutor. The main reasoning here is that source manager is probably set at the time we create a TransactionExecutor and then we should be able to re-use it for different transactions. If, however, we do need to pass a new source manager into this function from time to time, then keeping it as a function argument is fine.
| tx_script: TransactionScript, | ||
| advice_inputs: AdviceInputs, | ||
| foreign_account_inputs: Vec<AccountInputs>, | ||
| source_manager: Arc<dyn SourceManager>, |
| block_ref: BlockNumber, | ||
| notes: InputNotes<InputNote>, | ||
| tx_args: TransactionArgs, | ||
| source_manager: Arc<dyn SourceManager>, |
|
@igamigo - we probably will need to make corresponding updates in |
Upgrade Miden VM to 0.14.
Main Changes
asserts now take an error string directly rather than a code, the previous categorization of errors by range is no longer possible, so it was removed. The errors in MASM were rewritten and the build script now extracts the constants with their strings instead. The auto-generated error files use a thinMasmErrorwrapper around the extracted string, that can compute the error code on demand (for use in tests). TheTransactionExecutorno longer holds a map of errors internally, becauseHost::on_assert_failedcan no longer return anExecutionError. Those are handled in the VM now.SourceManagertovm_processor::executeinTransactionExecutor. I thought of two options here:TransactionExecutorusing a builder-style approach, e.g.TransactionExecutor::new(...).with_source_manager(source_manager).TransactionExecutor::execute_transaction.With the first option, it's most likely that users would not set the source manager with such an API, because they're not forced to. This is a learning from #1294.
The latter option forces users to at least think about what source manager to pass. In a setting where no debug information is desired, an empty one can be passed. The docs of that function were updated to mention that.
Something similar applies to
TransactionProver, although I think we could also not expose the source manager here. In my mind, during proving we assume that the transaction witness comes from an already executed transaction, so I wonder if the error reporting is necessary here. For now though, I've added the source manager, but it's easy to remove.Edit: I tried making the
Arc<dyn SourceManager>a field inLocalTransactionProver, but that makes it no longerSend+Sync. That causes problems in the proving service. The same would be true forTransactionExecutor, if we go with the builder-style approach. Given that we want to move in the direction of making it send + sync (#1350), that would be a problem. Even the approach I've taken now might complicate that, though.Optimal Source Manager Setup
TransactionContext(which eventually usually callsTransactionExecutor::execute_transaction). To that end, I madeArc<dyn SourceManager>a field in the context. (That made it non-Sync, but that's not really a problem, just required some allows of clippy lints).build.rsscript already assembles the kernel at build time and so the source files are not part ofTransactionKernel::assembler. But I think we'd want that for debug purposes, so that if there's an error in the tx kernel, we'd get better errors. So the question is, can we somehow add the source files when we instantiate the assembler? The source files are available inconcat!(env!("OUT_DIR"), "/asm/kernels/transaction"), so my question is primarily about the best way to do this using theAssemblerorSourceManagerAPI.Error Diagnostics
One caveat is that in order to get these error messages that reference the source file, you have to either:
miden_miette,Diagnosticerror and discard the source error (in order to do "Display XOR source" (Guidelines for implementing Display and Error::source for library errors rust-lang/project-error-handling#27 (comment))),DiagnosticforTransactionExecutorError, and require that every error that wraps it does the same.So to illustrate, in the
TransactionExecutor, we're basically doing this standard wrapping ofExecutionError:Even with debug mode enabled in the assembler, debug mode passed to the processor and the source manager containing the source code of the erroring Masm, we get this - depending on the error library and the error handling:
Anyhow
prints
Miden Miette
prints
Note that in both cases of anyhow and miette, no source code is printed. For anyhow, it's because it simply doesn't do anything with
Diagnostic, so that's expected. For miette, because our wrappingTransactionExecutorErrordoes not implementDiagnostic, theinto_diagnosticcall presumably cannot access the sourceExecutionErrorbut onlycore::error::Error, so it cannot print the diagnostics either (this is me speculating).If we unwrap the source error in a
map_errcall:then we get:
which is an actual diagnostic. But that is not a solution, of course. I just did this as a sanity check.
The only real solutions might be to derive
DiagnosticforTransactionExecutorError, i.e.:It only seems to work when using
transparent, but I'm not really familiar with miette.Or to print the diagnostic instead of returning the source error, i.e.:
Note that both of these diagnostics are pointing to the wrong location in source code. The offending line is
push.4.3.2.1.25 mem_storew. The second line of the code is actuallyuse.miden::contracts::wallets::basic->wallet, so the code seems to be correct, just pointing to the wrong location.I pushed an example to the
pgackst-vm-diagnosticbranch. RunRUST_BACKTRACE=1 cargo nextest run --cargo-profile test-dev --features concurrent,testing prove_witness_and_verifyto reproduce.Conclusion
In my opinion, the
PrintDiagnosticapproach is by far the easiest and most likely to get through to the user. In particular, also because it does not require end users to use miden miette for error reporting. If an end-user really wants to use a custom "diagnostics printer", we could still include theExecutionErrorin the error just so that it is still accessible. So for now, I went with the print diagnostic approach.I'd be happy to get more insights on this or if I overcomplicated things, cc @plafer.
Follow-Up
TransactionKernel::assemblerand friends if a debug flag is enabled, if that results in better errors.ExecutionError, e.g.TransactionContext::{execute_code, execute_code_with_assembler}orCodeExecutor::{run, execute_program}. In our tests, we can just use miden-miette, but if miden-base users also use these, they should be made aware that they need to do the same in order to get the better errors.