Evaluating Compute Pricing in Response to Increased Demand on the Internet Computer Protocol

Can we test this changes related to change in costs locally using pocket ic?

I fully agree, and I don’t think that’s actually what we did here. Scalability of ICP comes from adding more subnets. However, for sound tokenomics, it must be the case that highly loaded subnets burn more ICP for cycles than the node providers receive. In the high load that we saw, this was not the case: we saw highly loaded subnets that did not burn a ton of cycles. So with the cycles pricing adjustments discussed in this thread, we addressed those specific scenarios.

So TLDR: scalability comes from adding subnets, and with correct cycles pricing, it means that as ICP gains more and more load and more subnets are added, this all is good news for ICP token holders.

5 Likes

@Manu please consider adding a co-location fee or something to avoid a possible ā€œDOS attackā€ on a subnet by creating too many canisters.

Thank you for clearing up my misunderstanding. much appreciated

1 Like

@manu @bjoernek - is it possible to revisit this decision based on the fact that the DOLR AI canisters are artificially spiking cycle costs on select subnets?

They are on the same subnet as Dragginz, and that’s just another reason why we’re taking our time deploying anything.

2 Likes

A bit of a technical question here…I’ll also tag @free and maybe a motokoder in @ggreif:

If I’m running an update call, I get that it is 5M base fee now.

If I call a query, say I hit a ledger to query the latest blocks…the call gets upgraded to an update intercanister call and I get charged 260k + some byte cost to call and the ledger ends up paying its own 5M base fee for the message.

The question: 1) When the ledger returns, does it pay an inter-canister fee of 260k + data returned byte gas, or is some or part of this ā€˜free’ with the return?
2) When my canister gets the response, likely in a new round if a different subnet, do I pay a second 5M base fee? or maybe rather is the return a new message and charged as such? What if they were on the same subnet?

  1. The caller pays for all bytes transferred (request and response) and for the inter-canister call fee (once, because this is one call). For each response, the protocol reserves enough cycles for the maximum payload size (2 MB), then refunds the difference.
  2. The message base fee (5M cycles) is charged on every message execution: in your case, once for handling the (canister or ingress) request; and once for handling the response. Remember the difference between an ā€œupdate methodā€ and a message execution: an update method that contains await statements is (most likely, with the exception of synchronous failures) executed as a number of separate messages (one for the incoming request and one for every response / await; with each message execution its own atomic transaction, hence the cost).
    It makes no difference whether the call was addressed to someone on the same subnet or a different one; or even if it was a self call.

So my next question would be for the motoko team, @ggreif, @claudio, @luc-blaeser:

I seem to recall that there may have been some optimizations in the motoko engine that would detect a self await call and continue on without dumping back to the replica if there were enough cycles left in the round? In these instances, would it still be counted as a message? Perhaps I’m misunderstanding this and awaits always go back to the replica for scheduling but I thought it would be worth asking.

I believe compute and storage costs should ideally drop over time so that fully on-chain DApps like RuBaRu can scale sustainably on the Internet Computer. Of course, there should be safeguards against misuse and the network should scale with demand.

With RuBaRu’s user base steadily growing and more content being uploaded on-chain, we’ve been keeping a close eye on costs. There’s definitely still room for us to optimize our backend, but the network also has to stay economically viable. One of our biggest learnings so far has been lazy on-chain resource usage. We’re optimistic that Moore’s Law will play out on the Internet Computer over time.

If this is true (I have no idea if it is or not) then it would be quite error prone. Each message execution is supposed to be atomic. If Motoko (or some CDK) more or less arbitrarily decides when to merge multiple message executions into one, you will never be able to tell where a commit point may be. One would expect a commit at each await, but such a feature would make it so that only some (essentially randomly picked) awaits trigger a commit.

One example of where this might cause significant issues is using a timer / heartbeat to do work: if you do the work directly, any trap causes a silent rollback of the state, so you’ll never learn what happened; if instead you make a self-call from the timer / heartbeat handler, you have a chance to log the outcome of this self-call or do some other kind of error handling; if you explicitly do the latter but Motoko or the CDK decide to sometimes execute it as the former, you have a problem.

This makes sense. I guess my quest then is when and where does the 5,000,0000 base fee get charged? Is it when and if there is a state commit? Is it at the beginning of a round and is charged even if there is a trap?

Is it possible that if inky get charged for one message if my canister gets multiple executions in one ā€œroundā€.

My end goal here is to calculate the cycles used when I perform a particular action.

I suspect the most conservative approach would be to assume that each await charges at lest 5,000,000. But then a follow up is what about one shots(do I get the 5M charge when the response comes back but motoko ignores it? I don’t that we ever resolved that thread of inquiry either.

Maybe a deeper question here is: where does the cycles accounting happen? Is motoko counting things and reports to the replica? Or is it all replica? Or maybe a hybrid?

It’s Execution (i.e. the replica) that decides what you get charged for and when.

From looking at the code, it looks like for ingress messages and requests you prepay (the 5M plus maximum number of instructions), then get refunded the difference in instructions at the end. You also prepay (5M plus maximum instructions plus maximum payload size) for every call that you make, at the end of the round when the request is enqueued. You get refunded for the payload when you start executing the response; and for the remaining instructions when you complete execution of the response.

Which would seem to imply that you also pay the 5M (plus any payload) for one shot calls. Because, as you noted, there still exists a response, only with a function index of -1 in the corresponding callback. So while you’re not actually executing anything, you’ve already prepaid the 5M when you made the (one shot) call and you’re only getting refunded for having executed zero instructions So one shot calls with huge response payloads may turn out to be quite expensive.