Ledger interface: Return `BlockIndex` from `account_balance`

When writing a canister that maintains accounts on the ledger, it is hard to get a reliable notion of “how much is in there”, due to the asynchronicity of calls. You can call account_balance, but when it comes back it is hard to tell what transactions are already included in it, and which are not.

This would help if the response would contain the BlockIndex, e.g. as in

service : {
  // Get the amount of ICP on the specified account.
  account_balance : (AccountBalanceArgs) -> (Tokens, BlockIndex) query;
}

Incidentally, this would actually be a backward-compatible change, thanks to Candid subtyping.

Would this be worth doing?

2 Likes

The account balance is always at the last block index of the ledger at the moment the call is served. How would you use the block index exactly?

The crux is “The moment” – these are asynchronous calls, so it is hard to say when the call was answered. Message ordering can help if only one canister is involved, but else you really need this “time” stamp.

Consider canister A telling canister B that it has “just” transferred money into an account (including the BockIndex). Shortly afterwards canister B receives a balance response. It cannot know whether that response includes the effect of A’s transaction, or not. With a BlockIndex, it could.

If canister-A waits for the transfer call to come back with a success response, then tells canister-B that the money is transferred, and then canister-B makes the balance call, then the balance is sure to include the effect of A’s transaction.

If B makes a balance call before receiving A’s notification, or if A makes the notification call before the transfer callback comes back, then there’s no guarantee if the balance includes the effects of A’s transaction.

Exactly! And a timestamp would help.

The problem is even more pronounced when external users (not canisters) try to make sense of transfers and balances, especially when they use query calls for the balance (which has its own problems, but still).

Tangentially: The problem of understanding the order of update responses and query responses is however true for all canisters, which is one reason I’m happy we finally got the canister state counter, so we can help here by including the canister state counter in the update and query call response on the platform level.

1 Like

Oh, nevermind, in the discussion it looked as if I managed to convince the relevant parties of the usefulness of a real state counter for a canister (incremented with each state change), but eventually we only got the weaker version incremented with each code/settings change. I still expect that we’ll get the full version eventually, probably the moment someone inside DFINITY needs it for a concrete use-case.

2 Likes

I think the recommended practice for this kind of use case is to use subaccount. Then you would not rely on checking balance on the main account.

This video goes over the details https://www.youtube.com/watch?v=Hm-NWwiUQZw