forked from Qortal/qortal
Browse Source
- Adds support for minting accounts as well as trade bot states - Includes automatic import of both types on node startup, and automatic export on node shutdown - Retains legacy trade bot states in a separate "TradeBotStatesArchive.json" file, whilst keeping the current active ones in "TradeBotStates.json". This prevents states being re-imported after they have been removed, but still keeps a copy of the data in case a key is ever needed. - Uses indentation in the JSON files for easier readability.bootstrap
CalDescent
3 years ago
21 changed files with 810 additions and 70 deletions
@ -0,0 +1,298 @@
|
||||
package org.qortal.repository.hsqldb; |
||||
|
||||
import org.apache.logging.log4j.LogManager; |
||||
import org.apache.logging.log4j.Logger; |
||||
import org.json.JSONArray; |
||||
import org.json.JSONException; |
||||
import org.json.JSONObject; |
||||
import org.qortal.data.account.MintingAccountData; |
||||
import org.qortal.data.crosschain.TradeBotData; |
||||
import org.qortal.repository.Bootstrap; |
||||
import org.qortal.repository.DataException; |
||||
import org.qortal.repository.Repository; |
||||
import org.qortal.settings.Settings; |
||||
import org.qortal.utils.Base58; |
||||
import org.qortal.utils.Triple; |
||||
|
||||
import java.io.File; |
||||
import java.io.FileNotFoundException; |
||||
import java.io.FileWriter; |
||||
import java.io.IOException; |
||||
import java.nio.file.Files; |
||||
import java.nio.file.Path; |
||||
import java.nio.file.Paths; |
||||
import java.util.Iterator; |
||||
import java.util.List; |
||||
|
||||
public class HSQLDBImportExport { |
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger(Bootstrap.class); |
||||
|
||||
public static void backupTradeBotStates(Repository repository) throws DataException { |
||||
HSQLDBImportExport.backupCurrentTradeBotStates(repository); |
||||
HSQLDBImportExport.backupArchivedTradeBotStates(repository); |
||||
|
||||
LOGGER.info("Exported sensitive/node-local data: trade bot states"); |
||||
} |
||||
|
||||
public static void backupMintingAccounts(Repository repository) throws DataException { |
||||
HSQLDBImportExport.backupCurrentMintingAccounts(repository); |
||||
|
||||
LOGGER.info("Exported sensitive/node-local data: minting accounts"); |
||||
} |
||||
|
||||
|
||||
/* Trade bot states */ |
||||
|
||||
/** |
||||
* Backs up the trade bot states currently in the repository, without combining them with past ones |
||||
* @param repository |
||||
* @throws DataException |
||||
*/ |
||||
private static void backupCurrentTradeBotStates(Repository repository) throws DataException { |
||||
try { |
||||
Path backupDirectory = HSQLDBImportExport.getExportDirectory(true); |
||||
|
||||
// Load current trade bot data
|
||||
List<TradeBotData> allTradeBotData = repository.getCrossChainRepository().getAllTradeBotData(); |
||||
JSONArray currentTradeBotDataJson = new JSONArray(); |
||||
for (TradeBotData tradeBotData : allTradeBotData) { |
||||
JSONObject tradeBotDataJson = tradeBotData.toJson(); |
||||
currentTradeBotDataJson.put(tradeBotDataJson); |
||||
} |
||||
|
||||
// Wrap current trade bot data in an object to indicate the type
|
||||
JSONObject currentTradeBotDataJsonWrapper = new JSONObject(); |
||||
currentTradeBotDataJsonWrapper.put("type", "tradeBotStates"); |
||||
currentTradeBotDataJsonWrapper.put("dataset", "current"); |
||||
currentTradeBotDataJsonWrapper.put("data", currentTradeBotDataJson); |
||||
|
||||
// Write current trade bot data (just the ones currently in the database)
|
||||
String fileName = Paths.get(backupDirectory.toString(), "TradeBotStates.json").toString(); |
||||
FileWriter writer = new FileWriter(fileName); |
||||
writer.write(currentTradeBotDataJsonWrapper.toString(2)); |
||||
writer.close(); |
||||
|
||||
} catch (DataException | IOException e) { |
||||
throw new DataException("Unable to export trade bot states from repository"); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Backs up the trade bot states currently in the repository to a separate "archive" file, |
||||
* making sure to combine them with any unique states already present in the archive. |
||||
* @param repository |
||||
* @throws DataException |
||||
*/ |
||||
private static void backupArchivedTradeBotStates(Repository repository) throws DataException { |
||||
try { |
||||
Path backupDirectory = HSQLDBImportExport.getExportDirectory(true); |
||||
|
||||
// Load current trade bot data
|
||||
List<TradeBotData> allTradeBotData = repository.getCrossChainRepository().getAllTradeBotData(); |
||||
JSONArray allTradeBotDataJson = new JSONArray(); |
||||
for (TradeBotData tradeBotData : allTradeBotData) { |
||||
JSONObject tradeBotDataJson = tradeBotData.toJson(); |
||||
allTradeBotDataJson.put(tradeBotDataJson); |
||||
} |
||||
|
||||
// We need to combine existing archived TradeBotStates data before overwriting
|
||||
String fileName = Paths.get(backupDirectory.toString(), "TradeBotStatesArchive.json").toString(); |
||||
File tradeBotStatesBackupFile = new File(fileName); |
||||
if (tradeBotStatesBackupFile.exists()) { |
||||
|
||||
String jsonString = new String(Files.readAllBytes(Paths.get(fileName))); |
||||
Triple<String, String, JSONArray> parsedJSON = HSQLDBImportExport.parseJSONString(jsonString); |
||||
if (parsedJSON.getA() == null || parsedJSON.getC() == null) { |
||||
throw new DataException("Missing data when exporting archived trade bot states"); |
||||
} |
||||
String type = parsedJSON.getA(); |
||||
String dataset = parsedJSON.getB(); |
||||
JSONArray data = parsedJSON.getC(); |
||||
|
||||
if (!type.equals("tradeBotStates") || !dataset.equals("archive")) { |
||||
throw new DataException("Format mismatch when exporting archived trade bot states"); |
||||
} |
||||
|
||||
Iterator<Object> iterator = data.iterator(); |
||||
while(iterator.hasNext()) { |
||||
JSONObject existingTradeBotDataItem = (JSONObject)iterator.next(); |
||||
String existingTradePrivateKey = (String) existingTradeBotDataItem.get("tradePrivateKey"); |
||||
// Check if we already have an entry for this trade
|
||||
boolean found = allTradeBotData.stream().anyMatch(tradeBotData -> Base58.encode(tradeBotData.getTradePrivateKey()).equals(existingTradePrivateKey)); |
||||
if (found == false) |
||||
// Add the data from the backup file to our "allTradeBotDataJson" array as it's not currently in the db
|
||||
allTradeBotDataJson.put(existingTradeBotDataItem); |
||||
} |
||||
} |
||||
|
||||
// Wrap all trade bot data in an object to indicate the type
|
||||
JSONObject allTradeBotDataJsonWrapper = new JSONObject(); |
||||
allTradeBotDataJsonWrapper.put("type", "tradeBotStates"); |
||||
allTradeBotDataJsonWrapper.put("dataset", "archive"); |
||||
allTradeBotDataJsonWrapper.put("data", allTradeBotDataJson); |
||||
|
||||
// Write ALL trade bot data to archive (current plus states that are no longer in the database)
|
||||
FileWriter writer = new FileWriter(fileName); |
||||
writer.write(allTradeBotDataJsonWrapper.toString(2)); |
||||
writer.close(); |
||||
|
||||
} catch (DataException | IOException e) { |
||||
throw new DataException("Unable to export trade bot states from repository"); |
||||
} |
||||
} |
||||
|
||||
|
||||
/* Minting accounts */ |
||||
|
||||
/** |
||||
* Backs up the minting accounts currently in the repository, without combining them with past ones |
||||
* @param repository |
||||
* @throws DataException |
||||
*/ |
||||
private static void backupCurrentMintingAccounts(Repository repository) throws DataException { |
||||
try { |
||||
Path backupDirectory = HSQLDBImportExport.getExportDirectory(true); |
||||
|
||||
// Load current trade bot data
|
||||
List<MintingAccountData> allMintingAccountData = repository.getAccountRepository().getMintingAccounts(); |
||||
JSONArray currentMintingAccountJson = new JSONArray(); |
||||
for (MintingAccountData mintingAccountData : allMintingAccountData) { |
||||
JSONObject mintingAccountDataJson = mintingAccountData.toJson(); |
||||
currentMintingAccountJson.put(mintingAccountDataJson); |
||||
} |
||||
|
||||
// Wrap current trade bot data in an object to indicate the type
|
||||
JSONObject currentMintingAccountDataJsonWrapper = new JSONObject(); |
||||
currentMintingAccountDataJsonWrapper.put("type", "mintingAccounts"); |
||||
currentMintingAccountDataJsonWrapper.put("dataset", "current"); |
||||
currentMintingAccountDataJsonWrapper.put("data", currentMintingAccountJson); |
||||
|
||||
// Write current trade bot data (just the ones currently in the database)
|
||||
String fileName = Paths.get(backupDirectory.toString(), "MintingAccounts.json").toString(); |
||||
FileWriter writer = new FileWriter(fileName); |
||||
writer.write(currentMintingAccountDataJsonWrapper.toString(2)); |
||||
writer.close(); |
||||
|
||||
} catch (DataException | IOException e) { |
||||
throw new DataException("Unable to export minting accounts from repository"); |
||||
} |
||||
} |
||||
|
||||
|
||||
/* Utils */ |
||||
|
||||
/** |
||||
* Imports data from supplied file |
||||
* Data type is loaded from the file itself, and if missing, TradeBotStates is assumed |
||||
* |
||||
* @param filename |
||||
* @param repository |
||||
* @throws DataException |
||||
* @throws IOException |
||||
*/ |
||||
public static void importDataFromFile(String filename, Repository repository) throws DataException, IOException { |
||||
Path path = Paths.get(filename); |
||||
if (!path.toFile().exists()) { |
||||
throw new FileNotFoundException(String.format("File doesn't exist: %s", filename)); |
||||
} |
||||
byte[] fileContents = Files.readAllBytes(path); |
||||
if (fileContents == null) { |
||||
throw new FileNotFoundException(String.format("Unable to read file contents: %s", filename)); |
||||
} |
||||
|
||||
LOGGER.info(String.format("Importing %s into repository ...", filename)); |
||||
|
||||
String jsonString = new String(fileContents); |
||||
Triple<String, String, JSONArray> parsedJSON = HSQLDBImportExport.parseJSONString(jsonString); |
||||
if (parsedJSON.getA() == null || parsedJSON.getC() == null) { |
||||
throw new DataException(String.format("Missing data when importing %s into repository", filename)); |
||||
} |
||||
String type = parsedJSON.getA(); |
||||
JSONArray data = parsedJSON.getC(); |
||||
|
||||
Iterator<Object> iterator = data.iterator(); |
||||
while(iterator.hasNext()) { |
||||
JSONObject dataJsonObject = (JSONObject)iterator.next(); |
||||
|
||||
if (type.equals("tradeBotStates")) { |
||||
HSQLDBImportExport.importTradeBotDataJSON(dataJsonObject, repository); |
||||
} |
||||
else if (type.equals("mintingAccounts")) { |
||||
HSQLDBImportExport.importMintingAccountDataJSON(dataJsonObject, repository); |
||||
} |
||||
else { |
||||
throw new DataException(String.format("Unrecognized data type when importing %s into repository", filename)); |
||||
} |
||||
|
||||
} |
||||
LOGGER.info(String.format("Imported %s into repository from %s", type, filename)); |
||||
} |
||||
|
||||
private static void importTradeBotDataJSON(JSONObject tradeBotDataJson, Repository repository) throws DataException { |
||||
TradeBotData tradeBotData = TradeBotData.fromJson(tradeBotDataJson); |
||||
repository.getCrossChainRepository().save(tradeBotData); |
||||
} |
||||
|
||||
private static void importMintingAccountDataJSON(JSONObject mintingAccountDataJson, Repository repository) throws DataException { |
||||
MintingAccountData mintingAccountData = MintingAccountData.fromJson(mintingAccountDataJson); |
||||
repository.getAccountRepository().save(mintingAccountData); |
||||
} |
||||
|
||||
public static Path getExportDirectory(boolean createIfNotExists) throws DataException { |
||||
Path backupPath = Paths.get(Settings.getInstance().getExportPath()); |
||||
|
||||
if (createIfNotExists) { |
||||
// Create the qortal-backup folder if it doesn't exist
|
||||
try { |
||||
Files.createDirectories(backupPath); |
||||
} catch (IOException e) { |
||||
LOGGER.info(String.format("Unable to create %s folder", backupPath.toString())); |
||||
throw new DataException(String.format("Unable to create %s folder", backupPath.toString())); |
||||
} |
||||
} |
||||
|
||||
return backupPath; |
||||
} |
||||
|
||||
/** |
||||
* Parses a JSON string and returns "data", "type", and "dataset" fields. |
||||
* In the case of legacy JSON files with no type, they are assumed to be TradeBotStates archives, |
||||
* as we had never implemented this for any other types. |
||||
* |
||||
* @param jsonString |
||||
* @return Triple<String, String, JSONArray> (type, dataset, data) |
||||
*/ |
||||
private static Triple<String, String, JSONArray> parseJSONString(String jsonString) throws DataException { |
||||
String type = null; |
||||
String dataset = null; |
||||
JSONArray data = null; |
||||
|
||||
try { |
||||
// Firstly try importing the new format
|
||||
JSONObject jsonData = new JSONObject(jsonString); |
||||
if (jsonData != null && jsonData.getString("type") != null) { |
||||
|
||||
type = jsonData.getString("type"); |
||||
dataset = jsonData.getString("dataset"); |
||||
data = jsonData.getJSONArray("data"); |
||||
} |
||||
|
||||
} catch (JSONException e) { |
||||
// Could be a legacy format which didn't contain a type or any other outer keys, so try importing that
|
||||
// Treat these as TradeBotStates archives, given that this was the only type previously implemented
|
||||
try { |
||||
type = "tradeBotStates"; |
||||
dataset = "archive"; |
||||
data = new JSONArray(jsonString); |
||||
|
||||
} catch (JSONException e2) { |
||||
// Still failed, so give up
|
||||
throw new DataException("Couldn't import JSON file"); |
||||
} |
||||
} |
||||
|
||||
return new Triple(type, dataset, data); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,390 @@
|
||||
package org.qortal.test; |
||||
|
||||
import org.apache.commons.io.FileUtils; |
||||
import org.bitcoinj.core.Address; |
||||
import org.bitcoinj.core.AddressFormatException; |
||||
import org.bitcoinj.core.ECKey; |
||||
import org.json.JSONArray; |
||||
import org.json.JSONObject; |
||||
import org.junit.After; |
||||
import org.junit.Before; |
||||
import org.junit.Test; |
||||
import org.qortal.account.PublicKeyAccount; |
||||
import org.qortal.controller.tradebot.LitecoinACCTv1TradeBot; |
||||
import org.qortal.controller.tradebot.TradeBot; |
||||
import org.qortal.crosschain.Litecoin; |
||||
import org.qortal.crosschain.LitecoinACCTv1; |
||||
import org.qortal.crosschain.SupportedBlockchain; |
||||
import org.qortal.crypto.Crypto; |
||||
import org.qortal.data.account.MintingAccountData; |
||||
import org.qortal.data.crosschain.TradeBotData; |
||||
import org.qortal.repository.DataException; |
||||
import org.qortal.repository.Repository; |
||||
import org.qortal.repository.RepositoryManager; |
||||
import org.qortal.repository.hsqldb.HSQLDBImportExport; |
||||
import org.qortal.settings.Settings; |
||||
import org.qortal.test.common.Common; |
||||
import org.qortal.utils.NTP; |
||||
|
||||
import java.io.FileWriter; |
||||
import java.io.IOException; |
||||
import java.nio.file.Path; |
||||
import java.nio.file.Paths; |
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
import static org.junit.Assert.*; |
||||
|
||||
public class ImportExportTests extends Common { |
||||
|
||||
@Before |
||||
public void beforeTest() throws DataException { |
||||
Common.useDefaultSettings(); |
||||
this.deleteExportDirectory(); |
||||
} |
||||
|
||||
@After |
||||
public void afterTest() throws DataException { |
||||
this.deleteExportDirectory(); |
||||
} |
||||
|
||||
|
||||
@Test |
||||
public void testExportAndImportTradeBotStates() throws DataException, IOException { |
||||
try (final Repository repository = RepositoryManager.getRepository()) { |
||||
|
||||
// Ensure no trade bots exist
|
||||
assertTrue(repository.getCrossChainRepository().getAllTradeBotData().isEmpty()); |
||||
|
||||
// Create some trade bots
|
||||
List<TradeBotData> tradeBots = new ArrayList<>(); |
||||
for (int i=0; i<10; i++) { |
||||
TradeBotData tradeBotData = this.createTradeBotData(repository); |
||||
repository.getCrossChainRepository().save(tradeBotData); |
||||
tradeBots.add(tradeBotData); |
||||
} |
||||
|
||||
// Ensure they have been added
|
||||
assertEquals(10, repository.getCrossChainRepository().getAllTradeBotData().size()); |
||||
|
||||
// Export them
|
||||
HSQLDBImportExport.backupTradeBotStates(repository); |
||||
|
||||
// Delete them from the repository
|
||||
for (TradeBotData tradeBotData : tradeBots) { |
||||
repository.getCrossChainRepository().delete(tradeBotData.getTradePrivateKey()); |
||||
} |
||||
|
||||
// Ensure they have been deleted
|
||||
assertTrue(repository.getCrossChainRepository().getAllTradeBotData().isEmpty()); |
||||
|
||||
// Import them
|
||||
Path exportPath = HSQLDBImportExport.getExportDirectory(false); |
||||
Path filePath = Paths.get(exportPath.toString(), "TradeBotStates.json"); |
||||
HSQLDBImportExport.importDataFromFile(filePath.toString(), repository); |
||||
|
||||
// Ensure they have been imported
|
||||
assertEquals(10, repository.getCrossChainRepository().getAllTradeBotData().size()); |
||||
|
||||
// Ensure all the data matches
|
||||
for (TradeBotData tradeBotData : tradeBots) { |
||||
byte[] tradePrivateKey = tradeBotData.getTradePrivateKey(); |
||||
TradeBotData repositoryTradeBotData = repository.getCrossChainRepository().getTradeBotData(tradePrivateKey); |
||||
assertNotNull(repositoryTradeBotData); |
||||
assertEquals(tradeBotData.toJson().toString(), repositoryTradeBotData.toJson().toString()); |
||||
} |
||||
|
||||
repository.saveChanges(); |
||||
} |
||||
} |
||||
|
||||
@Test |
||||
public void testExportAndImportCurrentTradeBotStates() throws DataException, IOException { |
||||
try (final Repository repository = RepositoryManager.getRepository()) { |
||||
|
||||
// Ensure no trade bots exist
|
||||
assertTrue(repository.getCrossChainRepository().getAllTradeBotData().isEmpty()); |
||||
|
||||
// Create some trade bots
|
||||
List<TradeBotData> tradeBots = new ArrayList<>(); |
||||
for (int i=0; i<10; i++) { |
||||
TradeBotData tradeBotData = this.createTradeBotData(repository); |
||||
repository.getCrossChainRepository().save(tradeBotData); |
||||
tradeBots.add(tradeBotData); |
||||
} |
||||
|
||||
// Ensure they have been added
|
||||
assertEquals(10, repository.getCrossChainRepository().getAllTradeBotData().size()); |
||||
|
||||
// Export them
|
||||
HSQLDBImportExport.backupTradeBotStates(repository); |
||||
|
||||
// Delete them from the repository
|
||||
for (TradeBotData tradeBotData : tradeBots) { |
||||
repository.getCrossChainRepository().delete(tradeBotData.getTradePrivateKey()); |
||||
} |
||||
|
||||
// Ensure they have been deleted
|
||||
assertTrue(repository.getCrossChainRepository().getAllTradeBotData().isEmpty()); |
||||
|
||||
// Add some more trade bots
|
||||
List<TradeBotData> additionalTradeBots = new ArrayList<>(); |
||||
for (int i=0; i<5; i++) { |
||||
TradeBotData tradeBotData = this.createTradeBotData(repository); |
||||
repository.getCrossChainRepository().save(tradeBotData); |
||||
additionalTradeBots.add(tradeBotData); |
||||
} |
||||
|
||||
// Export again
|
||||
HSQLDBImportExport.backupTradeBotStates(repository); |
||||
|
||||
// Import current states only
|
||||
Path exportPath = HSQLDBImportExport.getExportDirectory(false); |
||||
Path filePath = Paths.get(exportPath.toString(), "TradeBotStates.json"); |
||||
HSQLDBImportExport.importDataFromFile(filePath.toString(), repository); |
||||
|
||||
// Ensure they have been imported
|
||||
assertEquals(5, repository.getCrossChainRepository().getAllTradeBotData().size()); |
||||
|
||||
// Ensure that only the additional trade bots have been imported and that the data matches
|
||||
for (TradeBotData tradeBotData : additionalTradeBots) { |
||||
byte[] tradePrivateKey = tradeBotData.getTradePrivateKey(); |
||||
TradeBotData repositoryTradeBotData = repository.getCrossChainRepository().getTradeBotData(tradePrivateKey); |
||||
assertNotNull(repositoryTradeBotData); |
||||
assertEquals(tradeBotData.toJson().toString(), repositoryTradeBotData.toJson().toString()); |
||||
} |
||||
|
||||
// None of the original trade bots should exist in the repository
|
||||
for (TradeBotData tradeBotData : tradeBots) { |
||||
byte[] tradePrivateKey = tradeBotData.getTradePrivateKey(); |
||||
TradeBotData repositoryTradeBotData = repository.getCrossChainRepository().getTradeBotData(tradePrivateKey); |
||||
assertNull(repositoryTradeBotData); |
||||
} |
||||
|
||||
repository.saveChanges(); |
||||
} |
||||
} |
||||
|
||||
@Test |
||||
public void testExportAndImportAllTradeBotStates() throws DataException, IOException { |
||||
try (final Repository repository = RepositoryManager.getRepository()) { |
||||
|
||||
// Ensure no trade bots exist
|
||||
assertTrue(repository.getCrossChainRepository().getAllTradeBotData().isEmpty()); |
||||
|
||||
// Create some trade bots
|
||||
List<TradeBotData> tradeBots = new ArrayList<>(); |
||||
for (int i=0; i<10; i++) { |
||||
TradeBotData tradeBotData = this.createTradeBotData(repository); |
||||
repository.getCrossChainRepository().save(tradeBotData); |
||||
tradeBots.add(tradeBotData); |
||||
} |
||||
|
||||
// Ensure they have been added
|
||||
assertEquals(10, repository.getCrossChainRepository().getAllTradeBotData().size()); |
||||
|
||||
// Export them
|
||||
HSQLDBImportExport.backupTradeBotStates(repository); |
||||
|
||||
// Delete them from the repository
|
||||
for (TradeBotData tradeBotData : tradeBots) { |
||||
repository.getCrossChainRepository().delete(tradeBotData.getTradePrivateKey()); |
||||
} |
||||
|
||||
// Ensure they have been deleted
|
||||
assertTrue(repository.getCrossChainRepository().getAllTradeBotData().isEmpty()); |
||||
|
||||
// Add some more trade bots
|
||||
List<TradeBotData> additionalTradeBots = new ArrayList<>(); |
||||
for (int i=0; i<5; i++) { |
||||
TradeBotData tradeBotData = this.createTradeBotData(repository); |
||||
repository.getCrossChainRepository().save(tradeBotData); |
||||
additionalTradeBots.add(tradeBotData); |
||||
} |
||||
|
||||
// Export again
|
||||
HSQLDBImportExport.backupTradeBotStates(repository); |
||||
|
||||
// Import all states from the archive
|
||||
Path exportPath = HSQLDBImportExport.getExportDirectory(false); |
||||
Path filePath = Paths.get(exportPath.toString(), "TradeBotStatesArchive.json"); |
||||
HSQLDBImportExport.importDataFromFile(filePath.toString(), repository); |
||||
|
||||
// Ensure they have been imported
|
||||
assertEquals(15, repository.getCrossChainRepository().getAllTradeBotData().size()); |
||||
|
||||
// Ensure that all known trade bots have been imported and that the data matches
|
||||
tradeBots.addAll(additionalTradeBots); |
||||
|
||||
for (TradeBotData tradeBotData : tradeBots) { |
||||
byte[] tradePrivateKey = tradeBotData.getTradePrivateKey(); |
||||
TradeBotData repositoryTradeBotData = repository.getCrossChainRepository().getTradeBotData(tradePrivateKey); |
||||
assertNotNull(repositoryTradeBotData); |
||||
assertEquals(tradeBotData.toJson().toString(), repositoryTradeBotData.toJson().toString()); |
||||
} |
||||
|
||||
repository.saveChanges(); |
||||
} |
||||
} |
||||
|
||||
@Test |
||||
public void testExportAndImportLegacyTradeBotStates() throws DataException, IOException { |
||||
try (final Repository repository = RepositoryManager.getRepository()) { |
||||
|
||||
// Create some trade bots, but don't save them in the repository
|
||||
List<TradeBotData> tradeBots = new ArrayList<>(); |
||||
for (int i=0; i<10; i++) { |
||||
TradeBotData tradeBotData = this.createTradeBotData(repository); |
||||
tradeBots.add(tradeBotData); |
||||
} |
||||
|
||||
// Create a legacy format TradeBotStates.json backup file
|
||||
this.exportLegacyTradeBotStatesJson(tradeBots); |
||||
|
||||
// Ensure no trade bots exist in repository
|
||||
assertTrue(repository.getCrossChainRepository().getAllTradeBotData().isEmpty()); |
||||
|
||||
// Import the legacy format file
|
||||
Path exportPath = HSQLDBImportExport.getExportDirectory(false); |
||||
Path filePath = Paths.get(exportPath.toString(), "TradeBotStates.json"); |
||||
HSQLDBImportExport.importDataFromFile(filePath.toString(), repository); |
||||
|
||||
// Ensure they have been imported
|
||||
assertEquals(10, repository.getCrossChainRepository().getAllTradeBotData().size()); |
||||
|
||||
for (TradeBotData tradeBotData : tradeBots) { |
||||
byte[] tradePrivateKey = tradeBotData.getTradePrivateKey(); |
||||
TradeBotData repositoryTradeBotData = repository.getCrossChainRepository().getTradeBotData(tradePrivateKey); |
||||
assertNotNull(repositoryTradeBotData); |
||||
assertEquals(tradeBotData.toJson().toString(), repositoryTradeBotData.toJson().toString()); |
||||
} |
||||
|
||||
repository.saveChanges(); |
||||
} |
||||
} |
||||
|
||||
@Test |
||||
public void testExportAndImportMintingAccountData() throws DataException, IOException { |
||||
try (final Repository repository = RepositoryManager.getRepository()) { |
||||
|
||||
// Ensure no minting accounts exist
|
||||
assertTrue(repository.getAccountRepository().getMintingAccounts().isEmpty()); |
||||
|
||||
// Create some minting accounts
|
||||
List<MintingAccountData> mintingAccounts = new ArrayList<>(); |
||||
for (int i=0; i<10; i++) { |
||||
MintingAccountData mintingAccountData = this.createMintingAccountData(); |
||||
repository.getAccountRepository().save(mintingAccountData); |
||||
mintingAccounts.add(mintingAccountData); |
||||
} |
||||
|
||||
// Ensure they have been added
|
||||
assertEquals(10, repository.getAccountRepository().getMintingAccounts().size()); |
||||
|
||||
// Export them
|
||||
HSQLDBImportExport.backupMintingAccounts(repository); |
||||
|
||||
// Delete them from the repository
|
||||
for (MintingAccountData mintingAccountData : mintingAccounts) { |
||||
repository.getAccountRepository().delete(mintingAccountData.getPrivateKey()); |
||||
} |
||||
|
||||
// Ensure they have been deleted
|
||||
assertTrue(repository.getAccountRepository().getMintingAccounts().isEmpty()); |
||||
|
||||
// Import them
|
||||
Path exportPath = HSQLDBImportExport.getExportDirectory(false); |
||||
Path filePath = Paths.get(exportPath.toString(), "MintingAccounts.json"); |
||||
HSQLDBImportExport.importDataFromFile(filePath.toString(), repository); |
||||
|
||||
// Ensure they have been imported
|
||||
assertEquals(10, repository.getAccountRepository().getMintingAccounts().size()); |
||||
|
||||
// Ensure all the data matches
|
||||
for (MintingAccountData mintingAccountData : mintingAccounts) { |
||||
byte[] privateKey = mintingAccountData.getPrivateKey(); |
||||
MintingAccountData repositoryMintingAccountData = repository.getAccountRepository().getMintingAccount(privateKey); |
||||
assertNotNull(repositoryMintingAccountData); |
||||
assertEquals(mintingAccountData.toJson().toString(), repositoryMintingAccountData.toJson().toString()); |
||||
} |
||||
|
||||
repository.saveChanges(); |
||||
} |
||||
} |
||||
|
||||
|
||||
private TradeBotData createTradeBotData(Repository repository) throws DataException { |
||||
byte[] tradePrivateKey = TradeBot.generateTradePrivateKey(); |
||||
|
||||
byte[] tradeNativePublicKey = TradeBot.deriveTradeNativePublicKey(tradePrivateKey); |
||||
byte[] tradeNativePublicKeyHash = Crypto.hash160(tradeNativePublicKey); |
||||
String tradeNativeAddress = Crypto.toAddress(tradeNativePublicKey); |
||||
|
||||
byte[] tradeForeignPublicKey = TradeBot.deriveTradeForeignPublicKey(tradePrivateKey); |
||||
byte[] tradeForeignPublicKeyHash = Crypto.hash160(tradeForeignPublicKey); |
||||
|
||||
String receivingAddress = "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE"; |
||||
|
||||
// Convert Litecoin receiving address into public key hash (we only support P2PKH at this time)
|
||||
Address litecoinReceivingAddress; |
||||
try { |
||||
litecoinReceivingAddress = Address.fromString(Litecoin.getInstance().getNetworkParameters(), receivingAddress); |
||||
} catch (AddressFormatException e) { |
||||
throw new DataException("Unsupported Litecoin receiving address: " + receivingAddress); |
||||
} |
||||
|
||||
byte[] litecoinReceivingAccountInfo = litecoinReceivingAddress.getHash(); |
||||
|
||||
byte[] creatorPublicKey = new byte[32]; |
||||
PublicKeyAccount creator = new PublicKeyAccount(repository, creatorPublicKey); |
||||
|
||||
long timestamp = NTP.getTime(); |
||||
String atAddress = "AT_ADDRESS"; |
||||
long foreignAmount = 1234; |
||||
long qortAmount= 5678; |
||||
|
||||
TradeBotData tradeBotData = new TradeBotData(tradePrivateKey, LitecoinACCTv1.NAME, |
||||
LitecoinACCTv1TradeBot.State.BOB_WAITING_FOR_AT_CONFIRM.name(), LitecoinACCTv1TradeBot.State.BOB_WAITING_FOR_AT_CONFIRM.value, |
||||
creator.getAddress(), atAddress, timestamp, qortAmount, |
||||
tradeNativePublicKey, tradeNativePublicKeyHash, tradeNativeAddress, |
||||
null, null, |
||||
SupportedBlockchain.LITECOIN.name(), |
||||
tradeForeignPublicKey, tradeForeignPublicKeyHash, |
||||
foreignAmount, null, null, null, litecoinReceivingAccountInfo); |
||||
|
||||
return tradeBotData; |
||||
} |
||||
|
||||
private MintingAccountData createMintingAccountData() { |
||||
// These don't need to be valid keys - just 32 byte strings for the purposes of testing
|
||||
byte[] privateKey = new ECKey().getPrivKeyBytes(); |
||||
byte[] publicKey = new ECKey().getPrivKeyBytes(); |
||||
|
||||
return new MintingAccountData(privateKey, publicKey); |
||||
} |
||||
|
||||
private void exportLegacyTradeBotStatesJson(List<TradeBotData> allTradeBotData) throws IOException, DataException { |
||||
JSONArray allTradeBotDataJson = new JSONArray(); |
||||
for (TradeBotData tradeBotData : allTradeBotData) { |
||||
JSONObject tradeBotDataJson = tradeBotData.toJson(); |
||||
allTradeBotDataJson.put(tradeBotDataJson); |
||||
} |
||||
|
||||
Path backupDirectory = HSQLDBImportExport.getExportDirectory(true); |
||||
String fileName = Paths.get(backupDirectory.toString(), "TradeBotStates.json").toString(); |
||||
FileWriter writer = new FileWriter(fileName); |
||||
writer.write(allTradeBotDataJson.toString()); |
||||
writer.close(); |
||||
} |
||||
|
||||
private void deleteExportDirectory() { |
||||
// Delete archive directory if exists
|
||||
Path archivePath = Paths.get(Settings.getInstance().getExportPath()); |
||||
try { |
||||
FileUtils.deleteDirectory(archivePath.toFile()); |
||||
} catch (IOException e) { |
||||
|
||||
} |
||||
} |
||||
|
||||
} |
Loading…
Reference in new issue