1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
// Copyright 2019-2024 ChainSafe Systems
// SPDX-License-Identifier: Apache-2.0, MIT

//! This module contains the migration logic for the `NV22` upgrade for the
//! Market actor.
use std::sync::Arc;

use crate::shim::econ::TokenAmount;
use crate::utils::db::CborStoreExt;
use anyhow::Context;
use cid::Cid;
use fil_actor_market_state::v12::{
    DealProposal, DealState as DealStateOld, State as MarketStateOld,
};
use fil_actor_market_state::v13::{
    DealState as DealStateNew, ProviderSectorsMap as ProviderSectorsMapNew, SectorDealIDs,
    SectorDealsMap, State as MarketStateNew, PROVIDER_SECTORS_CONFIG, SECTOR_DEALS_CONFIG,
    STATES_AMT_BITWIDTH,
};

use fil_actors_shared::v12::Array as ArrayOld;
use fil_actors_shared::v13::Array as ArrayNew;
use fvm_ipld_blockstore::Blockstore;
use fvm_shared4::clock::ChainEpoch;

use crate::state_migration::common::{ActorMigration, ActorMigrationInput, ActorMigrationOutput};

use super::miner::ProviderSectors;

pub struct MarketMigrator {
    upgrade_epoch: ChainEpoch,
    provider_sectors: Arc<ProviderSectors>,
    out_cid: Cid,
}
pub(in crate::state_migration) fn market_migrator<BS: Blockstore>(
    upgrade_epoch: ChainEpoch,
    provider_sectors: Arc<ProviderSectors>,
    out_cid: Cid,
) -> anyhow::Result<Arc<dyn ActorMigration<BS> + Send + Sync>> {
    Ok(Arc::new(MarketMigrator {
        upgrade_epoch,
        provider_sectors,
        out_cid,
    }))
}

impl<BS: Blockstore> ActorMigration<BS> for MarketMigrator {
    fn migrate_state(
        &self,
        store: &BS,
        input: ActorMigrationInput,
    ) -> anyhow::Result<Option<ActorMigrationOutput>> {
        let in_state: MarketStateOld = store.get_cbor_required(&input.head)?;

        let (provider_sectors, new_states) =
            self.migrate_provider_sectors_and_states(store, &in_state.states, &in_state.proposals)?;

        let out_state = MarketStateNew {
            proposals: in_state.proposals,
            states: new_states,
            pending_proposals: in_state.pending_proposals,
            escrow_table: in_state.escrow_table,
            locked_table: in_state.locked_table,
            next_id: in_state.next_id,
            deal_ops_by_epoch: in_state.deal_ops_by_epoch,
            last_cron: in_state.last_cron,
            total_client_locked_collateral: TokenAmount::from(
                in_state.total_client_locked_collateral,
            )
            .into(),
            total_provider_locked_collateral: TokenAmount::from(
                in_state.total_provider_locked_collateral,
            )
            .into(),
            total_client_storage_fee: TokenAmount::from(in_state.total_client_storage_fee).into(),
            pending_deal_allocation_ids: in_state.pending_deal_allocation_ids,
            provider_sectors,
        };

        let new_head = store.put_cbor_default(&out_state)?;

        Ok(Some(ActorMigrationOutput {
            new_code_cid: self.out_cid,
            new_head,
        }))
    }

    fn is_deferred(&self) -> bool {
        true
    }
}

impl MarketMigrator {
    fn migrate_provider_sectors_and_states(
        &self,
        store: &impl Blockstore,
        states: &Cid,
        proposals: &Cid,
    ) -> anyhow::Result<(Cid, Cid)> {
        //dbg!("running market migration");
        let (provider_sectors_root, new_state_array_root) =
            self.migrate_provider_sectors_and_states_with_scratch(store, states, proposals)?;

        Ok((provider_sectors_root, new_state_array_root))
    }

    /// This method implements the migration logic as outlined in the [FIP-0076](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0076.md#migration)
    // > For each deal state object in the market actor state that has a terminated epoch set to -1:
    // > * find the corresponding deal proposal object and extract the provider's actor ID;
    // > * in the provider's miner state, find the ID of the sector with the corresponding deal ID in sector metadata;
    // >   * if such a sector cannot be found, assert that the deal's end epoch has passed and use sector ID 0 [1];
    // > * set the new deal state object's sector number to the sector ID found;
    // > * add the deal ID to the ProviderSectors mapping for the provider's actor ID and sector number.
    // > For each deal state object in the market actor state that has a terminated epoch set to any other value:
    // > * set the deal state object's sector number to 0.
    fn migrate_provider_sectors_and_states_with_scratch(
        &self,
        store: &impl Blockstore,
        states: &Cid,
        proposals: &Cid,
    ) -> anyhow::Result<(Cid, Cid)> {
        let old_state_array = ArrayOld::<DealStateOld, _>::load(states, store)?;
        let mut new_state_array =
            ArrayNew::<DealStateNew, _>::new_with_bit_width(store, STATES_AMT_BITWIDTH);

        let proposals_array = ArrayOld::<DealProposal, _>::load(proposals, store)?;

        old_state_array.for_each(|deal_id, old_state| {
            let proposal = proposals_array
                .get(deal_id)?
                .context("deal proposal not found")?;

            let sector_number =
                if old_state.slash_epoch == -1 && proposal.end_epoch >= self.upgrade_epoch {
                    // find the corresponding deal proposal object and extract the provider's actor ID;
                    self.provider_sectors
                        .deal_to_sector
                        .read()
                        .get(&deal_id)
                        .map(|sector_id| sector_id.number)
                        .unwrap_or(0)
                } else {
                    0
                };

            let new_state = DealStateNew {
                sector_number,
                last_updated_epoch: old_state.last_updated_epoch,
                sector_start_epoch: old_state.sector_start_epoch,
                slash_epoch: old_state.slash_epoch,
            };
            new_state_array.set(deal_id, new_state)?;

            Ok(())
        })?;

        let new_state_array_root = new_state_array.flush()?;
        let mut out_provider_sectors =
            ProviderSectorsMapNew::empty(store, PROVIDER_SECTORS_CONFIG, "provider sectors");

        for (miner, sectors) in self.provider_sectors.miner_to_sector_to_deals.read().iter() {
            let mut actor_sectors =
                SectorDealsMap::empty(store, SECTOR_DEALS_CONFIG, "sector deals");

            for (sector, deals) in sectors.iter() {
                actor_sectors.set(
                    sector,
                    SectorDealIDs {
                        deals: deals.clone(),
                    },
                )?;
            }

            out_provider_sectors.set(miner, actor_sectors.flush()?)?;
        }

        let out_provider_sectors_root = out_provider_sectors.flush()?;

        Ok((out_provider_sectors_root, new_state_array_root))
    }
}