Symbolチェーン上にDBファイルを書き込んでみた

# はじめに
今回はSymbolチェーン上にDBファイルを書き込んでみたらどうなるか？というネタ記事となります。
単なる思いつきですが、チェーン上にDBを書き込んで復元するとこまで試してみます。

こういうネタ記事をアドカレに書ければ良かったな。。

# 使用環境
・Symbolブロックチェーン（テストネット）
・Visual Studio Community 2022 - Version 17.8.6
・C# .NET 8
・SQLite

# 大まかな手順
1. SQLiteを使用して、画像をBLOB形式で格納します。
2. ファイルの圧縮: SQLiteファイルをGZIP形式で圧縮します。
3. MetalOnSymbolを使用して、圧縮されたファイルをSymbolチェーン上に書き込みます。
4. MetalOnSymbolを用いてチェーン上のデータをフェッチし、ローカルにファイルとして保存します。
5. GZIPファイルを解凍し、SQLiteデータベースに接続して画像データが無事復元されたことを確認します。

# １．SQLiteに画像を格納
今回はSymbolアイコンを使います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2532264/6913581e-1513-d085-3115-dc6747cfe390.png)

SQLiteに画像を格納する際はBLOBという形式で保存されます。
A5SQLというフリーのSQLクライアントで確認します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2532264/cb7acf8f-b09e-0ad1-249d-c598b1ac88f4.png)

# ２．SQLiteファイルを圧縮する
SQLiteファイルを丸ごとGZIP形式に圧縮します。

```csharp
using System.IO.Compression;

public static void CompressFileWithGZip(string sourceFilePath, string destinationGZipPath)
{
    using (var sourceFileStream = new FileStream(sourceFilePath, FileMode.OpenOrCreate, FileAccess.Read))
    {
        using (var destinationFileStream = new FileStream(destinationGZipPath, FileMode.Create, FileAccess.Write))
        {
            using (var compressionStream = new GZipStream(destinationFileStream, CompressionMode.Compress))
            {
                sourceFileStream.CopyTo(compressionStream);
            }
        }
    }
}
```

# ３．Symbolチェーンに書き込む
Nugetで必要なパッケージをインストールします。
・MetalOnSymbol

ちなみに使い方はtoshiさんがサンプルコードをおいてくださっているのでそのまま使用します。

https://github.com/0x070696E65/metal-on-symbol-for-csharp

Forgeを使って圧縮したSQLiteファイルをチェーン上に書き込みます。

```csharp
using CatSdk.CryptoTypes;
using CatSdk.Symbol;

public static async void Forge(string filePath)
{
    var fileData = File.ReadAllBytes(filePath);

    var config = new SymbolServiceConfig("NODE_URL");
    var symbolService = new SymbolService(config);
    await symbolService.Init();

    var metalService = new MetalService(symbolService);

    var alicePrivateKey = new PrivateKey("SOURCE_PRIVATE_KEY");
    var aliceKeyPair = new KeyPair(alicePrivateKey);

    var bobPrivateKey = new PrivateKey("TARGET_PRIVATE_KEY");
    var bobKeyPair = new KeyPair(bobPrivateKey);

    // トランザクション構築
    var (key, txs, _) = await metalService.CreateForgeTxs(aliceKeyPair.PublicKey, bobKeyPair.PublicKey, fileData);

    // MetalIDの確認
    var metalId = metalService.CalculateMetalId(
            MetadataType.Account,
            aliceKeyPair.PublicKey,
            bobKeyPair.PublicKey,
            key
    );
    Console.WriteLine($"MetalID: {metalId}");

    // MetalIDを忘れないようにファイルに書き出しておく！
    File.WriteAllText(Path.Combine(Path.GetDirectoryName(filePath!), "metalId"), metalId);

    // 署名
    var batches = symbolService.BuildSignedAggregateCompleteTxBatches(
            txs,
            aliceKeyPair,
            new List<KeyPair>() { bobKeyPair }
    );

    // 手数料確認
    var totalFee = batches.Select(batch => (long)batch.Fee.Value).Sum();
    Console.WriteLine($"Total Fee: {totalFee}");

    // アナウンス
    var result = await symbolService.ExecuteBatches(batches);
    Console.WriteLine(result);
}
```

# ４．Symbolチェーンから取り出す
Fetchを使ってチェーン上に書き込んだDBファイルを取り出します。

```csharp
using CatSdk.CryptoTypes;
using CatSdk.Symbol;

public static async void Fetch(string metalId, string outputPath)
{
    var config = new SymbolServiceConfig("NODE_URL");
    var symbolService = new SymbolService(config);
    await symbolService.Init();

    var metalService = new MetalService(symbolService);
    var result = await metalService.FetchByMetalId(metalId);

    await using (var fs = new FileStream(outputPath, FileMode.Create))
    {
        fs.Write(result.Payload, 0, result.Payload.Length);
    }
    Console.WriteLine("Complete");
}
```

# ５．GZIPファイルを解凍
GZIPファイルを解凍します。

```csharp
using System.IO.Compression;

public static void DecompressGZipFile(string gzipFilePath, string decompressedFilePath)
{
    using (var gzipStream = new GZipStream(File.OpenRead(gzipFilePath), CompressionMode.Decompress))
    {
        using (var decompressedFileStream = File.Create(decompressedFilePath))
        {
            gzipStream.CopyTo(decompressedFileStream);
        }
    }
}
```


## SQLite接続して画像を確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2532264/f47fbef6-3632-a0ca-19ec-0c22706774df.png)

ファイルサイズも元ファイルと変わらないので特に問題なく復元できたと思われます。

# なんでこんなことしたのか
書き込むデータをDBでひとまとめに出来る点は利便性が向上するかなと思いました。
もしかしたらファイルサイズも減らせるかと思いましたが、それは無理でした（笑）もう少し検証が必要です。

こういうこともできましたよ！という程度のものと思ってください。

# おまけ
# SQLiteファイルをBase85形式に変換
~~NugetからSimpleBaseをインストールして使います。~~
~~Base64形式でも良かったのですが、Base85形式のほうが膨張率が少ないのでこちらを使用します。~~
MetalOnSymbolを使用したほうが簡単そうだったのでこの章は割愛しました。
一応簡単にできるのでコードだけ置いておきます。

クラスで用意する必要もなかったかもですが。。

```csharp
using SimpleBase; //nugetからSimpleBaseをインストールしてください。

public static class Base85Converter
{
    public static string Encode(string filePath)
    {
        return Base85.Ascii85.Encode(File.ReadAllBytes(filePath));
    }

    public static byte[] Decode(string encoded)
    {
        return Base85.Ascii85.Decode(encoded);
    }
}
```

デコードしてきたbyte[]は以下のコードでファイルに書き出すことができます。

```csharp
// ファイルにバイト配列を書き込む
File.WriteAllBytes(filePath, decodedBytes);
```