pub(crate) mod immediate;
pub(crate) mod params;
pub mod parts;
pub(crate) mod quantize;
pub(crate) mod reconcile;
pub(crate) mod schedule;
pub mod split;
pub(crate) mod store;
pub(crate) mod transmission;
pub use immediate::{ImmediateMigrationPlan, ImmediateMigrationTx};
pub use params::MigrationParams;
pub use parts::{
BoundNote, BoundaryWitness, PartId, PartRecord, PartState, PrepareResult, SigningStrategy,
};
pub use quantize::{Denominations, decompose};
pub(crate) use reconcile::due_now_parts;
pub use reconcile::{
ChainView, PartAssessment, PartClass, RecommendedAction, ReconcileReport, reconcile,
};
pub use schedule::{TransmissionWindow, WindowReport, bucket_index};
pub(crate) use schedule::{plan_schedule, upcoming_windows, window_timeline};
pub use split::{MigrationPlan, NoteSplitTx, plan_hash, plan_migration};
pub use transmission::{
PartTransmissionError, TransmissionClient, TransmissionReceipt, TransmissionRoute,
};
use zcash_primitives::transaction::TxId;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ConsentBinding {
pub params_hash: [u8; 32],
pub plan_hash: [u8; 32],
pub consented_at: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MigrationMode {
Scheduled,
Immediate,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MigrationPhase {
Planned,
NoteSplitting {
round: u32,
pending_txids: Vec<TxId>,
},
PartsScheduled,
Complete {
residual: u64,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MigrationState {
pub params: MigrationParams,
pub consent: ConsentBinding,
pub strategy: SigningStrategy,
pub mode: MigrationMode,
pub account: zip32::AccountId,
pub phase: MigrationPhase,
pub parts: Vec<PartRecord>,
}
impl MigrationState {
pub fn reserved_output_ids(&self) -> Vec<pepper_sync::wallet::OutputId> {
self.parts
.iter()
.filter(|part| {
matches!(
part.state,
PartState::Bound
| PartState::Assigned
| PartState::Signed
| PartState::Broadcast
)
})
.filter_map(|part| part.note.map(|note| note.output_id))
.collect()
}
}
impl crate::wallet::LightWallet {
pub(crate) fn with_migration_state<R>(
&mut self,
f: impl FnOnce(&mut Self, &mut MigrationState) -> R,
) -> Option<R> {
let mut state = self.migration.take()?;
let result = f(self, &mut state);
self.migration = Some(state);
Some(result)
}
}
#[cfg(test)]
mod tests {
#[test]
fn transmission_path_has_no_sync_dependency() {
let forbidden = [
"pepper_sync::sync",
"sync_and_await",
"pause_sync",
"resume_sync",
"GrpcIndexer",
"zingo_netutils",
];
for module in ["parts", "transmission", "schedule", "store", "reconcile"] {
let path = format!(
"{}/src/wallet/migration/{module}.rs",
env!("CARGO_MANIFEST_DIR")
);
let Ok(source) = std::fs::read_to_string(&path) else {
continue; };
for needle in forbidden {
assert!(
!source.contains(needle),
"{path} references `{needle}`: the migration transmission path must remain \
decoupled from the sync engine"
);
}
}
}
}