Q5: Principal主体、HashMap、Cycles和stable变量

Principal, Hashmap, Cycles (how to deal with upgrades) & stable variables

Principal 主体身份

 Principal 主体身份的概念是特定于IC互联网计算机的。

 Principal 主体是 IC 上所有实体的唯一标识符

  • canister容器有自己的主体 Principal (对应于canister容器 ID)
  • 每个用户都有自己的主体 Principal 。
  • 钱包有它自己的主体 Principal 。
  • 可以运行以下dfx 身份命令来访问 主体 Principal 。
dfx identity get-principal
ubetf-42t5l-l64h6-ljrqr-6ztbu-tanvs-jrwiv-a45x4-ucoxp-cqr4i-mqe //My dfx principal

这里程序也有一个主体 Principal 。


IC 上的每条消息msg都包含有关呼叫者主体的信息。 程序可以使用以下语法在 Motoko 中访问此信息。

public shared(msg) func whoami() : async Principal {
    let principal_caller = msg.caller;
    return(principal_caller);
};

上面使用 msg.caller 访问msg信息主体。 也可使用此语法:

public shared({caller}) func whoami() : async Principal {
    return(caller);
};

在互联网计算机上有一个特殊的主体,称为Anonymous 匿名主体。 该主体的文本版本是 2vxsx-fae。 它对应于任何未经身份验证的用户。


最后,在 Motoko 中,有一个用于对主体进行基本操作的Principal模块。

HashMap 哈希图

An HashMap is a key / value store that allow you to store elements of type value and later retrieve them using an element of type key. Usually, we note the type of the keys : K & the type of the values : V.

HashMap 是一种key / value存储,允许程序存储值类型的元素,然后使用键类型的元素检索它们。 通常,我们记下键的类型:K 和值的类型:V。

