Enable Canisters to Hold ICP

Outstanding responses preventing a ledger upgrade - it needn’t be that way.


Was the break in determinism just discovered? I know pjljw has been a hotspot for issues, but I wasn’t aware of something this big.


The break in determinism was discovered just a few hours ago because it caused the subnet finalisation rate to go down. While investigation is still on going, it is known that the metadata (ie number of pages dirtied by the canister) of a specific canister stored as part of canister state differs on different replicas. This causes CUP signing to fail as replicas did not agree on state hash, stopping execution and slowing the block rate. Will post more once more is known, team is working on a fix now.


This may not be obvious to readers of this thread (since this is his second post), so I thought I should point out for context that @Jan above is Jan Camenisch, the CTO of DFINITY. :slightly_smiling_face:


Update on enabling canisters to transfer ICP

TL;DR: The Foundation will resubmit the proposal to upgrade the ledger and enable canisters to transfer ICP. The Foundation will vote for adopting the proposal.

Yesterday, voting on proposal 30496 to upgrade the Ledger and enable canisters to transfer ICP saw 18.9% votes for adoption and less than 0.1% for rejection with only half an hour to the deadline. Unfortunately, a technical issue in application subnet “pljlw” made it risky to perform the upgrade to the Ledger canister at the same time. Important to note, the issue with subnet “pljlw” is unrelated to the ICP transferability upgrade. Fortunately, the subnet is back to running normally. As promised, the Foundation is resubmitting the upgrade proposal to update the Ledger canister and enable ICP transferability. Given the majority of the community was in favour of adoption, this time, the Foundation will vote in favor of the upgrade proposal.


I am confused why this has to do so like this, which sounds like a waste of time. You said that “the issue with subnet ‘pljlw’ is unrelated to the ICP transferability upgrade.” Why cannot jut let the ICP transferability upgrade? As per this saying, if one day there are millions of subsets and if one subset has this kind of issue and we will not be able to do any of this kind of upgrade things? Is my understanding right?

Currently, converting ICP to Cycles to top-up a canister triggers a call-tree involving the ledger and the canister to be topped up. If the canister to be topped up is on an unresponsive subnetwork, then the call-tree may not terminate and the ledger cannot be upgraded (since atm, to safely upgrade a canister the canister needs to be stopped).
We will remove this dependency (either by eliminating the need to stop the canister, or via different flows for ICP conversion to cycles so that the dependency is removed). So, upgrading the ledger will be completely independent of the behavior of other canisters/subnetworks.


Here is the resubmitted proposal.
Canisters are now able to transfer ICP!


I wonder is the phase of the nns ICP wallet when we accidentally delete it, although we still save the phase, but we can’t backup the wallet, is there any way to recreate it?

Trying to use the new interface… what am I missing?

❯ dfx canister --network ic call "ryjl3-tyaaa-aaaaa-aaaba-cai" account_balance "(record { \"account\" = blob \"\a8\137Tw\ca\0d\04KeY2r(6P\d2<\0a\c0\ac\bcnH\ca\d5\e6\fc\"})"

The Replica returned an error: code 5, message: "Canister ryjl3-tyaaa-aaaaa-aaaba-cai trapped explicitly: Panicked at 'Deserialization Failed: "Fail to decode argument 0 from table0 to record { account : vec nat8 }"', /builds/dfinity/ic/rs/rust_canisters/dfn_core/src/endpoint.rs:34:41"

Using the exact same interface (pulled from the proposal’s github link) on a local test canister works just fine:

❯ dfx canister call legends account_balance "(record { \"account\" = blob \"\a8\137Tw\ca\0d\04KeY2r(6P\d2<\0a\c0\ac\bcnH\ca\d5\e6\fc\"})"

(record { e8s = 0 : nat64 })

If you have accidentally deleted the recovery phrase then you can simply create a new recovery phrase which will be different from the phrase you had before. Is your question if you can somehow re-install the old recovery phrase into your internet identity instead of getting a new, different one?

how can i create a new phase once i have checked it in the approved devices section nns please guide me

I got exactly the same error as @jorgenbuilder

The Replica returned an error: code 4, message: "IC0503: Canister ryjl3-tyaaa-aaaaa-aaaba-cai trapped explicitly: Panicked at 'Deserialization Failed: "Fail to decode argument 0 from table0 to record { account : vec nat8 }"', /builds/dfinity/ic/rs/rust_canisters/dfn_core/src/endpoint.rs:34:41"

Context: a canister that tries to get its own balance.
You can find the code here.

EDIT: it is now fixed, the account id hash was missing.


@quint , @jorgenbuilder

I believe the problems you’re facing with ledger interface are caused by the fact that you’re trying to use canister principals as ledger account identifiers. Principals and ledger account identifiers are different identifiers (even though the ledger account id is derived from the principal of the owner and a subaccount):

$ dfx identity get-principal 

$ dfx ledger account-id

I know this is very confusing, but it’s how the ledger was originally designed, and there is some reasoning behind this.

We’re planning to release a few support libraries and examples demonstrating how to correctly talk to the Ledger, a formal specification for the Ledger canister, and a reference implementation in Motoko. This will happen in the next few weeks. I’ll also give a community conversation talk on these topics.

If you’re already eager to start building things, you can use the following draft PRs as your point of departure:


Thanks for the response! Just to clarify, the example above uses account ids.

1 Like

Thanks for the response! Just to clarify, the example above uses account ids.

Not really:

    public type AccountIdentifier = Blob;

    public func balance() : async Ledger.ICP {
        await ledger.account_balance({
            // same as Blob.fromArray(Blob.fromPrincipal(Principal.fromActor(this), null)); 
            account = Blob.fromArray(AccountIdentifier.fromPrincipal(Principal.fromActor(this), null));

This code converts a Principal into a blob, not computes an account identifier from a principal.
For the reference, this is the proper way to compute account identifier from a principal: Draft: a canister that transfers funds using ICP Ledger API by roman-kashitsyn · Pull Request #123 · dfinity/examples · GitHub

  public func accountIdentifier(principal: Principal, subaccount: Subaccount) : AccountIdentifier {
    let hash = SHA224.Digest();
    let hashSum = hash.sum();
    let crc32Bytes = beBytes(CRC32.ofArray(hashSum));
    Blob.fromArray(Array.append(crc32Bytes, hashSum))

The default subaccount is 32 zero bytes.


It uses this package to convert a principal to an account ids, not Blob, am I missing something?..

How are these the same?

// same as Blob.fromArray(Blob.fromPrincipal(Principal.fromActor(this), null));
account = Blob.fromArray(AccountIdentifier.fromPrincipal(Principal.fromActor(this), null));


Ah, thanks for the clarification, I missed the fact that an external package is used. Unfortunately, this package doesn’t include the extra 4 bytes (CRC32 of the hash) into the account identifier as indicated in the ledger candid interface docs. So the ledger canister fails to decode a 32 byte array from the message.


This fixed it. :sweat_smile: I guess I need to read my own documentation
Thanks a lot @roman-kashitsyn!


Is this the official ledger canister on mainnet?


(The id and interface differs from what the rust code uses but I was able to query the balance of my canister after updating them.)

1 Like