com.coinblesk.client.utils.upgrade.UpgradeUtils.java Source code

Java tutorial

Introduction

Here is the source code for com.coinblesk.client.utils.upgrade.UpgradeUtils.java

Source

/*
 * Copyright 2016 The Coinblesk team and the CSG Group at University of Zurich
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 *
 */

package com.coinblesk.client.utils.upgrade;

import android.content.Context;
import android.util.Log;

import com.coinblesk.client.BuildConfig;
import com.coinblesk.client.config.AppConfig;
import com.coinblesk.client.config.Constants;
import com.coinblesk.client.utils.ClientUtils;
import com.coinblesk.client.utils.SharedPrefUtils;
import com.google.common.reflect.TypeToken;
import com.google.gson.Gson;

import org.apache.commons.io.FileUtils;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.params.MainNetParams;
import org.bitcoinj.params.TestNet3Params;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.Map;

/**
 * @author Andreas Albrecht
 */
public class UpgradeUtils {
    private static final String TAG = UpgradeUtils.class.getName();

    private static boolean checkDone = false;

    public void checkUpgrade(Context context, NetworkParameters params) {
        if (checkDone) {
            // skip if already done
            return;
        }

        String appVersion = SharedPrefUtils.getAppVersion(context);
        if (appVersion == null) {
            // first start - no app version yet.
            Log.d(TAG, "No appVersion yet - first launch");
        } else if (appVersion.equals(BuildConfig.VERSION_NAME)) {
            Log.d(TAG, "appVersion '" + appVersion + "' equals current version - no action required");
        } else {
            // add upgrade instructions as needed.
        }

        if (migrateFrom_v1_0_262(context)) {
            Log.d(TAG, "Migrate from v1.0.262 / 2of2 multisig (CeBIT) to CLTV");
            /* special case: migrate from objectstore and 2of2 multisig wallet
             * - migrate from early version v1.0.262 (CeBIT)
             * - enable transfer of funds to new address (2of2 multisig to cltv)
             */
            try {
                // migration is done for mainnet and testnet regardless of the current network setting of the app!
                NetworkParameters[] cltvMigrationParams = new NetworkParameters[] { MainNetParams.get(),
                        TestNet3Params.get() };
                for (NetworkParameters migrationParams : cltvMigrationParams) {
                    doMigrateFrom_v1_0_262(context, migrationParams);
                }
            } catch (Exception e) {
                Log.e(TAG, "Migration failed: ", e);
            }
        }

        SharedPrefUtils.setAppVersion(context, BuildConfig.VERSION_NAME);
        checkDone = true;
    }

    private boolean migrateFrom_v1_0_262(Context context) {
        // these directories were used by the object store
        // if any of these exist, we migrate from an existing wallet.
        File objectStoreMain = new File(context.getFilesDir(), "mainnet_wallet_" + "_uuid_object_storage");
        File objectStoreTest = new File(context.getFilesDir(), "testnet_wallet_" + "_uuid_object_storage");
        return objectStoreMain.exists() || objectStoreTest.exists();
    }

