Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ CLAUDE.md
.deploy
/apps/*/out
/apps/*/cache
/persistence
12 changes: 12 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ members = [
"apps/anvil_orchestrator",
"apps/anvil_provisioner",
"apps/index_deployer",
"apps/migrate_persistence",
"apps/tracker",
"_deprecated/liquidity_tracker",
"_deprecated/alloy-evm-connector",
Expand Down
12 changes: 12 additions & 0 deletions apps/migrate_persistence/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "migrate_persistence"
version = "0.1.0"
edition = "2021"

[dependencies]
eyre = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
clap = { workspace = true}
index-maker = { workspace = true }
symm-core = { workspace = true }
2 changes: 2 additions & 0 deletions apps/migrate_persistence/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
### usage
cargo run --release -- --file path_to_file/old_invoice.json
114 changes: 114 additions & 0 deletions apps/migrate_persistence/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
use clap::Parser;
use eyre::Result;
use std::path::PathBuf;
use std::sync::Arc;

// Import from your index-maker project
use index_maker::solver::mint_invoice_manager::MintInvoiceManager;
use symm_core::core::persistence::util::JsonFilePersistence;

/// Migrate MintInvoiceManager from old format to new composite persistence format
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
/// Path to the InvoiceManager.json file
#[arg(short, long)]
file: PathBuf,

/// Dry run - don't write any files
#[arg(short, long, default_value_t = false)]
dry_run: bool,
}

fn main() -> Result<()> {
let args = Args::parse();

println!("=== MintInvoiceManager Migration Tool ===\n");
println!("File: {:?}", args.file);

if args.dry_run {
println!("Mode: DRY RUN (no files will be written)\n");
} else {
println!("Mode: LIVE (files will be written)\n");
}

// Check if file exists
if !args.file.exists() {
eprintln!("Error: File not found: {:?}", args.file);
std::process::exit(1);
}

// Read the file
println!("Reading file...");
let file_content = std::fs::read_to_string(&args.file)?;
let root_value: serde_json::Value = serde_json::from_str(&file_content)?;

// Check if already migrated
if root_value.get("metadata").is_some() {
println!("✓ File is already in new format (has 'metadata' key).");
println!("✓ Nothing to do.");
return Ok(());
}

// Parse old format
let invoices_array = match root_value.get("invoices") {
Some(inv) => inv,
None => {
eprintln!("Error: No 'invoices' key found. Unknown format.");
std::process::exit(1);
}
};

use index_maker::solver::mint_invoice::MintInvoice;
use symm_core::core::bits::Address;

let old_invoices: Vec<(u32, Address, MintInvoice)> =
serde_json::from_value(invoices_array.clone())?;

println!("Found {} invoices in old format\n", old_invoices.len());

if args.dry_run {
// Just show what would happen
for (i, (chain_id, address, invoice)) in old_invoices.iter().enumerate() {
println!("[{}/{}] Would process: {}",
i + 1,
old_invoices.len(),
invoice.client_order_id
);
println!(" -> Chain: {}, Address: {}", chain_id, address);
}

println!("\n Run without --dry-run to apply changes");
return Ok(());
}

// Create backup
let backup_path = args.file.with_extension("json.backup");
println!("Creating backup: {:?}", backup_path);
std::fs::copy(&args.file, &backup_path)?;
println!("✓ Backup created\n");

// Use MintInvoiceManager to do the migration
// This automatically uses the correct key format and child path logic
let persistence = Arc::new(JsonFilePersistence::new(&args.file));
let mut manager = MintInvoiceManager::new(persistence);

// Add each invoice - this creates child files automatically
for (i, (chain_id, address, invoice)) in old_invoices.into_iter().enumerate() {
// This will:
// 1. Create child file with full invoice data
// 2. Add metadata entry
// 3. Update root file
manager.add_invoice(chain_id, address, invoice)?;

println!(" Done");
}

println!();
println!("=== Migration Summary ===");
println!(" Updated root file with metadata");
println!(" Backup saved as: {:?}", backup_path);
println!("\n Migration complete!");

Ok(())
}
57 changes: 52 additions & 5 deletions libs/symm-core/src/core/persistence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ pub trait Persist {
pub trait Persistence {
fn load_value(&self) -> Result<Option<Value>>;
fn store_value(&self, value: Value) -> Result<()>;
fn child(&self, key: String) -> Result<Box<dyn Persistence>>; // To allow nested persistence
}

pub mod util {
use std::{fs, path::PathBuf};
use std::{collections::HashMap, fs, path::PathBuf, sync::Arc};

use eyre::{Context, Result};
use parking_lot::RwLock;
Expand All @@ -23,20 +24,35 @@ pub mod util {
;

pub struct InMemoryPersistence {
data: RwLock<Option<String>>,
data: Arc<RwLock<HashMap<String, String>>>, // Shared storage with keys
prefix: String, // Namespace for this instance
}

impl InMemoryPersistence {
pub fn new() -> Self {
Self {
data: RwLock::new(None),
data: Arc::new(RwLock::new(HashMap::new())),
prefix: String::new(),
}
}

fn new_with_data(data: Arc<RwLock<HashMap<String, String>>>, prefix: String) -> Self {
Self { data, prefix }
}

fn get_key(&self) -> String {
if self.prefix.is_empty() {
"root".to_string()
} else {
self.prefix.clone()
}
}
}

impl Persistence for InMemoryPersistence {
fn load_value(&self) -> Result<Option<Value>> {
if let Some(json_string) = self.data.read().as_ref() {
let key = self.get_key();
if let Some(json_string) = self.data.read().get(&key) {
Ok(Some(
serde_json::from_str(json_string).context("Failed to deserialize")?,
))
Expand All @@ -46,10 +62,23 @@ pub mod util {
}

fn store_value(&self, value: Value) -> Result<()> {
let key = self.get_key();
let json_string = serde_json::to_string_pretty(&value)?;
*self.data.write() = Some(json_string);
self.data.write().insert(key, json_string);
Ok(())
}

fn child(&self, key: String) -> Result<Box<dyn Persistence>> {
let child_prefix = if self.prefix.is_empty() {
key
} else {
format!("{}/{}", self.prefix, key)
};
Ok(Box::new(InMemoryPersistence::new_with_data(
Arc::clone(&self.data),
child_prefix,
)))
}
}

pub struct JsonFilePersistence {
Expand All @@ -60,6 +89,19 @@ pub mod util {
pub fn new(path: impl Into<PathBuf>) -> Self {
JsonFilePersistence { path: path.into() }
}

fn create_child_path(&self, key: &str) -> PathBuf {
let mut parent_path = self.path.clone();

// If parent has .json extension, remove it to use as directory
if parent_path.extension().is_some() {
parent_path.set_extension("");
}

// Add key as filename with .json extension
parent_path.push(format!("{}.json", key));
parent_path
}
}

impl Persistence for JsonFilePersistence {
Expand All @@ -85,5 +127,10 @@ pub mod util {
let json_string = serde_json::to_string_pretty(&value).context("Failed to serialize")?;
fs::write(&self.path, &json_string).context("Failed to write json file")
}

fn child(&self, key: String) -> Result<Box<dyn Persistence>> {
let child_path = self.create_child_path(&key);
Ok(Box::new(JsonFilePersistence::new(child_path)))
}
}
}
Loading