Skip to content

Commit 8def27b

Browse files
fix: store wallets.json with backend data files (#4175)
Fixes https://dfinity.atlassian.net/browse/SDK-1918
1 parent b0bf41b commit 8def27b

15 files changed

+141
-48
lines changed

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,11 @@ Implement `dfx ledger allowance` subcommand that complies with the [ICRC-2](http
6363
- Add VetKD types and methods to management canister IDL
6464
- The VetKD test key id `Bls12_381_G2:dfx_test_key` is now enabled when starting `dfx` with `--replica`.
6565

66+
### fix: dfx will no longer try to use a nonexistent wallet after changing backend settings without --clean
67+
68+
After running `dfx start` with different options, dfx would try to use a wallet that was created
69+
on a previous run, which would fail. Now, dfx will create a new wallet if the settings have changed.
70+
6671
## Dependencies
6772

6873
### Motoko

e2e/tests-dfx/error_context.bash

+11-9
Original file line numberDiff line numberDiff line change
@@ -20,33 +20,35 @@ teardown() {
2020

2121
assert_command dfx identity get-wallet
2222

23-
echo "invalid json" >"$E2E_SHARED_LOCAL_NETWORK_DATA_DIRECTORY/wallets.json"
23+
WALLETS_JSON="$(shared_wallets_json)"
24+
25+
echo "invalid json" >"$WALLETS_JSON"
2426

2527
assert_command_fail dfx identity get-wallet
26-
assert_match "failed to parse contents of .*/network/local/wallets.json as json"
28+
assert_match "failed to parse contents of .*/network/local/[0-9a-f]+/wallets.json as json"
2729
assert_match "expected value at line 1 column 1"
2830

2931
assert_command_fail dfx wallet upgrade
30-
assert_match "failed to parse contents of .*/network/local/wallets.json as json"
32+
assert_match "failed to parse contents of .*/network/local/[0-9a-f]+/wallets.json as json"
3133
assert_match "expected value at line 1 column 1"
3234

33-
echo '{ "identities": {} }' >"$E2E_SHARED_LOCAL_NETWORK_DATA_DIRECTORY/wallets.json"
35+
echo '{ "identities": {} }' >"$WALLETS_JSON"
3436

3537
# maybe you were sudo when you made it
36-
chmod u=w,go= "$E2E_SHARED_LOCAL_NETWORK_DATA_DIRECTORY/wallets.json"
38+
chmod u=w,go= "$WALLETS_JSON"
3739
assert_command_fail dfx identity get-wallet
38-
assert_match "failed to read .*/network/local/wallets.json"
40+
assert_match "failed to read .*/network/local/[0-9a-f]+/wallets.json"
3941
assert_match "Permission denied"
4042

4143
assert_command_fail dfx wallet upgrade
42-
assert_match "failed to read .*/network/local/wallets.json"
44+
assert_match "failed to read .*/network/local/[0-9a-f]+/wallets.json"
4345
assert_match "Permission denied"
4446

4547
# can't write it?
46-
chmod u=r,go= "$E2E_SHARED_LOCAL_NETWORK_DATA_DIRECTORY/wallets.json"
48+
chmod u=r,go= "$WALLETS_JSON"
4749
assert_command dfx identity new --storage-mode plaintext alice
4850
assert_command_fail dfx identity get-wallet --identity alice
49-
assert_match "failed to write to .*/local/wallets.json"
51+
assert_match "failed to write to .*/local/[0-9a-f]+/wallets.json"
5052
assert_match "Permission denied"
5153
}
5254

e2e/tests-dfx/network.bash

+6-6
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,17 @@ teardown() {
3030
}
3131

3232
@test "create with wallet stores canister ids for configured-ephemeral networks in canister_ids.json" {
33+
echo "{}" | jq '.local.type="ephemeral"' >"$E2E_NETWORKS_JSON"
34+
3335
dfx_start
3436

35-
setup_actuallylocal_shared_network
36-
jq '.actuallylocal.type="ephemeral"' "$E2E_NETWORKS_JSON" | sponge "$E2E_NETWORKS_JSON"
37-
dfx_set_wallet
37+
dfx_set_wallet local
3838

39-
dfx canister create --all --network actuallylocal
39+
dfx canister create --all
4040

4141
# canister creates writes to a spinner (stderr), not stdout
42-
assert_command dfx canister id e2e_project_backend --network actuallylocal
43-
assert_match "$(jq -r .e2e_project_backend.actuallylocal .dfx/actuallylocal/canister_ids.json)"
42+
assert_command dfx canister id e2e_project_backend
43+
assert_match "$(jq -r .e2e_project_backend.local .dfx/local/canister_ids.json)"
4444
}
4545

4646
@test "create stores canister ids for default-ephemeral local networks in .dfx/{network}canister_ids.json" {

e2e/tests-dfx/project_local_network.bash

+4-2
Original file line numberDiff line numberDiff line change
@@ -108,11 +108,13 @@ teardown() {
108108
@test "with a shared network, wallet id is stored in the shared location" {
109109
dfx_start
110110

111-
assert_file_not_exists "$E2E_SHARED_LOCAL_NETWORK_DATA_DIRECTORY/wallets.json"
111+
WALLETS_JSON="$(shared_wallets_json)"
112+
113+
assert_file_not_exists "$WALLETS_JSON"
112114

113115
WALLET_ID="$(dfx identity get-wallet)"
114116

115-
assert_command jq -r .identities.default.local "$E2E_SHARED_LOCAL_NETWORK_DATA_DIRECTORY/wallets.json"
117+
assert_command jq -r .identities.default.local "$WALLETS_JSON"
116118
assert_eq "$WALLET_ID"
117119
assert_file_not_exists .dfx/local/wallets.json
118120
}

e2e/tests-dfx/wallet.bash

+33-3
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,15 @@ teardown() {
1313
}
1414

1515
@test "error reporting for --wallet parameter" {
16+
dfx_start
1617
assert_command_fail dfx canister call hello_backend greet '' --with-cycles 100 --wallet "abc-def"
1718
assert_contains "Failed to read principal from id 'abc-def', and did not find a wallet for that identity"
1819
assert_contains "Text must be in valid Base32 encoding"
1920

2021
assert_command_fail dfx canister call hello_backend greet '' --with-cycles 100 --wallet "alice"
2122
assert_contains "Failed to read principal from id 'alice', and did not find a wallet for that identity"
2223

23-
mkdir -p "$E2E_SHARED_LOCAL_NETWORK_DATA_DIRECTORY"
24-
echo "{}" > "$E2E_SHARED_LOCAL_NETWORK_DATA_DIRECTORY/wallets.json"
24+
echo "{}" > "$(shared_wallets_json)"
2525
assert_command_fail dfx canister call hello_backend greet '' --with-cycles 100 --wallet broken
2626
assert_contains "Failed to read principal from id 'broken' (Text must be in valid Base32 encoding.), and failed to load the wallet for that identity"
2727
assert_contains "missing field \`identities\`"
@@ -236,11 +236,41 @@ teardown() {
236236
}
237237

238238
@test "detects if there is no wallet to upgrade" {
239+
dfx_start
239240
dfx_new hello
240241
assert_command_fail dfx wallet upgrade
241242
assert_match "There is no wallet defined for identity 'default' on network 'local'. Nothing to do."
242243
}
243244

245+
@test "creates a new wallet when switching between pocketic and replica" {
246+
dfx_new hello
247+
248+
USE_REPLICA=1 dfx_start --artificial-delay 101
249+
dfx deploy
250+
251+
dfx_stop
252+
253+
USE_REPLICA="" dfx_start --artificial-delay 99
254+
dfx deploy
255+
}
256+
257+
@test "creates new wallet if backend changes" {
258+
dfx_new hello
259+
260+
dfx_start --artificial-delay 101
261+
dfx deploy
262+
263+
dfx_stop
264+
265+
dfx_start --artificial-delay 99
266+
dfx deploy
267+
}
268+
269+
@test "must run dfx start before accessing wallet on shared local network" {
270+
assert_command_fail dfx wallet upgrade
271+
assert_contains "cannot use a wallet before dfx start"
272+
}
273+
244274
@test "redeem-faucet-coupon can set a new wallet and top up an existing one" {
245275
dfx_new hello
246276
dfx_start
@@ -254,7 +284,7 @@ teardown() {
254284
# prepare wallet to hand out
255285
dfx wallet balance # this creates a new wallet with user faucet_testing as controller
256286
dfx canister call faucet set_wallet_to_hand_out "(principal \"$(dfx identity get-wallet)\")" # register the wallet as the wallet that the faucet will return
257-
rm "$E2E_SHARED_LOCAL_NETWORK_DATA_DIRECTORY/wallets.json" # forget about the currently configured wallet
287+
find "$E2E_SHARED_LOCAL_NETWORK_DATA_DIRECTORY" -name wallets.json -exec rm {} \; # forget about the currently configured wallet
258288

259289
# assert: no wallet configured
260290
export DFX_DISABLE_AUTO_WALLET=1

e2e/utils/_.bash

+9-1
Original file line numberDiff line numberDiff line change
@@ -220,12 +220,20 @@ dfx_stop() {
220220
}
221221

222222
dfx_set_wallet() {
223+
local network="${1:-actuallylocal}"
224+
223225
export WALLET_CANISTER_ID
224226
WALLET_CANISTER_ID=$(dfx identity get-wallet)
225-
assert_command dfx identity set-wallet "${WALLET_CANISTER_ID}" --force --network actuallylocal
227+
assert_command dfx identity set-wallet "${WALLET_CANISTER_ID}" --force --network "${network}"
226228
assert_match 'Wallet set successfully.'
227229
}
228230

231+
shared_wallets_json() {
232+
SETTINGS_DIGEST="$(jq -r .settings_digest "$E2E_SHARED_LOCAL_NETWORK_DATA_DIRECTORY/network-id")"
233+
WALLETS_JSON="$E2E_SHARED_LOCAL_NETWORK_DATA_DIRECTORY/$SETTINGS_DIGEST/wallets.json"
234+
echo "$WALLETS_JSON"
235+
}
236+
229237
setup_actuallylocal_project_network() {
230238
webserver_port=$(get_webserver_port)
231239
# [ ! -f "$E2E_ROUTE_NETWORKS_JSON" ] && echo "{}" >"$E2E_ROUTE_NETWORKS_JSON"

src/dfx-core/src/config/directories.rs

+21-1
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
1-
use crate::error::config::ConfigError;
1+
use crate::config::model::local_server_descriptor::NetworkMetadata;
22
use crate::error::config::ConfigError::{
33
DetermineConfigDirectoryFailed, EnsureConfigDirectoryExistsFailed,
44
};
5+
use crate::error::config::{ConfigError, GetSharedWalletConfigPathError};
56
use crate::error::get_user_home::GetUserHomeError;
67
use crate::error::get_user_home::GetUserHomeError::NoHomeInEnvironment;
78
#[cfg(not(windows))]
89
use crate::foundation::get_user_home;
910
use crate::fs::composite::ensure_dir_exists;
11+
use crate::identity::WALLET_CONFIG_FILENAME;
12+
use crate::json::load_json_file;
1013
use directories_next::ProjectDirs;
1114
use std::path::PathBuf;
1215

@@ -24,6 +27,23 @@ pub fn get_shared_network_data_directory(network: &str) -> Result<PathBuf, GetUs
2427
.join(network))
2528
}
2629

30+
pub fn get_shared_wallet_config_path(
31+
network: &str,
32+
) -> Result<Option<PathBuf>, GetSharedWalletConfigPathError> {
33+
let data_dir = get_shared_network_data_directory(network)?;
34+
35+
let network_id_path = data_dir.join("network-id");
36+
if network_id_path.exists() {
37+
let network_metadata: NetworkMetadata = load_json_file(&network_id_path)?;
38+
let path = data_dir
39+
.join(network_metadata.settings_digest)
40+
.join(WALLET_CONFIG_FILENAME);
41+
Ok(Some(path))
42+
} else {
43+
Ok(None)
44+
}
45+
}
46+
2747
pub fn get_user_dfx_config_dir() -> Result<PathBuf, ConfigError> {
2848
let config_root = std::env::var_os("DFX_CONFIG_ROOT");
2949
// dirs-next is not used for *nix to preserve existing paths

src/dfx-core/src/config/model/network_descriptor.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use crate::error::network_config::NetworkConfigError::{NetworkHasNoProviders, Ne
77
use crate::error::uri::UriError;
88
use candid::Principal;
99
use slog::Logger;
10-
use std::path::{Path, PathBuf};
10+
use std::path::PathBuf;
1111
use url::Url;
1212

1313
pub const MAINNET_MOTOKO_PLAYGROUND_CANISTER_ID: Principal =
@@ -18,7 +18,7 @@ pub const MOTOKO_PLAYGROUND_CANISTER_TIMEOUT_SECONDS: u64 = 1200;
1818
#[derive(Clone, Debug, PartialEq, Eq)]
1919
pub enum NetworkTypeDescriptor {
2020
Ephemeral {
21-
wallet_config_path: PathBuf,
21+
wallet_config_path: Option<PathBuf>,
2222
},
2323
Playground {
2424
playground_canister: Principal,
@@ -40,7 +40,7 @@ pub struct NetworkDescriptor {
4040
impl NetworkTypeDescriptor {
4141
pub fn new(
4242
r#type: NetworkType,
43-
ephemeral_wallet_config_path: &Path,
43+
ephemeral_wallet_config_path: Option<PathBuf>,
4444
playground: Option<PlaygroundConfig>,
4545
) -> Result<Self, NetworkConfigError> {
4646
if let Some(playground_config) = playground {
@@ -57,7 +57,7 @@ impl NetworkTypeDescriptor {
5757
} else {
5858
match r#type {
5959
NetworkType::Ephemeral => Ok(NetworkTypeDescriptor::Ephemeral {
60-
wallet_config_path: ephemeral_wallet_config_path.to_path_buf(),
60+
wallet_config_path: ephemeral_wallet_config_path,
6161
}),
6262
NetworkType::Persistent => Ok(NetworkTypeDescriptor::Persistent),
6363
}

src/dfx-core/src/error/config.rs

+10
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use crate::error::fs::{
33
CanonicalizePathError, CreateDirAllError, EnsureDirExistsError, NoParentPathError,
44
};
55
use crate::error::get_user_home::GetUserHomeError;
6+
use crate::error::structured_file::StructuredFileError;
67
use handlebars::RenderError;
78
use std::path::PathBuf;
89
use thiserror::Error;
@@ -113,3 +114,12 @@ pub enum MergeTechStackError {
113114
#[error("expected extension canister type tech_stack to be an object")]
114115
ExpectedExtensionCanisterTypeTechStackObject,
115116
}
117+
118+
#[derive(Error, Debug)]
119+
pub enum GetSharedWalletConfigPathError {
120+
#[error(transparent)]
121+
GetUserHome(#[from] GetUserHomeError),
122+
123+
#[error(transparent)]
124+
StructuredFileError(#[from] StructuredFileError),
125+
}

src/dfx-core/src/error/identity.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::error::config::GetSharedWalletConfigPathError;
12
use crate::error::fs::{
23
ReadFileError, ReadPermissionsError, RemoveDirectoryAndContentsError, RemoveDirectoryError,
34
RemoveFileError, RenameError, SetPermissionsError, WriteFileError,
@@ -209,7 +210,10 @@ pub enum MapWalletsToRenamedIdentityError {
209210
GetConfigDirectoryFailed(#[source] ConfigError),
210211

211212
#[error(transparent)]
212-
GetSharedNetworkDataDirectoryFailed(#[from] GetUserHomeError),
213+
GetSharedNetworkDataDirectoryFailed(#[from] GetSharedWalletConfigPathError),
214+
215+
#[error(transparent)]
216+
LoadNetworkMetadata(#[from] StructuredFileError),
213217

214218
#[error("Failed to rename wallet global config key")]
215219
RenameWalletGlobalConfigKeyFailed(#[source] RenameWalletGlobalConfigKeyError),

src/dfx-core/src/error/network_config.rs

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
use crate::error::config::{ConfigError, GetTempPathError};
1+
use crate::error::config::{ConfigError, GetSharedWalletConfigPathError, GetTempPathError};
22
use crate::error::fs::ReadToStringError;
33
use crate::error::get_user_home::GetUserHomeError;
44
use crate::error::socket_addr_conversion::SocketAddrConversionError;
5+
use crate::error::structured_file::StructuredFileError;
56
use crate::error::uri::UriError;
67
use candid::types::principal::PrincipalError;
78
use std::num::ParseIntError;
@@ -28,9 +29,15 @@ pub enum NetworkConfigError {
2829
source: UriError,
2930
},
3031

32+
#[error(transparent)]
33+
GetSharedWalletConfigPathError(#[from] GetSharedWalletConfigPathError),
34+
3135
#[error(transparent)]
3236
GetTempPath(#[from] GetTempPathError),
3337

38+
#[error(transparent)]
39+
LoadNetworkId(StructuredFileError),
40+
3441
#[error("Network '{0}' does not specify any network providers.")]
3542
NetworkHasNoProviders(String),
3643

src/dfx-core/src/error/wallet_config.rs

+3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ pub enum WalletConfigError {
1616

1717
#[error("Failed to save wallet configuration")]
1818
SaveWalletConfig(#[from] SaveWalletConfigError),
19+
20+
#[error("cannot use a wallet before dfx start")]
21+
NoWalletBeforeDfxStart,
1922
}
2023

2124
#[derive(Error, Debug)]

src/dfx-core/src/identity/mod.rs

+10-11
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//!
33
//! Wallets are a map of network-identity, but don't have their own types or manager
44
//! type.
5-
use crate::config::directories::{get_shared_network_data_directory, get_user_dfx_config_dir};
5+
use crate::config::directories::{get_shared_wallet_config_path, get_user_dfx_config_dir};
66
use crate::config::model::network_descriptor::NetworkDescriptor;
77
use crate::error::wallet_config::SaveWalletConfigError;
88
use crate::error::{
@@ -256,16 +256,15 @@ impl Identity {
256256
)
257257
.map_err(RenameWalletGlobalConfigKeyFailed)?;
258258
}
259-
let shared_local_network_wallet_path = get_shared_network_data_directory("local")
260-
.map_err(MapWalletsToRenamedIdentityError::GetSharedNetworkDataDirectoryFailed)?
261-
.join(WALLET_CONFIG_FILENAME);
262-
if shared_local_network_wallet_path.exists() {
263-
Identity::rename_wallet_global_config_key(
264-
original_identity,
265-
renamed_identity,
266-
shared_local_network_wallet_path,
267-
)
268-
.map_err(RenameWalletGlobalConfigKeyFailed)?;
259+
if let Some(shared_local_wallet_path) = get_shared_wallet_config_path("local")? {
260+
if shared_local_wallet_path.exists() {
261+
Identity::rename_wallet_global_config_key(
262+
original_identity,
263+
renamed_identity,
264+
shared_local_wallet_path,
265+
)
266+
.map_err(RenameWalletGlobalConfigKeyFailed)?;
267+
}
269268
}
270269
if let Some(temp_dir) = project_temp_dir {
271270
let local_wallet_path = temp_dir.join("local").join(WALLET_CONFIG_FILENAME);

src/dfx-core/src/identity/wallet.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ pub fn get_wallet_config_path(
2525
.join(name)
2626
.join(WALLET_CONFIG_FILENAME)
2727
}
28-
NetworkTypeDescriptor::Ephemeral { wallet_config_path } => wallet_config_path.clone(),
28+
NetworkTypeDescriptor::Ephemeral { wallet_config_path } => wallet_config_path
29+
.clone()
30+
.ok_or(WalletConfigError::NoWalletBeforeDfxStart)?,
2931
})
3032
}
3133

0 commit comments

Comments
 (0)