Skip to content
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

Reduce WASM size to fit into production chainspec #143

Merged
merged 4 commits into from
Jul 8, 2024

Conversation

koxu1996
Copy link
Contributor

@koxu1996 koxu1996 commented Jul 8, 2024

Context

In Casper v1.5.6 maximum contract size is 1 MiB (1,048,576 bytes), which we fairly exceed with demo contract (1.4 MiB), therefore making Kairos incompatible with production network.

Can we reduce it?

I was suspecting that Risc0 verifier might be the biggest part of final WASM, so I did some tests on bare minimum contract. However, bare Risc0 verifier (+ bincode2 deserialization for receipt) is less than 20 KB 👾. Casper API calls are also quite small (few kilobytes), so it means we should be able to get way less than 1 MiB.

First easy optimization

Simple idea: we can enable more aggressive optimization in Rust complier and WASM optimizer.

I run benchmark for WASM produced by nix build -L --no-link --show-trace .#packages.x86_64-linux.kairos-contracts:

  • Before PR: 1.4MiB (1,374,581 bytes)
  • Setting Rust o='s': 1004KiB (1,025,823 bytes)
  • Setting Rust o='z': 904KiB (921,813 bytes)
  • Setting WASM o='s': 1.3MiB (1,322,590 bytes)
  • Setting WASM o='z': 1.3MiB (1,320,589 bytes)
  • Setting Rust o='s' and WASM o='s': 964KiB (985,116 bytes)
  • Setting Rust o='s' and WASM o='z': 964KiB (983,713 bytes)
  • Setting Rust o='z' and WASM o='s': 868KiB (886,560 bytes)
  • Setting Rust o='z' and WASM o='z': 868KiB (884,798 bytes) ⬅️ This PR.

After those optimizations we are using 84.77% of production WASM limit 😌. Still much, but it was low hanging fruit.

@koxu1996 koxu1996 self-assigned this Jul 8, 2024
@Avi-D-coder
Copy link
Contributor

i had experimented with this and had not had much luck. If you can get it to pass the contract tests I'm all for it.

@Avi-D-coder
Copy link
Contributor

Risc0 verifier (+ bincode2 deserialization for receipt)

Make sure you use the code when judging the size, only code paths you use and ones that could be called by dyn trait make it into the optimized binary. Our contract also has std from risc0 transitive dependencies linked in.

@marijanp
Copy link
Contributor

marijanp commented Jul 8, 2024

Looks good, any idea why we are running out of gas in the tests?

Previously it was using 2500 (default), but it seems to be more costly
after code-space optimization.
Copy link
Contributor

@jonas089 jonas089 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Nice optimization.

@Avi-D-coder Avi-D-coder merged commit 54c0b1e into main Jul 8, 2024
4 checks passed
@marijanp marijanp deleted the feature/heavily-optimized-wasm branch July 8, 2024 14:41
@koxu1996
Copy link
Contributor Author

koxu1996 commented Jul 8, 2024

@Avi-D-coder @marijanp Contract calls in test engine are using the default payment, which is 2500 CSPR.

Since we optimized code for size, it might be more expensive to execute. I just measured it and previously the cost was 1043 CSPR, and after full size optimization it is 2756 CSPR 😨. For example just by setting Rust o='s' we get 1004KiB (still fits in production chainspec) and execution takes 1588 CSPR.

We should find a good balance between WASM size and cost of execution, but I think compatibility with the production chainspec is a priority here.


Code used for testing:

        let cost = self
            .builder
            .get_last_exec_results()
            .unwrap()
            .get(0)
            .unwrap()
            .cost();
        panic!("Cost of execution: {:?}", cost);

@Avi-D-coder
Copy link
Contributor

@koxu1996
Copy link
Contributor Author

koxu1996 commented Jul 8, 2024

Risc0 verifier (+ bincode2 deserialization for receipt)

Make sure you use the code when judging the size, only code paths you use and ones that could be called by dyn trait make it into the optimized binary. Our contract also has std from risc0 transitive dependencies linked in.

Yup, I made unsuccessful verification trigger panic, so Risc0 code was definitely used there.

@koxu1996 try build-std https://doc.rust-lang.org/cargo/reference/unstable.html

On the one hand Risc0 has a bug, where std is introduced in one of old dependencies (downcast-rs in rrs-lib). However, it gets optimized out, since it's not used in our code. Overall, std library is not present in Kairos contracts, so there is no need for build-std.

@Avi-D-coder
Copy link
Contributor

Hmm, that seems odd.
What is the main contributor to our binary size then?

@koxu1996
Copy link
Contributor Author

koxu1996 commented Jul 8, 2024

I don't like serde_json... but this needs more investigation, as we pull quite a lot of dependencies.

@Avi-D-coder
Copy link
Contributor

Try borsh we could not get bincode working in the wasm contract, float related issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants