-
Notifications
You must be signed in to change notification settings - Fork 148
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
[Proposal] Differentiate cold and hot contracts #97
Comments
On the side note, the idea to target 1 second of compute may not be correct. There should be extra time to propagate receipts and execution outcome before starting the next chunk of compute. If we target 1 second block time, then it has to be taken into consideration. |
@evgenykuzyakov Good point. However, we are implicitly targeting 1/2 second because blocks that are more than 1/2 full will lead to a steady gas price increase. |
For the record, here are the formulas to compute our current TPS. Transfer TPS: Contract call TPS for 300KiB contracts: |
We don't need hottest contracts in the trie, but we need all contracts calls ordered by block-number in a queue. Then we can order them in memory using Once a node syncs the trie for a shard, it has to parse the moving window and reconstruct the cache of the contracts. Once the cache is constructed, the node has to pre-compile contracts to avoid delaying blocks. @bowenwang1996 Is there a callback when the sync is complete to finalize in-memory operations or do we update them on the fly? If we update them on the fly, then the node has to compile contracts on the fly from the moving window. |
There is no such thing. We can do this operation when we finalize state sync. How do you want to store the cache information in state? |
We need to have a history of successful calls in a trie similar to delayed receipts for a tracking window. |
To simplify everything we can store a singleton key-value record that keeps No need for state sync changes. The in-memory cache doesn't need changes, but it has to be at least the size of the persistent cache. The persistent cache will only be used for charging compilation fees |
@bowenwang1996 pointed out that it's too easily abusable by having 200 smaller contract and calling/compiling them. Another suggestion is to create a time-based cache (based on block height), but don't have expiration for the previous calls. Instead let them decay by half every epoch. A simple version is to increase weight per contract hash call by
Now we need to maintain top200 based on weight. The issue is smaller contracts (143 bytes) can kick out contracts from the top200 much cheaper than putting 300Kb contracts into it. Which makes it abusable. We can switch top200 cache into |
@bowenwang1996 and @mikhailOK suggested another option. We can do this at deploy time and increase the cost of deploy operation. It will be a one-off event and will not affect future function calls. Function calls will assume the contract is already pre-compiled and pre-processed. So the only extra cost is to read the cached version from disk. This assumes you've tracked the shard node from the beginning of times, but obviously it might not be the case. The disk cache can be shared cross-shards, so if you tracked a shard then when it splits you still have all of them pre-compiled. But when you sync to a shard, you have to start pre-compiling all contracts that you don't have or try to do this on demand. Pros:
Caveats:
|
I suggest that we not consider state sync done until the contracts are compiled to avoid potential cold cache attacks. We can spawn several thread to parallelize the process. In fact I don't think this is a concern for validators because they start catching up in the previous epoch and I think one epoch is for sure enough time for them to compile all the contracts. |
But it means you have to inspect all accounts and extract code that you need to prepare and compile. Some code compilation will fail, but we still need to cache the result. |
We can store the hashes of contracts in state so that it is easier to look them up.
Do you mean that people maliciously submit binary that cannot be compiled? If so why do we need to cache the result? |
We already have it on every account. Otherwise we have to do ref-counting per contract hash, but it's complicated during resharding.
Yes, you need to remember the attempt, so you don't retry |
What is the current speed difference between the best WASM interpreter and executing compiled code? Also can we save compiled code somewhere on disk? Ideally we should compile on deployment and charging gas for that on deployment time (during state sync would also need to recompile, but there is time for that) and store already compiled code in a separate storage location. |
WASM is also loaded from disk. We can measure precisely, but generally the speed of reading from a random location doesn't depend that much on the side that is being read.
That is the current plan I believe. |
Not based on the proposal outlined in this issue, as far as I understand |
|
FYI @bowenwang1996 and @mikhailOK 's proposal is still a protocol-level change that will also affect Applayer, because now contract calls cannot return preparation/compilation errors. |
Discussed it with @evgenykuzyakov . I agree that the modified proposals would work. I did quick computation. Compiling 100 200KiB contracts takes approximately 8 seconds. @evgenykuzyakov also has a good idea how to retrofit it with error messages without breaking our protocol too much. |
Contract compilation is expensive. We have introduced caching for compilation, but unfortunately we currently cannot have different fees for contracts that are in the cache versus that are not. This means that contract calls are priced based on the worst case scenario -- when every call leads to a compilation. Unfortunately, we cannot predict when contract will be compiled or not, because different nodes that implement the protocol can have different cache settings. However, we can enforce it:
contract_compile_base
andcontract_compile_bytes
fees, https://github.com/nearprotocol/nearcore/blob/master/neard/res/genesis_config.json#L137 . Requiring the node to store the compiled contract in cache.We would need to introduce 2 parameters for the runtime config:
We would need to store the list of the 200 hottest contracts in the trie the way we store delayed receipts. Note, they don't need to be ordered, we just need to store the entries: (
code hash, number of times the code was called in the moving window)
.The text was updated successfully, but these errors were encountered: