-
Notifications
You must be signed in to change notification settings - Fork 469
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
Increase max application program size beyond 8kb #6155
Comments
I just want to point out that right now the max amount of data the AVM loads for a single app eval is 16KB (8KB for program, 8KB for boxes). I imagine increasing that amount would have performance implications. We could do something simmilar to #6057 for called programs in a group, but I think that would really hurt composability because it's not immediately obvious to developers which apps can be called together. |
This is the problem. We don't put in arbitrary limits for the fun of it. When boxes were introduced, we created a system to ensure that, even if the boxes were big, the I/O caused by evaluating a transaction was not greater than it used to be. Unfortunately, we have no such quota system on programs. So, very large programs would, potentially, cause a great deal of I/O. Consider, for example, that one transaction can call 8 different foreign apps. If each of those apps was 32kb, we need to read 9*32kb for a single transaction. I do not have a great idea for fixing this. Using such large programs is pretty intensive for a chain that intends to run txns at 10,000 TPS. I have the unfounded belief that programs bigger than 8kb probably have a lot of room for optimization, so I would like someone to spend some time trying to understand if there are improvements to puya's compilation (presumably we're not talking pyteal anymore) that could save space. That could include adding opcodes for very frequent sequences that could be shortened. My not so great idea for fixing this more directly would be try and come up with a way to bring large programs into the box quota system. If you invoke a large program, that would count against your I/O quota somehow. I suspect people would quickly run into other limits because of that choice. A completely unfleshed out thought is to somehow make it possible to invoke a program in a box. Then we let the quota system work as is. |
The solution using what we currently have availible to us is just breaking down your logic into multiple on-chain apps with different apps implenenting different methods. The main problem with this approach is that application can't share state access, so you need a lot of inter-app communication (ie. scratch, global/local, itxns, etc.) which can get rather complex. Rather than allowing one program to be larger than 8kb, what if we allowed apps to share state access (both read + write, including boxes)? Presumably this would be an optional field in Edit: So I suppose I'm asking if developers really want programs larger than 8kb or if they just want > 8kb of logic to be able to interact with the same state. I would suspect it's the latter. I am aware though that this would also introduce a lot of challenges with regards to compilation, deployment, composability, etc. |
Correct - because box storage is opaque, to have independent 'helper' contracts expected to act as app a + (helpers b, c, d) doesn't work. You can't call A which calls B which then has to call back to A to read box state - no re-entrancy allowed. Alternative then becomes passing everything B might need as part of its call. There's also the issue of B knowing its being called from a 'true' A and the various machinations that might involve (and extra calls). |
I disagree with increasing the maximum program size at least for now that we do not have any super complex logic app on Algorand! Instead, I encourage better TEAL code optimization as we know higher-level compilers do not generate optimized TEAL code! Also trying to use better logic architecture and breaking down processes in DAG structure usually helps greatly with this IMHO. |
I'm hitting these limits right now and they are a constraint for me. The request stands. |
I've gone through the discussion and I've mixed feelings, so I'll leave my (not so strong) opinions:
This make me think that the "AVM data pooling" and "Program size quota" approach suggested by @joe-p and @jannotti could be a path to explore, in two ways (not mutually exclusive):
|
Compiler optimizations should be discussed as a separate matter. The marginal gains in usable program space achievable through code optimization would be an order of magnitude smaller than what is proposed here to enable solutions which require substantially more room for business logic. |
No, because it has nothing to do with this issue and you're sidetracking this issue. People asking for 4x the size isn't some 'well, you could save 30 bytes here...' issue. |
Let's talk these through.
At the top-level, you have to make two app calls to the same ID in order to have them (both) execute? I think that could work, it seems pretty straightforward to implement. I suspect I will hear how ugly it is forever, but I'm used to that by now. How does one create and update such large programs? It's already a bit worrisome that you can create enormous transactions with 8kb code size. It's certainly the single biggest opportunity to spam block space for min-fee. I suppose update is somewhat handled by the "two app calls" idea. You can only perform an update of 16kb if you have two calls to the app. (Need to make sure you can't update it twice with two 16kb programs - using double block space.). How do you "pay for" creating such a large program? You can't include two calls in the create, because the app has no id yet.
If I understand the idea here, this is a separate, different approach, right? I think I prefer this approach, unifying the quota system a bit, rather than adding another separate "please send an empty transaction" pooling mechanism. People seem to enjoy cringing about that kind of pooling. I might prefer to be explicit. A transaction would include a number, from 1 to 8, which is how many resource slots it's using for program access/update quota beyond 8kb. Under current limits, if you want to call a 10kb program, you would set that value to 2 (10kb-8k)/1k (because a resource slot gets you 1kb of box quota). I would be amenable to raising that 1kb quota to 2kb, to make this a bit more palatable, I think we were overly conservative there. It would also be nice that the rule is "use 1 resource slot for each extra program page over 3". There are still creation problems to work out, because now you can send one creation transaction with a 16kb payload. Maybe we can live with that. If we bump the amount per slot, you could send a 24kb payload. I'm not sure how much to worry about block space. There should also be a larger min balance requirement for the program's account holder, it is using more ledger space. We (well, I) forgot to add that when extra pages were introduced. Is it fair to say nobody wants to mechanism for incremental program updates? You could patch together a program piece by piece? I'm again a bit taken by the idea of using boxes to hold programs. I don't see a way to make that simple enough though. |
I actually think this would be the best approach. Essentially allow apps to give state read/write access to other apps. I imagine the implementation would be a new Any sort of pooling solutions runs into a potential problem with composability (ie. two large apps might not be able to interact with eachother, even if they implement specific ARCs) |
I already have to do this now for Reti as well as NFDs. You have to load from box storage because nothing larger than 4k can exist in scratch/stack/etc. So in box storage alone, the (one-time) MBR cost is pretty high just like 'extra pages' cost is fairly high. I have factory contract that has init/load/complete methods and load pages of bytecode into box storage. Later, the create app or update app loads the pages from box storage, ~4k at a time. Switching app storage completely to boxes would be a nice unifier tbh - certainly in costs and 'access' |
Changing how contracts are even 'called' seems like a big change though as there are big ripple-affects that can be felt for a long time beyond the change. Wallets need to change (particularly hardware wallets which always take a long time for updates), SDKs, and everything using them. It's already a bit awkward as-is with the reference model where even with ARC56 and an ABI method, you can't just compose a call to app X method Y and expect it to 'just work' even w/ simulate. You might need 3 dummy transactions just to cover the box references simulate/populate have to fill in. So if even just calling app X with no other references needed more because app X was >8k - that could get interesting. |
@jannotti When you say "unifying the quota system a bit," are you contemplating a change that would allow apps to have read/write access to the state, including boxes, of other apps? Joe has suggested extending box read/write to other apps' boxes, which I think would also be a transformational capability, so I want to clarify if that is on the table as we discuss application capabilities somewhat more broadly here. |
Regarding @SilentRhetoric's comment just above, if that is what is being referred to I would also support it. Extra transaction calls simply for accessing state from other apps in box storage is a pain. |
It's not even that - because of re-entrancy not being allowed and depending on app design, it may be impossible. |
No, that is not what I meant. I meant something closer to my response to @cusma, in which extra program space is explicitly paid for by MBR for ledger space, and resource references at run-time. And/or programs can be run from boxes, so code space is explicitly managed with the exact same quota system as boxes. I don't think making boxes readable/writable by other apps is such a great idea for a few reasons. 1) It doesn't fix the actual problem here (code size), it just makes a difficult work-around work slightly better. 2) It will require a whole new set of opcodes, because each access of a box will require specifying the app. 3) People will (rightly) conclude that since box state is readable, it is part of the public API of an app, so they will demand a standardization effort. I think that's bad for creating apps that can manage their own internals however they like, a basic principle of building reliable systems. 4) Writable boxes from other apps would require yet another extension that I think would have to be quite elaborate, managing which apps can write another app's storage, and maybe which boxes are writable? Incredibly dangerous, yet surely the complaints about code space are not going to stop just because boxes become readable. 5) It recreates some of the problems of re-entrancy (which we disallow) because app A can call app B, which can then look into A's state. But A may not be in a consistent state, since it is in the middle of execution. |
@pbennett , instead of attacking toward and accusing any opposite opinion to "sidetrack things" , may I suggest to keep it professional, adult and technical here (if you are able to). |
1- Code and compilers optimization |
@jannotti thanks for expanding on this! As a first step I just want to reach a point in which we have clear trade-offs about feasibility (performance) and complexity (both implementation and usage). Some observations:
This leads to my second suggested approach based on a "quota system": treating the foreign App ID references similarly to the Box ID references, to limit program size accessible at runtime. In this scenario 1 App ID would allow the access up to This being said, if we want to "generalize" the quota system to the App program and "unifying" the role of App ID and Box ID references, we would have a bit of disproportion between App bytecode and Box storage. If possible, increasing a Box "slot" to 2kb (equal to an App page size) would close a bit this gap, but it's just a nice-to-have. I admit I've not spent so much time thinking about a "nice API" to use this App ID based approach, a rough idea would be:
@jannotti this being said, I don't know if an approach based directly on Box IDs (as opposed to one based on App IDs) would be easier to implement / more elegant and usable. It seems to me that, in any case, the most elegant solution is introducing a quota system on program sizes. |
Status
The maximum program size for an application is currently 8kb. Once a contract is deployed, the program size is immutable.
This limit is currently constraining multiple builders in the ecosystem who have hit the limit and are using creative approaches to optimize program size. The limit could also constrain future upgradability of contracts that are deployed today with max program pages but may need to add additional logic in the future.
Expected
Raising the maximum program size would enable developers to create more capable applications on Algorand without resorting to contortive optimizations.
Solution
Increase the maximum program size substantively.
One approach that could be elegant is to align the max program size to the max box size, which is currently 32KB. This way, an app factory pattern could store program bytes in one box and use that to create new applications. If this approach was followed, any future expansions of the box storage limit would also apply to the maximum program size.
Dependencies
Unknown
Urgency
Relatively urgent. Three different ecosystem projects have recently escalated to DevRel that they have hit the program size limit, so the current limit is actively constraining builders in the ecosystem.
The text was updated successfully, but these errors were encountered: