Best practices for a high traffic dapp launch

From Internet Computer Wiki
Jump to: navigation, search

Summary

Starting the project

Choosing a programming language

Motoko

Rust

Writing the dapp

Getting help

There are few resources where developers get help while building a dapp:

Testing the dapp

Stress testing

The IC does not have a testnet. Instead developers are encouraged to use the mainnet for test purposes. Before developers deploy the production ready dapp to mainnet and enable access to it to the world, they should first deploy test versions to mainnet. They should then stress it by emulating the anticipated load on it and see how well the dapp and the platform in general fares. If the desired performance is not achieved, developers are encouraged to get help by using the links provided above. Often times, the desired performance can be achieved by performing minor tweaks to the dapps.

Deploying the dapp

Launching the dapp to the world

Monitoring the dapp

Lessons from past launches

Motoko version

Motoko is a language being actively developed so it is recommended to use the latest version of Motoko to get maximal stability and performance.

Inter-canister calls[1]

The Internet Computer system provides inter-canister communication that follows the actor model: Inter-canister 'calls' are implemented via two asynchronous 'messages', one to initiate the call, and one to return the response. Canisters process messages atomically (and roll back upon certain error conditions), but not complete calls. This makes programming with inter-canister calls error-prone. Possible common sources for bugs, vulnerabilities or simply unexpected behavior are:

  • Reading global state before issuing an inter-canister call, and assuming it to still hold when the call comes back.
  • Changing global state before issuing an inter-canister call, changing it again in the response handler, but assuming nothing else changes the state in between (reentrancy).
  • Changing global state before issuing an inter-canister call, and not handling failures correctly, e.g. when the code handling the callback rolls backs.

If you find such a pattern in your code, you should analyze if a malicious party can trigger them, and assess the severity that effect. These issues apply to all canisters and are not Motoko-specific.

Rollbacks[1]

Even in the absence of inter-canister calls the behavior of rollbacks can be surprising. In particular, rejecting (i.e. throw) does 'not' rollback state changes done before, while trapping (e.g. Debug.trap, assert…, out of cycle conditions) does.

Therefore, one should check all public update call entry points for unwanted state changes or unwanted rollbacks. In particular, look for methods (or rather, messages, i.e. the code between commit points) where a state change is followed by a throw).

This issues apply to all canisters, and are not Motoko-specific, although other CDKs may not turn exceptions into rejects (which don’t roll back).

Talking to malicious canisters[1]

Talking to untrustworthy canisters can be risky, for the following (likely incomplete) reasons:

  • The other canister can withhold a response. Although the bidirectional messaging paradigm of the Internet Computer was designed to guarantee a response 'eventually', the other party can busy-loop for as long as they are willing to pay for before responding. Worse, there are ways to deadlock a canister.
  • The other canister can respond with invalidly encoded Candid. This will cause a Motoko-implemented canister to trap in the reply handler, with no easy way to recover. Other CDKs may give you better ways to handle invalid Candid, but even then you will have to worry about Candid cycle bombs that will cause your reply handler to trap.

Many canisters do not even do inter-canister calls, or only call other trustworthy canisters. For the others, the impact of this needs to be carefully assessed.

Time is not 'strictly' monotonous[1]

The timestamps for “current time” that the Internet Computer provides to its canisters is guaranteed to be monotonous, but not 'strictly' monotonous. It can return the same values, even in the same messages, as long as they are processed in the same block. It should therefore not be used to detect “happens-before” relations.

Instead of using and comparing timestamps to check whether Y has been performed after X happened last, introduce an explicit var y_done : Bool state, which is set to False by X and then to True by Y. When things become more complex, it will be easier to model that state via an enumeration with speaking tag names, and update this “state machine” along the way.

Another solution to this problem is to introduce a var v : Nat counter that you bump in every update method, and after each await. Now v is your canister’s state counter, and can be used like a timestamp in many ways.

While we are talking about time: The system time (typically) changes across an await. So if you do {{{1}}} and then await, the value in now may no longer be what you want.

Wrapping arithmetic[1]

The Nat64 data type, and the other fixed-width numeric types provide opt-in wrapping arithmetic (e.g. +%, fromIntWrap). Unless explicitly required by the current application, this should be avoided, as usually a too large or negative value is a serious, unrecoverable logic error, and trapping is the best one can do.

Shadowing of msg or caller[1]

Possible Attacks

Cycle balance drain attacks[1]

Large data attacks[1]=

References

Template:Reflist