Failed post_upgrade - missing field

I’m trying to upgrade one of my canisters. I added a new piece of state. the log shows the following error when upgrading.

Installing canisters...
Previously installed module hash: Some("6052fb9ecb5c8e7be2fd621de18ce504a1ffcb43185cd3970bfb5e0c54c30d6e")
Upgrading code for canister sns_rewards, with canister ID 2f5ll-gqaaa-aaaak-qcfuq-cai
New wasm module hash: 6f407c24cabd5867cb36abf76c461ccaef6b5c035f3999d3c3400c4168b13fbb
Error: Failed while trying to deploy canisters.
Caused by: Failed while trying to deploy canisters.
  Failed while trying to install all canisters.
    Failed to install wasm module to canister 'sns_rewards'.
      Failed during wasm installation call: The replica returned a rejection error: reject code CanisterError, reject message Error from Canister 2f5ll-gqaaa-aaaak-qcfuq-cai: Canister trapped explicitly: Panicked at 'called `Result::unwrap()` on an `Err` value: Syntax("missing field `last_daily_gldgov_burn`")', backend/canisters/sns_rewards/impl/src/lifecycle/post_upgrade.rs:19:10, error code None

last_daily_gldgov_burn is the new piece of state i added. it’s just a Rust u64.

here is my post_upgrade.rs hook

#[post_upgrade]
#[trace]
fn post_upgrade() {
    let memory = get_upgrades_memory();
    let reader = get_reader(&memory);

    let (runtime_state, logs, traces): (RuntimeState, Vec<LogEntry>, Vec<LogEntry>) = serializer
        ::deserialize(reader)
        .unwrap();

    canister_logger::init_with_logs(runtime_state.env.is_test_mode(), logs, traces);
    init_canister(runtime_state);

    info!("Post upgrade complete.")
}

here is how i manage the memory

use ic_stable_structures::{
    memory_manager::{ MemoryId, MemoryManager, VirtualMemory },
    DefaultMemoryImpl,
};

const UPGRADES: MemoryId = MemoryId::new(0);
const MATURITY_HISTORY: MemoryId = MemoryId::new(1);
const PAYMENT_ROUND_HISTORY: MemoryId = MemoryId::new(2);

// const EVENT_LOGS_INDEX_MEM_ID: MemoryId = MemoryId::new(1);
// const EVENT_LOGS_DATA_MEM_ID: MemoryId = MemoryId::new(2);

pub type VM = VirtualMemory<DefaultMemoryImpl>;

thread_local! {
    static MEMORY_MANAGER: MemoryManager<DefaultMemoryImpl> = MemoryManager::init(
        DefaultMemoryImpl::default()
    );
}

pub fn get_upgrades_memory() -> VM {
    get_memory(UPGRADES)
}

Any advice would be greatly appreciated :slight_smile:

Guessing i would have to manually upgrade doing something like below.


// Post-upgrade hook
#[ic_cdk_macros::post_upgrade]
fn post_upgrade() {
    let old_state_bytes = ic_cdk::storage::get::<Vec<u8>>("old_state").unwrap_or_default();
    let mut old_state: OldState = serde_cbor::from_slice(&old_state_bytes).unwrap_or_default();
    
    // Create a new instance of the new state struct
    let mut new_state = NewState {
        // Initialize the new property with a default value
        last_daily_gldgov_burn: 0,
        // Include other properties from the old state as needed
        // ...
    };

    // Copy relevant data from the old state to the new state, if necessary
    // ...

    // Use the new property or further processing...
}

Fixed it with an Option type as the new piece of state e.g

RunTimeState {
last_daily_gldgov_burn : Option<u64>

It does make sense I guess

1 Like

I think this is the way if the new fields have no default value. At least it’s what I understand from the tutorial about upgrade of the stable structures: https://github.com/dfinity/stable-structures/blob/main/docs/schema-upgrades.md

Hmm it’s weird though because I actually had an alias type for the u64 that does implement the Default trait. I’m sure I made a mistake somewhere though. Thanks :slight_smile:

1 Like