Tutorial step to lower usage of accumulation storage#1
Conversation
alxmirap
left a comment
There was a problem hiding this comment.
Thanks for putting this together Emeric. I think this probably combines a few things that could benefit from being in different PRs, like 1) adding new functionality and 2) separating code out from previous versions.
I think it would be good to make a clear distinction between service's v1 and v2. v1 is instructive, showing the basic capabilities of Jam to have its state and use it in Ethereum style, but the point made in the tutorial that this is not efficient is very good: it justifies why we have a v2.
v1 should be much more accessible and good as an introduction for new-comers to understand the basic service design, and the difference between refine and accumulation, while v2 would be much more geared to better engineered solutions and our favoured design approach.
To this end, it should introduce the notion of builder, using Jam as a rollup with commitment to raw state kept in the builder, and using refine for verification of operations, and not for calculating their outcome.
Also, it should demonstrate how to leverage the D3L by importing and exporting segments.
At this stage, pre-images might be a complication that we could introduce later, as there is not a very good use-case for them yet.
As for the code itself, I think we do well by having a fully working solution, but I don't think we should highlight or even refer all of it in the tutorial itself. For example, I think the State implementation with a Merkle tree is probably more involved and complex than needs to be in a tutorial.
We also have a service, a builder, a builder/state and a service state that shares a lot with the builder state. We could simplify this, or at least conveniently bound it in our discussion.
For instruction, it would probably be enough to refer to some abstract properties of this state:
- it is a commitment to external state kept somewhere else
- it evolves by simply checking the transition's starting point is our current state, and updating it to the transition's next
- we verify in refinement that the state transition matches the operations we declared with it
| //) | ||
| .get_matches(); | ||
|
|
||
| let Some(input_path) = matches.get_one::<PathBuf>("input") else { |
There was a problem hiding this comment.
Does this allow us to have several "input" args in the command? What will the program do in that case, shouldn't we enforce only one of each kind?
There was a problem hiding this comment.
Since there is no parameter name, the argument is positional (this is arg[1] and output is arg[2]), trying to pass an arg[3] emit an error, so guess it can only be one (I am not too familiar with clap).
There was a problem hiding this comment.
Checking on parameter head, it also only allow a single one (I think clap infer multiplicity by type, eg e Vec implies multiple V I think).
| ) | ||
| .arg( | ||
| arg!( | ||
| --head <String> "Overload root hash for this state transition (rather than using db head)" |
There was a problem hiding this comment.
I don't quite understand what you mean by "overload" here. Could you make it a little clearer, please?
| if op.exported_segments.len() > 0 || op.processed_segments.len() > 0 { | ||
| // We store hash of segment content with a reference count. | ||
| let mut buffed_segments: BTreeMap<Hash, u64> = | ||
| get("buffed_segments").unwrap_or(BTreeMap::new()); |
There was a problem hiding this comment.
Maybe cached_segments instead? 'buffed' has a connotation of somehow 'improved' or 'polished'.
| buffed_segments.remove(p); | ||
| } | ||
| } | ||
| for p in op.exported_segments { |
There was a problem hiding this comment.
I am confused as to why we have reference counting here.
Should it be possible for a segment to be exported by more than one item?
Perhaps we want something simpler in a tutorial, and ignore this low-level management?
I was thinking more in terms of a package exporting some segments, then another package which makes the first a pre-requisite would import segments from that.
There was a problem hiding this comment.
Should it be possible for a segment to be exported by more than one item?
the hash is the hash of the segment content, so if we export two segments with same content we produce same hash, so we need the Rc.
Actually given what we put in the segments, this may not happen, yet being safe here makes things simpler I think.
Perhaps we want something simpler in a tutorial, and ignore this low-level management?
I added a note about it at the end of the markdown description, but I find it a bit more complex, as one need to fully understand the import export trust model.
At first I wanted to store the origin of the segment (either segment merkle root and index or workitem hash and index), but I did find no way to access it from refinement or accumulation, then I think a bit and realize this kind of tracking is somehow simpler.
The main point of tracking is to avoid double import (but in the markdown I mention it not being useful for this particular use case).
Not too sure here, I will give it some thoughts I think.
| fn set_balance(&mut self, account: AccountId, token_id: TokenId, balance: u64) { | ||
| let to_key = token_ledger::api::balance_key(token_id, &account); | ||
| if !self.balances.set(to_key.to_vec(), balance) { | ||
| unimplemented!("error on key collision"); |
There was a problem hiding this comment.
Why would we have a key collision here? Can't we replace an existing balance?
Or do we want to ensure that is done exclusively through transitions?
There was a problem hiding this comment.
we store over a u15 index in binary tree , I think I describe this point in another comment.
I have to give it some thoughts, initially I thought some custom small tree was fun, but not too sure anymore.
| } | ||
|
|
||
| // fail on key collision by returning false | ||
| pub fn set(&mut self, k: Vec<u8>, v: V) -> bool { |
There was a problem hiding this comment.
We seem to have this function both in here and in state.rs, with almost no differences.
There is probably more repeated code, and it would be better to avoid this repetition.
There was a problem hiding this comment.
At first I did not have this repeated code (single crate for builder and state with feature skipping the witness record). Then switch to this to make things simple. I think with my latest change in builder I may be able to do something about it (I record witness in a simpler way and may just be able to call state after).
| ); | ||
| } | ||
|
|
||
| fn process_transfer<S: StateOps>( |
There was a problem hiding this comment.
Is this still relevant? I was under the impression this should now be handled exclusively in the builder. The advantage of using the root state transition in Jam service is that we can operate as a roll-up, and all transfers and state-computation are done in the L2 (the builder).
Refinement and accumulation should be the L1, and worry only with correctness. So, we might have a verify balance in transition.rs, but probably not a set_balance or process_transfer.
There was a problem hiding this comment.
set_balance or process_transfer are needed so we can run others operations afterward, indeed we could skip writing the last one.
We also want to calculate the resulting state root to store it during accumulation, this calculation is done while when updating the values (really not optimal but simple). We cannot trust the builder to provide us the resulting root (we only trust the starting partial state since we can validate it during accumulation against previously known state root).
Co-authored-by: alxmirap <alexandre.pinto@parity.io>
Co-authored-by: alxmirap <alexandre.pinto@parity.io>
Co-authored-by: alxmirap <alexandre.pinto@parity.io>
…c into cheme/externalstate
| } | ||
| let witness_root = *result.root(); | ||
| for (key, value) in witness_key_values.into_iter() { | ||
| result.set(key, value); |
There was a problem hiding this comment.
related to my last comment (about why we record accessed hashes), here result set will update state root , so when we do init from witness, if the values are not correct we will have a different root.
When inserting a value, the new root is calculated against every sibling previously inserted from the witness.
* Creates a binary to start convert a user-friendly list of unsigned operations into a list of signed operations * Small fixes to the construction of the signed list of operations. * Various refactorings and some cleanup: - renamed `token-ledger-v1` to `token-ledger-service-v1`, to maintain the parallel with v2 - in the Tutorial, updated an example command since `cargo run` no longer requires expliction `-i` or `-o` options. However, there are some more changes in this command, and the documentation will have to be all reviewed later. - Added a paragraph describing the purpose of a new binary to convert user-friendly Json operations into fully-specified ones. - some changes to justfile to enable it to be invoked from any location, and not only from that of the `justfile`. Commands build-service, create-service, query-service and submit-file, and functions to get and save the last service id, have been successfully tested - added a command to connect to an RPC node, for the moment still unhandled. Ensures that we must have either this or output specified. - Extracts some functions in main.rs to make the code more readable. * Formatting * Adds options for connecting to an RPC node, and to build and submit a WorkPackage directly to it. WiP: still needs some refactoring to reduce the size of long functions. * Minor code adjustments * formatting and clippy * Fixes the calculation of max core * Move crates to v1 and v2 folders Also, builder has new features: - ability to receive a user-friendly JSON file without signatures and valid AccountIds (ie Public Keys) - ability to connect to an RPC node and submit Work Package directly without having to encode it first.
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
…, adds an introductory section on PVM debugging (#3) * Creates a binary to start convert a user-friendly list of unsigned operations into a list of signed operations * Small fixes to the construction of the signed list of operations. * Various refactorings and some cleanup: - renamed `token-ledger-v1` to `token-ledger-service-v1`, to maintain the parallel with v2 - in the Tutorial, updated an example command since `cargo run` no longer requires expliction `-i` or `-o` options. However, there are some more changes in this command, and the documentation will have to be all reviewed later. - Added a paragraph describing the purpose of a new binary to convert user-friendly Json operations into fully-specified ones. - some changes to justfile to enable it to be invoked from any location, and not only from that of the `justfile`. Commands build-service, create-service, query-service and submit-file, and functions to get and save the last service id, have been successfully tested - added a command to connect to an RPC node, for the moment still unhandled. Ensures that we must have either this or output specified. - Extracts some functions in main.rs to make the code more readable. * Formatting * Adds options for connecting to an RPC node, and to build and submit a WorkPackage directly to it. WiP: still needs some refactoring to reduce the size of long functions. * Minor code adjustments * formatting and clippy * Fixes the calculation of max core * Move crates to v1 and v2 folders Also, builder has new features: - ability to receive a user-friendly JSON file without signatures and valid AccountIds (ie Public Keys) - ability to connect to an RPC node and submit Work Package directly without having to encode it first. * Add a fix to require 32K min stack size * Reviews and extends logging for easier debugging. * Fixes the signing and verification code to use the same admin key. Moves the basic structs and functions to token-ledger-common. * A couple more payloads useful for simple tests * Some notes related to debugging * formatting * Fixes some typos * fix duplicate code added in the merge
…ity (#4) * Creates a binary to start convert a user-friendly list of unsigned operations into a list of signed operations * Small fixes to the construction of the signed list of operations. * Various refactorings and some cleanup: - renamed `token-ledger-v1` to `token-ledger-service-v1`, to maintain the parallel with v2 - in the Tutorial, updated an example command since `cargo run` no longer requires expliction `-i` or `-o` options. However, there are some more changes in this command, and the documentation will have to be all reviewed later. - Added a paragraph describing the purpose of a new binary to convert user-friendly Json operations into fully-specified ones. - some changes to justfile to enable it to be invoked from any location, and not only from that of the `justfile`. Commands build-service, create-service, query-service and submit-file, and functions to get and save the last service id, have been successfully tested - added a command to connect to an RPC node, for the moment still unhandled. Ensures that we must have either this or output specified. - Extracts some functions in main.rs to make the code more readable. Moves the basic structs and functions to token-ledger-common. Adds an Extrinsic mode, to deliver the witness via extrinsic and outside the payload. This includes also a deep refactoring of the code, and commenting out everything related to pre-images. Extracts the concept of Execution Mode, and refactors it to recognise 3 types of packages: Immediate: the logic of the package's work item is executed immediately, and both refinement and accumulation are executed. Deferring: the logic of the package is verified to be valid, but accumulation is not executed. Instead, the operations and state are exported to the D3L Deferred: the package does not carry any operation or witness. These are retrieved from the D3L during refinement, and then the accumulation logic is executed, completing the operation started in the Deferring package. The builder has been expanded to create and submit the two related packages at the same time.
… to follow. * Reviews the tutorial write-up on handling segments * Removes the --extrinsic option. Now, we create the extrinsic only in the --connect-rpc mode. * small rewrites in the section about WorkPackages and WorkItems
This PR move the processing to an external state, making things more suitable.
This is currently in Draft, as it is very rough and the text is really not finished. (I would really appreciate help on redacting it).
There is multiple 'Mode':
@alxmirap I did not remove the preimage as I told you before, will likely do, but since it got a lot in common with the segment version I wonder if it can be ok (likely not, I will remove it later).