From 1d574b3ee1e406e6adc11f4088a3a3bfecd59972 Mon Sep 17 00:00:00 2001 From: Aelita4 Date: Wed, 25 Jan 2023 14:01:32 +0100 Subject: [PATCH] Add import/export functionality --- DzieCoin.iml | 1 + pom.xml | 5 + .../java/pl/mikorosa/dziecoin/Blockchain.java | 233 +++++++++++++++++- src/main/java/pl/mikorosa/dziecoin/Main.java | 18 +- .../pl/mikorosa/dziecoin/gui/MainFrame.java | 22 ++ 5 files changed, 261 insertions(+), 18 deletions(-) diff --git a/DzieCoin.iml b/DzieCoin.iml index dbc5699..23fef75 100644 --- a/DzieCoin.iml +++ b/DzieCoin.iml @@ -13,5 +13,6 @@ + \ No newline at end of file diff --git a/pom.xml b/pom.xml index 89b4625..190571a 100644 --- a/pom.xml +++ b/pom.xml @@ -20,5 +20,10 @@ mysql-connector-java 8.0.30 + + com.github.davidmoten + commons-csv + 1.6.002 + \ No newline at end of file diff --git a/src/main/java/pl/mikorosa/dziecoin/Blockchain.java b/src/main/java/pl/mikorosa/dziecoin/Blockchain.java index c33520e..e42a8d8 100644 --- a/src/main/java/pl/mikorosa/dziecoin/Blockchain.java +++ b/src/main/java/pl/mikorosa/dziecoin/Blockchain.java @@ -1,13 +1,35 @@ package pl.mikorosa.dziecoin; +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVPrinter; +import org.apache.commons.csv.CSVRecord; + +import javax.swing.*; +import java.io.IOException; +import java.io.Reader; +import java.io.Writer; +import java.nio.file.Files; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; +import java.util.Map; public class Blockchain { private List blocks; private int length; + private boolean importToDatabase; - public Blockchain(String genesisBlockRecipient) { + public static void clearAllTablesInDatabase() { + Main.db.getTransactionsTable().cleanTable(); + Main.db.getNFTsTable().cleanTable(); + Main.db.getBlocksTable().cleanTable(); + } + + public Blockchain(String genesisBlockRecipient, boolean importToDatabase) { + System.out.println("Automatic import to database " + (importToDatabase ? "enabled" : "disabled")); + if(importToDatabase) clearAllTablesInDatabase(); + + this.importToDatabase = importToDatabase; blocks = new ArrayList<>(); length = blocks.size(); @@ -27,6 +49,205 @@ public class Blockchain { } } + public Blockchain(String blocksCSVPath, String transactionCSVPath, String NFTsCSVPath, boolean importToDatabase) { + System.out.println("Automatic import to database " + (importToDatabase ? "enabled" : "disabled")); + if(importToDatabase) clearAllTablesInDatabase(); + this.importToDatabase = importToDatabase; + this.blocks = new ArrayList<>(); + this.length = this.blocks.size(); + + try { + List toMine = importData(blocksCSVPath, transactionCSVPath, NFTsCSVPath, importToDatabase); + for (Block block : toMine) { + this.addBlock(block); + } + System.out.println("Import complete"); + } catch (IOException e) { + System.out.println("Error while importing data: "); + e.printStackTrace(); + JOptionPane.showMessageDialog(null, "Unable to import: " + e.getMessage(), "Fatal error", JOptionPane.ERROR_MESSAGE); + System.exit(1); + } catch (BlockchainIntegrityException e) { + JOptionPane.showMessageDialog(null, "Unable to add a block: " + e.getMessage(), "Fatal error", JOptionPane.ERROR_MESSAGE); + System.exit(1); + } + } + + public Blockchain(boolean importToDatabase) { + this.blocks = new ArrayList<>(); + this.length = this.blocks.size(); + this.importToDatabase = importToDatabase; + + this.importFromDatabase(); + System.out.println("Import complete"); + } + + public void importFromDatabase() { + System.out.println("Importing from database..."); + List blocksToImport = new ArrayList<>(); + + System.out.println("Importing block data..."); + List> blocks = Main.db.getBlocksTable().select(); + for (Map block : blocks) { + String hash = (String) block.get("hash"); + String prevHash = (String) block.get("prev_hash"); + int nonce = (Integer) block.get("nonce"); + int height = (Integer) block.get("height"); + + blocksToImport.add(new Block(hash, prevHash, height, new ArrayList<>(), new ArrayList<>(), nonce)); + } + + System.out.println("Importing transaction data..."); + List> transactions = Main.db.getTransactionsTable().select(); + for (Map transaction : transactions) { + String sender = (String) transaction.get("sender"); + String recipient = (String) transaction.get("recipient"); + int amount = (Integer) transaction.get("amount"); + int blockHeight = (Integer) transaction.get("block_height"); + + blocksToImport.get(blockHeight - 1).addTransaction(new Transaction(sender, recipient, amount)); + } + + System.out.println("Importing NFT data..."); + List> NFTs = Main.db.getNFTsTable().select(); + for (Map NFT : NFTs) { + String contract = (String) NFT.get("contract"); + String owner = (String) NFT.get("owner"); + String data = (String) NFT.get("data"); + int blockHeight = (Integer) NFT.get("block_height"); + + blocksToImport.get(blockHeight - 1).addNFT(new NFT(contract, owner, data)); + } + + for (Block block : blocksToImport) { + try { + this.addBlock(block); + } catch (BlockchainIntegrityException e) { + JOptionPane.showMessageDialog(null, "Unable to add a block: " + e.getMessage(), "Fatal error", JOptionPane.ERROR_MESSAGE); + System.exit(1); + } + } + } + + public List importData(String blocksPath, String transactionsPath, String NFTsPath, boolean importToDatabase) throws IOException { + List blocks = new ArrayList<>(); + + //if(importToDatabase) clearAllTablesInDatabase(); + + Reader blocksReader = Files.newBufferedReader(Paths.get(blocksPath)); + Iterable blocksRecords = CSVFormat.DEFAULT.withHeader("height", "hash", "prev_hash", "nonce").withFirstRecordAsHeader().parse(blocksReader); + System.out.println("Importing block data..."); + for(CSVRecord record : blocksRecords) { + int height = Integer.parseInt(record.get("height")); + String hash = record.get("hash"); + if(!hash.startsWith("0".repeat(Main.difficulty))) { + JOptionPane.showMessageDialog(null, "Unable to import:\nImport data does not match difficulty (" + Main.difficulty + ")", "Fatal error", JOptionPane.ERROR_MESSAGE); + System.exit(1); + } + String prevHash = record.get("prev_hash"); + int nonce = Integer.parseInt(record.get("nonce")); + Block block = new Block(hash, prevHash, height, new ArrayList<>(), new ArrayList<>(), nonce); + blocks.add(block); + //if(importToDatabase) Main.db.getBlocksTable().insert(height, hash, prevHash, nonce); + } + + Reader transactionsReader = Files.newBufferedReader(Paths.get(transactionsPath)); + Iterable transactionRecords = CSVFormat.DEFAULT.withHeader("id", "sender", "recipient", "amount", "block_height").withFirstRecordAsHeader().parse(transactionsReader); + System.out.println("Importing transaction data..."); + for(CSVRecord record : transactionRecords) { + String sender = record.get("sender"); + String recipient = record.get("recipient"); + int amount = Integer.parseInt(record.get("amount")); + int blockHeight = Integer.parseInt(record.get("block_height")); + + blocks.get(blockHeight - 1).addTransaction(new Transaction(sender, recipient, amount)); + //if(importToDatabase) Main.db.getTransactionsTable().insert(sender, recipient, amount, blockHeight); + } + + Reader NFTsReader = Files.newBufferedReader(Paths.get(NFTsPath)); + Iterable NFTsRecords = CSVFormat.DEFAULT.withHeader("id", "contract", "owner", "data", "block_height").withFirstRecordAsHeader().parse(NFTsReader); + System.out.println("Importing NFT data..."); + for(CSVRecord record : NFTsRecords) { + String contract = record.get("contract"); + String owner = record.get("owner"); + String data = record.get("data"); + int blockHeight = Integer.parseInt(record.get("block_height")); + + blocks.get(blockHeight - 1).addNFT(new NFT(contract, owner, data)); + //if(importToDatabase) Main.db.getNFTsTable().insert(contract, owner, data, blockHeight); + } + + return blocks; + } + + public enum ExportType { + BLOCK(1), + TRANSACTION(2), + NFT(4); + + public final int value; + + ExportType(int value) { + this.value = value; + } + } + + public void exportData(int bitfield) throws IOException { + if((bitfield & ExportType.BLOCK.value) != 0) exportBlockData(); + if((bitfield & ExportType.TRANSACTION.value) != 0) exportTransactionData(); + if((bitfield & ExportType.NFT.value) != 0) exportNFTData(); + } + + private void exportBlockData() throws IOException { + Writer blockWriter = Files.newBufferedWriter(Paths.get("blocks.csv")); + CSVPrinter blockCsvPrinter = new CSVPrinter(blockWriter, CSVFormat.DEFAULT.withHeader("height", "hash", "prev_hash", "nonce")); + for (Block block : this.blocks) { + List data = new ArrayList<>(); + data.add(Integer.toString(block.getHeight())); + data.add(block.getHash()); + data.add(block.getPrevHash()); + data.add(Integer.toString(block.getNonce())); + blockCsvPrinter.printRecord(data); + } + blockCsvPrinter.flush(); + } + + private void exportTransactionData() throws IOException { + Writer transactionWriter = Files.newBufferedWriter(Paths.get("transactions.csv")); + CSVPrinter transactionCsvPrinter = new CSVPrinter(transactionWriter, CSVFormat.DEFAULT.withHeader("id", "sender", "recipient", "amount", "block_height")); + int transactionCount = 1; + for (Block block : this.blocks) { + for (Transaction transaction : block.getTransactions()) { + List data = new ArrayList<>(); + data.add(Integer.toString(transactionCount++)); + data.add(transaction.getSender()); + data.add(transaction.getRecipient()); + data.add(Integer.toString(transaction.getAmount())); + data.add(Integer.toString(block.getHeight())); + transactionCsvPrinter.printRecord(data); + } + } + transactionCsvPrinter.flush(); + } + + private void exportNFTData() throws IOException { + Writer NFTWriter = Files.newBufferedWriter(Paths.get("nfts.csv")); + CSVPrinter NFTCsvPrinter = new CSVPrinter(NFTWriter, CSVFormat.DEFAULT.withHeader("id", "contract", "owner", "data", "block_height")); + int NFTCount = 1; + for (Block block : this.blocks) { + for (NFT nft : block.getNFTs()) { + List data = new ArrayList<>(); + data.add(Integer.toString(NFTCount++)); + data.add(nft.getContract()); + data.add(nft.getOwner()); + data.add(nft.getData()); + data.add(Integer.toString(block.getHeight())); + NFTCsvPrinter.printRecord(data); + } + } + NFTCsvPrinter.flush(); + } + public int getLength() { return length; } @@ -70,6 +291,16 @@ public class Blockchain { blocks.add(block); length++; if(length != 1 && Main.mainFrame != null) Main.mainFrame.onNewBlockUpdate(); + + if(importToDatabase) { + Main.db.getBlocksTable().insert(block.getHeight(), block.getHash(), block.getPrevHash(), block.getNonce()); + for (Transaction transaction : block.getTransactions()) { + Main.db.getTransactionsTable().insert(transaction.getSender(), transaction.getRecipient(), transaction.getAmount(), block.getHeight()); + } + for (NFT nft : block.getNFTs()) { + Main.db.getNFTsTable().insert(nft.getContract(), nft.getOwner(), nft.getData(), block.getHeight()); + } + } } public int getAddressBalance(String walletAddress) { diff --git a/src/main/java/pl/mikorosa/dziecoin/Main.java b/src/main/java/pl/mikorosa/dziecoin/Main.java index 7294f55..563d5e5 100644 --- a/src/main/java/pl/mikorosa/dziecoin/Main.java +++ b/src/main/java/pl/mikorosa/dziecoin/Main.java @@ -15,16 +15,12 @@ public class Main { public static void main(String[] args) { db = new DatabaseConnection(); - System.out.println(db.getBlocksTable().select()); - System.out.println(db.getTransactionsTable().select()); - System.out.println(db.getNFTsTable().select()); - Wallet w1 = new Wallet(); Wallet w2 = new Wallet(); Wallet w3 = new Wallet(); difficulty = 4; - blockchain = new Blockchain(w1.getAddress()); + blockchain = new Blockchain(w1.getAddress(), false); mainFrame = new MainFrame(); mainFrame.setVisible(true); @@ -58,17 +54,5 @@ public class Main { } catch (BlockchainIntegrityException e) { System.out.println("Blockchain Integrity Exception: " + e.getMessage()); } - - for (Block block : blockchain.getBlocks()) { - System.out.println("Block " + block.getHeight() + ": " + block.getHash()); - for (Transaction transaction : block.getTransactions()) { - System.out.println(" " + transaction.getSender() + " => " + transaction.getRecipient() + " - " + transaction.getAmount()); - } - } - - System.out.println("Balances:"); - System.out.println("Wallet 1: " + blockchain.getAddressBalance(w1.getAddress())); - System.out.println("Wallet 2: " + blockchain.getAddressBalance(w2.getAddress())); - System.out.println("Wallet 3: " + blockchain.getAddressBalance(w3.getAddress())); } } \ No newline at end of file diff --git a/src/main/java/pl/mikorosa/dziecoin/gui/MainFrame.java b/src/main/java/pl/mikorosa/dziecoin/gui/MainFrame.java index 3cfce20..b6dc47a 100644 --- a/src/main/java/pl/mikorosa/dziecoin/gui/MainFrame.java +++ b/src/main/java/pl/mikorosa/dziecoin/gui/MainFrame.java @@ -121,6 +121,7 @@ public class MainFrame extends JFrame { else if(status == Block.VerifyCodes.INVALID_POW_SIGNATURE) JOptionPane.showMessageDialog(null, "Invalid proof of work signature found", "Verification", JOptionPane.ERROR_MESSAGE); else if(status == Block.VerifyCodes.INVALID_BLOCK_HASH) JOptionPane.showMessageDialog(null, "Invalid block hash found", "Verification", JOptionPane.ERROR_MESSAGE); }); + payForTuitionButton.addActionListener(e -> { int input = JOptionPane.showConfirmDialog(null, "6 DC will be sent to pay tuition\nAre you sure?", "Payment Confirmation", JOptionPane.YES_NO_OPTION); if(input == JOptionPane.YES_OPTION) { @@ -146,6 +147,7 @@ public class MainFrame extends JFrame { } } }); + payForAdvanceButton.addActionListener(e -> { int input = JOptionPane.showConfirmDialog(null, "1 DC will be sent to pay advance\nAre you sure?", "Payment Confirmation", JOptionPane.YES_NO_OPTION); if(input == JOptionPane.YES_OPTION) { @@ -220,7 +222,27 @@ public class MainFrame extends JFrame { }); exportButton.addActionListener(e -> { + int bitfield = 0; + if(exportBlockDataCheckBox.isSelected()) bitfield |= Blockchain.ExportType.BLOCK.value; + if(exportTransactionDataCheckBox.isSelected()) bitfield |= Blockchain.ExportType.TRANSACTION.value; + if(exportNFTDataCheckBox.isSelected()) bitfield |= Blockchain.ExportType.NFT.value; + if(bitfield == 0) { + JOptionPane.showMessageDialog(null, "Select something to export"); + return; + } + + try { + bc.exportData(bitfield); + int count = Integer.bitCount(bitfield); + JOptionPane.showMessageDialog(null, "Successfully exported " + count + " file" + ((count > 1) ? "s" : "")); + } catch (IOException ex) { + JOptionPane.showMessageDialog(null, "Failed to export data:\n" + ex.getMessage()); + } + + exportBlockDataCheckBox.setSelected(false); + exportTransactionDataCheckBox.setSelected(false); + exportNFTDataCheckBox.setSelected(false); }); createMintNFTButton.addActionListener(e -> {