Skip to main content

Working with the NNS

If your canister interacts with the NNS, you'll need to setup the NNS state in your tests. If you've already setup a full NNS state locally using the DFX NNS extension then you'll know that this can take a while to complete.

Fortunately, when writing tests with PocketIC you can restore the subnet state from a directory, which is much faster. This guide will walk through the process of creating this state directory for use in your tests.

Project setup

To not impact any other projects running on DFX, let's create a new project:

dfx new --no-frontend --type motoko nns_state

Change into the newly created directory:

cd nns_state

Add a local system network to the dfx.json file. This will create the appropriate network configuration for the NNS without affecting any other projects that are running on DFX:

dfx.json
{
// redacted...
"networks": {
"local": {
"bind": "127.0.0.1:8080",
"type": "ephemeral",
"replica": {
"subnet_type": "system"
}
}
}
// redacted...
}

Stop DFX if it is already running:

dfx stop

Start DFX with a clean network in the background:

dfx start --background --clean --artificial-delay 0

Install the NNS extension for DFX:

dfx extension install nns

Now, use this extension to setup the NNS. This can take up to a few minutes to complete:

dfx extension run nns install

The subnet state is now stored in the .dfx/network/local/state/replicated_state directory. Save the absolute path to this directory into a global variable for use later:

export NNS_STATE_PATH=$(pwd)/.dfx/network/local/state/replicated_state

Wait a few seconds to make sure there are no more logs from DFX coming in, and there is at least checkpoint in the .dfx/network/local/state/replicated_state/node-100/state/checkpoints folder, then stop DFX:

dfx stop

Getting the subnet Id

Install Rust

Install Rust according to the instructions on the official website.

Install protoc

On macOS, you can use Homebrew:

brew install protobuf

On Ubuntu, you can use apt:

sudo apt install protobuf-compiler

Install jq

On macOS, you can use Homebrew:

brew install jq

On Ubuntu, you can use apt:

sudo apt install jq

Using ic-regedit

Now clone the ic repository:

git clone git@github.com:dfinity/ic.git

Change into the ic directory:

cd ic

Get the subnet Id from your NNS state:

cargo run -p ic-regedit snapshot $NNS_STATE_PATH/ic_registry_local_store | jq -r ".nns_subnet_id.principal_id.raw"

This should output something similar to the following:

(principal-id)jnebg-dq662-hfu2t-2momi-md5lw-7qmvl-htn5r-52soa-6xbqp-6p5sd-aae

Save everything after the (principal-id) part for use later in your tests.

Copying the NNS state

Set the root path where you want to copy the NNS state to:

export TARGET_PATH=/path/to/tests

Copy the NNS state into your project:

mkdir -p $TARGET_PATH && cp -r $NNS_STATE_PATH $TARGET_PATH/nns_state/

The state directory includes a lot of files, so if you don't want to commit all of them to your repository, you can compress the state directory and commit the archive instead.

First, change to the directory containing the state:

cd $TARGET_PATH

Then compress the state directory:

tar -zcvf nns_state.tar.gz nns_state

Then when you need to use the state, you can decompress it from the root of your repository with:

tar -xvf path/to/tests/state/nns_state.tar.gz -C path/to/tests/state

This could be done with an npm postinstall script, by adding the following to your package.json:

package.json
{
// redacted...
"scripts": {
"postinstall": "tar -xvf path/to/tests/state/nns_state.tar.gz -C path/to/tests/state"
// redacted...
}
// redacted...
}

Using the NNS state in your tests

You'll need the subnet Id that you recored earlier:

const NNS_SUBNET_ID =
'nt6ha-vabpm-j6nog-bkr62-vbgbt-swwzc-u54zn-odtoy-igwlu-ab7uj-4qe';

And you'll need to reference the path to the NNS state:

const NNS_STATE_PATH = resolve(
__dirname,
'..',
'state',
'nns_state',
'node-100',
'state',
);

Now you can setup your PocketIC instance to use the NNS state:

const pic = await PocketIc.create({
nns: {
state: {
type: SubnetStateType.FromPath,
path: NNS_STATE_PATH,
subnetId: Principal.fromText(NNS_SUBNET_ID),
},
},
});

After creating the instance, make sure to set the PocketIc time to be the same or greater than the time that you created the NNS state:

await pic.setTime(new Date(2024, 1, 30).getTime());
await pic.tick();

Check out the NNS Proxy example for a full example of how to use the NNS state in your tests.