-
Notifications
You must be signed in to change notification settings - Fork 18
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Accumulators for intra-block book-keeping #105
Conversation
The overall reason is that production chains will want to spend tokens but not mint. Of course the immediate motivation for me is that I only spending will need the scratchpad, and I want the code to be more isolated.
As an aside, I'd think "pallets" could work like this:
We do not require the spin lock per se, since the runtime is single threaded, but whatever. |
Oh crap, I haven't actually updated to executive to use the new accumulator logic. So there is still that part. But anyway this is coming together now, and some review on the design is very welcome. |
- `key_path` now returns `[u8]` instead of `str` - added and used various `From` implementations - implemented `ConstraintCheckingSuccess::transform` - fixed various tests - ran rustfmt Signed-off-by: muraca <[email protected]>
@JoshOrndorff I hope this commit is helpful. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good but lots of comment clean ups with regards to Todos and some other questions I had
Self::#variants2(inner) => inner.check(inputs, peeks, outputs).map_err(|e| Self::Error::#variants2(e)), | ||
Self::#variants6(inner) => inner.check(inputs, peeks, outputs) | ||
.map(|old| { | ||
//TODO I would really rather have an into or from impl for ConstraintCheckingSuccess, but that's just won't compile |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this still a necessary TODO comment?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I was stuck/confused on this last night. Matteo's transform
method is a good way forward.
pub provides: Vec<Vec<u8>>, | ||
pub requires: Vec<Vec<u8>>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
any better description for what these Vec<Vec<u8>>
are? An alias of any kind? if not is what it is.
/// This is a function and takes a value, as opposed to being a constant for an important reason. | ||
/// Aggregate runtimes made from multiple pieces will need to give a different initial value depending | ||
/// which of the constituent constraint checkers is being called. | ||
fn initial_value(_: Self::ValueType) -> Self::ValueType; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will this just always be the Default
for the ValueType
associated type? Should ValueType
just instead implement the Default
trait?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought about that same thing. All the examples of accumulators that I thought of can indeed be implemented using the default value as the initial value.
I ended up going with a separate method for these reasons:
- Semantics. While it seems the initial value of the accumulator will typically be the default, there is no fundamental reason it must be this way. Someone may write an unintuitive impl of
Default
for that purpose. - To mirror https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.fold
- The main one: For aggregate runtimes. When the constraint checker is composed with the macro from individual constraint checkers, it needs to do an actual match and return a different initial value depending on which piece is being called. Default does allow taking a param and returning a different result based on that param.
impl<A> From<ValidTransaction> for PreliminarilyValidTransaction<A> { | ||
fn from(transaction: ValidTransaction) -> Self { | ||
Self { | ||
provides: transaction.provides, | ||
requires: transaction.requires, | ||
priority: transaction.priority, | ||
intermediate_accumulator_value: None, | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@muraca What is the point of this one? When would we need to go in this reverse direction from valid to preliminarily valid?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll have to double check tomorrow, but I think I did this because it wasn't possible to use some of the ValidTransactionBuilder's fields
let inner_types = inner_types.clone(); | ||
let inner_types2 = inner_types.clone(); | ||
let inner_types3 = inner_types.clone(); | ||
let inner_types4 = inner_types.clone(); | ||
let inner_types5 = inner_types.clone(); | ||
let variants2 = variants.clone(); | ||
let variants3 = variants.clone(); | ||
let variants4 = variants.clone(); | ||
let variants5 = variants.clone(); | ||
let variants6 = variants.clone(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's gotta be a better way to do this right? Does anyone know? I'm pretty much a macro newb. This crate is by far the most complex one I've ever written.
Thanks for the reviews everyone. I know this PR is pretty far along, but I thought of an alternative design for tallying up fees and giving them to the block author and I need to write it out so we can consider them. My biggest complaint about the accumulators was always that they were not very UTXO native and were more like storage items, and this alternative design solves that problem. When users send transactions, they must ensure that the input coins exceed the value of the output coins. They also have the ability to protect those output coins with whatever verifiers they want. Typically it might require a signature from the intended recipient. Of course token senders can also specify outputs that are UpForGrabs and can be claimed by anyone. And this is the key to the alternate design. When you send tokens, you explicitly specify a tip to the block author by putting UpForGrabs. Only tokens that are explicitly included in an output as UpForGrabs may be claimed by the block author. If the input value exceeds the output value, then the difference truly is burned. Then block authors claim their reward by inserting a transaction at the end of the block that sweeps up all of those UpForGrabs coins that were created during the block. There is no risk that someone else will get to those coins first because the block author can simply drop any transactions that try to claim the UpForGrabs ones. The implementation would roughly be that when authoring, the block author notes all the UpForGrabs coins in a temporary storage that is cleared before the block ends just like how we do the extrinsics now. The key benefits are:
|
Okay, I'm going to close this one for now. Block rewards can be done more elegantly by the alternate design described above, and enforcing the right number of inherent calls can be done in the offchain pre-checks. If we ever decide we need it, we have the design done and can re-open this. |
Overview
This PR adds a feature that is called "accumulators" in the code and I've also considered calling "scratchpads". An accumulator serves as a temporary place for Tuxedo pieces to do some accounting within a single block. The accumulator will be cleared automatically by the Tuxedo executive at the end of each block, so the stored data will not be available in the next block, or even after the block's execution closes.
Usecases
Fit in Substrate
This feature is similar to paritytech/polkadot-sdk#359 in some ways. However it is implemented in the Tuxedo core which is entirely in the runtime. That issue asks for host functions and performance which this will not provide. This also does not satisfy @burdges's request to have the data in-memory and skip scale encoding.
Drawbacks
This feature is pretty un-UTXO-y in the sense that we are basically providing (a strict structured interface around) storage items to the Tuxedo pieces. I've tried very hard to avoid storage items. I even went to great lengths to handle inherents in a UTXO-native way in #100 and I'm glad I did.
The thing that makes me mostly okay with this is that the storage is cleared at the end of each block, so the data does not persist. I think the term "scratchpad" emphasizes this well as it is a temporary place to write some notes while doing a job and it is not part of the final project.
I have not yet thought of another way to achieve giving tips to block authors, a feature that has existed even since bitcoin. Indeed, even in bitcoin there is a dedicated variable for tallying such tips.
Refactor and Features
@clangenb I remember you saying you like it when the refactor goes in first and the feature that motivated comes in another PR. That has stuck with me. Once I get this ironed out, I plan to separate the core feature from the way I'm using it in the money piece, and put them in separately. Thanks for inspiring more discipline.