cspr-rad / kairos

Apache License 2.0
2 stars 0 forks source link

Reduce WASM size to fit into production chainspec #143

Closed koxu1996 closed 1 month ago

koxu1996 commented 1 month ago

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 :space_invader:. 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:

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

Avi-D-coder commented 1 month ago

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 commented 1 month ago

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 commented 1 month ago

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

koxu1996 commented 1 month ago

@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 :fearful:. 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 commented 1 month ago

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

koxu1996 commented 1 month ago

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 commented 1 month ago

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

koxu1996 commented 1 month ago

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

Avi-D-coder commented 1 month ago

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