    private void doMigrateFrom_v1_0_262(Context context, NetworkParameters migrationParams) throws IOException {
        Log.d(TAG, "********* MIGRATION FROM v1.0.262 - " + migrationParams.getId() + "*********");

        final File rootDir = context.getFilesDir();
        final File storageDir;
        final File archiveDir = new File(rootDir, "archive_" + System.currentTimeMillis());
        final File walletFile;
        final File chainFile;

        final File newWalletFile;
        final File newChainFile;

        if (ClientUtils.isMainNet(migrationParams)) {
            String walletFilesPrefix = AppConfig.MainNetConfig.get().getWalletFilesPrefix();
            storageDir = new File(rootDir, "mainnet_wallet__uuid_object_storage");
            walletFile = new File(rootDir, "mainnet_wallet_.wallet");
            newWalletFile = new File(rootDir, walletFilesPrefix + ".wallet");
            chainFile = new File(rootDir, "mainnet_wallet_.spvchain");
            newChainFile = new File(rootDir, walletFilesPrefix + ".spvchain");
        } else if (ClientUtils.isTestNet(migrationParams)) {
            String walletFilesPrefix = AppConfig.TestNetConfig.get().getWalletFilesPrefix();
            storageDir = new File(rootDir, "testnet_wallet__uuid_object_storage");
            walletFile = new File(rootDir, "testnet_wallet_.wallet");
            newWalletFile = new File(rootDir, walletFilesPrefix + ".wallet");
            chainFile = new File(rootDir, "testnet_wallet_.spvchain");
            newChainFile = new File(rootDir, walletFilesPrefix + ".spvchain");
        } else {
            throw new RuntimeException("Network params not supported (unknown): " + migrationParams.toString());
        }

        final File keyFile = new File(storageDir, "ECKeyWrapper.json");

        if (keyFile.exists() && walletFile.exists()) {

            // Keys: stored in ECKeyWrapper.json
            /* Key format (JSON):
              {
            "...uuid1...": {
                "isPublicOnly": true,
                "keyPayload": [...bytes (integers)...],
                "name": "remote_server_public_key",
                "uuid": "...uuid1..."
            },
            "...uuid2...": {
                "isPublicOnly": false,
                "keyPayload": [...bytes (integers)...],
                "name": "remote_client_public_key",
                "uuid": "...uuid2..."
            }
              }
            */

            Log.d(TAG, "Key file found: " + keyFile);
            String keyFileJson = FileUtils.readFileToString(keyFile);
            Type type = new TypeToken<Map<String, ECKeyWrapper>>() {
            }.getType();
            // Note: do not use gson from serializeutils (key is not stored in base64).
            Map<String, ECKeyWrapper> keys = new Gson().fromJson(keyFileJson, type);
            ECKey serverKey = null;
            ECKey clientKey = null;
            for (ECKeyWrapper key : keys.values()) {
                if (key.isPublicOnly && key.name.equals("remote_server_public_key")) {
                    serverKey = ECKey.fromPublicOnly(key.keyPayload);
                } else if (!key.isPublicOnly && key.name.equals("remote_client_public_key")) {
                    clientKey = ECKey.fromPrivate(key.keyPayload);
                } else {
                    Log.d(TAG, "Unknown key name: " + key.name);
                }
            }

            if (clientKey != null && serverKey != null) {
                Log.d(TAG, "Found client and server keys - store in shared preferences.");
                try {

                    /********** Actual Migration Code **********/
                    SharedPrefUtils.setClientKey(context, migrationParams, clientKey);
                    SharedPrefUtils.setServerKey(context, migrationParams, serverKey, "n/a - migration");
                    Log.d(TAG, "Migrated keys:" + " clientPubKey=" + clientKey.getPublicKeyAsHex()
                            + ", serverPubKey=" + serverKey.getPublicKeyAsHex());

                    // move wallet file
                    Log.d(TAG, "Migrate wallet file: " + walletFile.toString() + " -> " + newWalletFile.toString());
                    FileUtils.copyFile(walletFile, newWalletFile);
                    Log.d(TAG, "Migrate chain file: " + chainFile.toString() + " -> " + newChainFile.toString());
                    FileUtils.copyFile(chainFile, newChainFile);

                    SharedPrefUtils.enableMultisig2of2ToCltvForwarder(context);

                    // move everything to an archive file.
                    Log.d(TAG, "Move old files to archive dir: " + archiveDir.toString());
                    FileUtils.moveToDirectory(storageDir, archiveDir, true);
                    FileUtils.moveToDirectory(walletFile, archiveDir, true);
                    FileUtils.moveToDirectory(chainFile, archiveDir, true);

                } catch (Exception e) {
                    Log.d(TAG, "Exception: ", e);
                    // clear the changes made.
                    SharedPrefUtils.setClientKey(context, migrationParams, null);
                    SharedPrefUtils.setServerKey(context, migrationParams, null, "");
                    if (newWalletFile.exists()) {
                        newWalletFile.delete();
                    }
                }
            }
        } else {
            Log.d(TAG,
                    "Key file or wallet file not found - no migration required - " + String.format(
                            "keyFile: %s (exists: %s), walletFile: %s (exists: %s)", keyFile.toString(),
                            keyFile.exists(), walletFile.toString(), walletFile.exists()));
        }

        Log.d(TAG, "********* MIGRATION FROM v1.0.262 FINISHED *********");
    }

    private static class ECKeyWrapper {
        /* keys stored in the objectstore have the following structure */
        byte[] keyPayload;
        boolean isPublicOnly;
        String name;
    }

}