Hello IC Developer Community
According to developers, managing cycles is a major pain point
Luckily, at CycleOps, we spend all our waking cycles thinking about one thing…cycles
That’s why our team is proud to open-source a new cycles-manager library, providing a simplified, permissioned cycles management framework for Internet Computer applications.
Motivation
Multi-canister applications tend to have the following architecture, with an index canister that holds the mapping and/or manages several child canisters. Currently, there is no library that developers can use to easily manage the cycles within a multi-canister architecture.
The cycles-manager library proposes a simple, flexible pattern for cycles management, with a “Battery” canister that implements the CyclesManager module and APIs, and a “Child” canister that implements the CyclesRequester module.
Concepts
The Cycles Manager
The Cycles Manager is placed in the index/battery canister and is responsible for transfering cycles to child canisters that request cycles and are permitted to do so by the cycles manager. It does this by:
- Maintaining a list of all allowed child canisters, and ensuring child canisters do not request more than their alloted cycles quotas.
- Tracking the aggregate cycles apportioned to all canisters, ensuring that the battery/index canister does not transfer more cycles than alloted by the aggregate quota
You can initialize the CyclesManager
by calling CyclesManager.init()
with the appropriate settings.`
CyclesManager.init({
// Make a default setting using a rate quota, such that
// each child canister can request a maximum of 1T cycles every 24 hours
defaultCyclesSettings = {
quota = #rate({
maxAmount = 1_000_000_000_000;
durationInSeconds = 60 * 60 * 24;
});
};
// Make an aggregate cycles quota (applies to all cycles transfer requests from child canisters)
// All child canisters can request a maximum of 25T cycles every 24 hours
aggregateSettings = {
quota = #rate({
maxAmount = 25_000_000_000_000;
durationInSeconds = 60 * 60 * 24;
});
};
// A child canister must request at least 50 Billion cycles per topup request
minCyclesPerTopup = ?50_000_000_000;
});
Adding a child canister
Adding a child canister to the Cycles Manager allows it to request cycles from the canister implementing that CyclesManager. You can add a child canister with
CyclesManager.addChildCanister(
cyclesManager,
<child canister principal>,
{
// Add a specific rate quota for this canister that
// allows it to burn through **2T cycles** every **365 days**
//
// Adding a specific quota for a canister will override the default canister cycles quota
quota = ?#rate({
maxAmount = 2_000_000_000_000;
durationInSeconds = 60 * 60 * 24 * 365;
});
}
);
Default Settings
Default settings provide a default cycles quota setting for all canisters added to the cycles manager. Unless a specific quota is specified for a canister in the child canister map, any added canister will fall back to use the default setting.
You can update the CyclesManager’s default cycles quota by calling
CyclesManager.setDefaultCanisterCyclesQuota(
cyclesManager,
// update to have the default canister cycles quota be 2T every 24 hours
#rate({
maxAmount = 2_000_000_000_000;
durationInSeconds = 60 * 60 * 24;
})
);
Aggregate Settings
Aggregate settings provide a security blanket for the index/battery canister in which the CyclesManager library is used. This allows the developer to specify a limit on how many cycles can be disbursed from the CyclesManger over a desired time period.
You can update the CyclesManager’s aggregate cycles quota by calling
CyclesManager.setAggregateCyclesQuota(
cyclesManager,
// update to allow 20T cycles transferred in aggregate every 24 hours
#rate({
maxAmount = 20_000_000_000_000;
durationInSeconds = 60 * 60 * 24;
});
);
Min Cycles Per Topup
You have the ability to specify a minimum cycles requested per each canister topup request. This helps prevent a rogue canister from attacking your battery canister by repeatedly issuing requests for a low amount of cycles (i.e. 10 cycles). Any topup requests made that have an amount lower than this minmum amount are rejected. It is recommended to set this limit to at least 50 billion cycles.
You can update the CyclesManager’s minCyclesPerTopup property by calling
CyclesManager.setMinCyclesPerTopup(cyclesManager, 50_000_000_000);
Interface
Any canister implementing the CyclesManager must implement the following interface in order for child canisters to be able to request cycles
public type Interface = actor {
// prefixed so that it won't conflict with any existing APIs on the implementing canister
cycles_manager_transferCycles: shared (Nat) -> async TransferCyclesResult;
};
The Cycles Requester
The cycles requester allows a child canister to easily request cycles from a battery canister implementing the CyclesManager Interface (shown in the Interface section of this document).
In order to do this, a child canister must do the following:
- The specific child canister must have been added to the battery canister’s cycles manager via
CyclesManager.addChildCanister()
. - Initialize the CyclesRequester in the child canister
// Example, if your battery canister id was "be2us-64aaa-aaaaa-qaabq-cai";
let batteryCanisterPrincipal = Principal.fromText("be2us-64aaa-aaaaa-qaabq-cai");
cyclesRequester := ?CyclesRequester.init({
batteryCanisterPrincipal;
topupRule = {
// Request 200 Billion cycles from the battery canister anytime when this canister is below 1T cycles
threshold = 1_000_000_000_000;
method = #by_amount(200_000_000_000);
};
});
- Add the
requestTopupIfBelowThreshold
to any update methods of your child canister.
public func yourUpdateAPI(): async () {
// before doing something, check if we need to request cycles
// most of the time, this will not run (only when cycles are needed!)
let _ = await* requestTopupIfLow();
// the rest of your function update logic here ...
};
Using the cycles-manager with production applications via CycleOps
The cycles manager works great for managing the cycles of multi-canister applications, but at the end of the day you still need a tool for monitoring and managing the cycles of the Battery canister(s) for your application. This is where using the CycleOps service gives you monitoring, accounting, and reliability with trustless, automated cycles management. Want to learn more about CycleOps? Keep reading here.
Credit
Thanks to the DFINITY grants team for sponsoring the development of this library.