You can create an HashMap and use it by importing the HashMap module (don't forget the capital M).
This is how you would instantiate your first HashMap, with Keys of type Principal and value of type Name.

程序可以创建一个 HashMap 并通过导入 HashMap 模块来使用它(不要忘记大写的 M)。

这就是程序实例化第一个 HashMap 的方式,其中key的类型为 Principal,value的类型为 Name。

import HashMap "mo:base/HashMap";
import Principal "mo:base/Principal";
actor {

    let anonymous_principal : Principal = Principal.fromText("2vxsx-fae");
    let users = HashMap.HashMap<Principal, Text>(0, Principal.equal, Principal.hash);
    users.put(anonymous_principal, "This is the anonymous principal");

    public func test() : async ?Text {
        return(users.get(Principal.fromText("2vxsx-fae")));
    };

};

此时,程序 Motoko 语法:HashMap.HashMap 意味着从 HashMap 模块导入 HashMap 对象。

Then we have three arguments to instantiate the HashMap.

  • 0 corresponds to the initial capacity of the HashMap. The capacity will automatically grow for you everytime you reach the maximum capacity of the HashMap, you don't need to worry about it 🥳.

  • Principal.equal is needed to compare the Keys.

  • Principal.hash is needed to hash the Keys.

If you are not familiar with the concept of hash and hash table, I recommend watching this video .

如果您不熟悉哈希和哈希表的概念,我建议您观看此视频。

I really encourage you to understand the inner working of the HashMap, that way you'll get why we need to provide Principal.equal & Principal.hash.

Let's move to the pratical application. You can add values inside the HashMap using the following syntax.

我真的鼓励您了解 HashMap 的内部工作原理,这样您就会明白为什么我们需要提供Principal.equal 和Principal.hash。


让我们转向实际应用。 您可以使用以下语法在 HashMap 中添加值。

import HashMap "mo:base/HashMap";
import Principal "mo:base/Principal";
actor {

    let anonymous_principal : Principal = Principal.fromText("2vxsx-fae");
    let users = HashMap.HashMap<Principal, Text>(0, Principal.equal, Principal.hash);
    users.put(anonymous_principal, "This is the anonymous principal");

    public func test() : async ?Text {
        return(users.get(Principal.fromText("2vxsx-fae")));
    };

};

Here I have added the value "This is the anonymous principal" with the Key that corresponds to the anonymous principal.

Let's try to retrieve our value.

在这里,程序添加了值“This is the anonymous principal”以及与匿名主体对应的密钥2vxsx-fae。


现在让程序尝试找回我们的价值。

import HashMap "mo:base/HashMap";
import Principal "mo:base/Principal";
actor {

    let anonymous_principal : Principal = Principal.fromText("2vxsx-fae");
    let users = HashMap.HashMap<Principal, Text>(0, Principal.equal, Principal.hash);
    users.put(anonymous_principal, "This is the anonymous principal");

    public func test() : async ?Text {
        return(users.get(Principal.fromText("2vxsx-fae")));
    };


};

在 Motoko playground(Playground链接:    https://m7sm4-2iaaa-aaaab-qabra-cai.raw.ic0.app/ )中部署此 Actor 并运行测试将返回:

(opt "This is the anonymous principal")

Cycles 费用成本

IC互联网计算机上的每个canister容器都会消耗cycles。 这些用于衡量和支付计算和存储费用。


这是一个表格,总结了每个常见操作的cycles成本。

Table 1. Cycles Cost per Transaction (as of July 26, 2021)

TransactionDescriptionAll Application Subnets

Canister Created

For creating canisters on a subnet

100,000,000,000

Compute Percent Allocated Per Second

For each percent of the reserved compute allocation (a scarce resource).

100,000

Update Message Execution

For every update message executed

590,000

Ten Update Instructions Execution

For every 10 instructions executed when executing update type messages

4

Xnet Call

For every inter-canister call performed (includes the cost for sending the request and receiving the response)

260,000

Xnet Byte Transmission

For every byte sent in an inter-canister call (for bytes sent in the request and response)

1,000

Ingress Message Reception

For every ingress message received

1,200,000

Ingress Byte Reception

For every byte received in an ingress message

2,000

GB Storage Per Second

For storing a GB of data per second

127,000

每个canister容器都有自己的cycle余额,并且可以通过消息将cycle传输到其它canister容器。


在 Motoko 中,程序可以使用 ExperimentalCycles 模块来运行和试验循环。 (这个模块将来可能会被修改)。这是免费获取canister余额的方法。


import Cycles "mo:base/ExperimentalCycles";
actor {

    public func balance() : async Nat {
        return(Cycles.balance())
    };
};

Each message sent on the IC contains a number of cycles attached to it. You can look the available amount with the following code.

IC 上发送的每条消息都包含许多附加的cycles。 程序可以使用以下代码查看可用金额。

import Cycles "mo:base/ExperimentalCycles";
actor {

    public func message_available() : async Nat {
        return(Cycles.available())
    };
};

如果程序想让用户按cycles付费才能访问某项功能,程序可以这样编写:

import Cycles "mo:base/ExperimentalCycles";
actor {

    let AMOUNT_TO_PAY : Nat = 100_000;
    public func pay_to_access() : async Text {
        if(Cycles.available() < 100_000) {
            return("This is not enough, send more cycles.");
        }:
        let received = Cycles.accept(AMOUNT_TO_PAY);
        return("Thanks for paying, you are now a premium user 😎");
    };
};


Stable variables/Stable变量

默认情况下,当升级canister容器软件时,将丢失canister原先上所有状态信息与数据。 😢

假设程序上有一个名为 counter 的变量,该变量之前已被递增过;在 canister软件升级后该变量的值将被重置,不是软件升级前的数值。

actor {

    var my_name : Text = "";

    public func change_name(name : Text) : async () {
        my_name := name;
    };

    public func show_name() : async Text {
        return(my_name)
    };

};

以下是可以使用此 actor 进行的实验(在 Motoko playground上部署后)

Playground链接:    https://m7sm4-2iaaa-aaaab-qabra-cai.raw.ic0.app/

change_name("Motoko");
show_name()  // "Motoko"


现在可添加新代码并重新部署canister容器,对容器软件升级:

actor {

    var new_value : Text = "Let's upgrade";
    var my_name : Text = "";

    public func change_name(name : Text) : async () {
        my_name := name;
    };

    public func show_name() : async Text {
        return(my_name)
    };

};
show_name() // ""

从结果看起来canister容器忘记了它的名字内容。幸运的是,有一种方法可以在 Motoko 中保持升级状态。可以使用stable变量来做到这一点:


actor {

    stable var my_name : Text = "";

    public func change_name(name : Text) : async () {
        my_name := name;
    };

    public func show_name() : async Text {
        return(my_name)
    };

};

如果您尝试与此actor执行相同的操作套件,会注意到my_name的值在升级过程中保持不变。

不幸的是,只有一些变量/对象可以被定义为stable。例如,HashMap 不能被定义为stable。


在这些情况下,您需要使用以下系统函数挂钩(systems hooks)方法。

 system func preupgrade() {
    // Do something before upgrade
  };

  system func postupgrade() {
    // Do something after upgrade
  };
}

上面程序的诀窍是使用 preupgrade 方法将所有数据放入stable变量中,并使用stable变量重新初始化容器状态。

(For more informations : https://smartcontracts.org/docs/language-guide/upgrades.html)

Q5: Principal主体、HashMap、Cycles和stable变量
arkMeta Crypto Network Limited, arkSong 2023年10月31日
标签
登录 留下评论

Q04:自定义类型和链接列表
Custom type and Linked list