diff --git a/src/main/java/fucoin/AbstractNode.java b/src/main/java/fucoin/AbstractNode.java index 7fd042fe65bac4f7e1d544290f1e6d8c1733d232..7b7f943c10982101fddc28c673a24319fbc02d19 100644 --- a/src/main/java/fucoin/AbstractNode.java +++ b/src/main/java/fucoin/AbstractNode.java @@ -3,13 +3,13 @@ package fucoin; import akka.actor.ActorRef; import akka.actor.Address; import akka.actor.UntypedActor; +import com.google.gson.Gson; import fucoin.actions.transaction.ActionGetAmount; import fucoin.wallet.AbstractWallet; -import java.io.File; -import java.io.FileOutputStream; -import java.io.OutputStreamWriter; -import java.io.Serializable; +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Paths; import java.time.LocalDateTime; import java.util.HashMap; @@ -73,26 +73,82 @@ public abstract class AbstractNode extends UntypedActor implements Serializable return knownNeighbors; } - private HashMap<LocalDateTime, String> snapshot = new HashMap(); + /** + * Stores all snapshots. + */ + private HashMap<LocalDateTime, String> snapshots = new HashMap(); + + /** + * Last loaded snapshot. + */ + protected LocalDateTime lastRetrievedSnapshot = null; + + /** + * Return all Snapshots. + * + * @return Map of snapshot name to snapshot as string + */ + public HashMap<LocalDateTime, String> getSnapshots() { + return snapshots; + } - public HashMap<LocalDateTime, String> getSnapshot() { - return snapshot; + /** + * Checks if the last loaded snapshot was snapshot "time". + * @param time Snapshot name. + * @return true = was the last loaded snapshot, false = no wasn't the last loaded snapshot + */ + public boolean isLastLoadedSnapshot(LocalDateTime time) { + if (lastRetrievedSnapshot.equals(time)) { + return true; + } + return false; } - public String readSnapshot(LocalDateTime time) { - return getSnapshot().get(time); + /** + * Read a snapshot from memory or disk. + * @param name Node name + * @param time Snapshot name + * @return Empty or String with a valid json. + */ + public String readSnapshot(String name, LocalDateTime time) { + lastRetrievedSnapshot = time; + if (snapshots.containsKey(time)) { + return getSnapshots().get(time); + } + + String[] identifier = getSnapshotDirAndFilename(name, time); + + try { + File theDir = new File(identifier[1]); + if (!theDir.exists()) { + return ""; + } + byte[] encoded = Files.readAllBytes(Paths.get(identifier[1])); + String content = new String(encoded); + + //store snapshot to memory + snapshots.put(time, content); + return content; + } catch (IOException e) { + return ""; + } + } + /** + * Stores snapshot to memory and persistent to disk. + * @param name Name of the node. + * @param content Serialized object. + * @param time Snapshot name. + * @return if was successful or not. + */ public boolean writeSnapshot(String name, String content, LocalDateTime time) { - if (getSnapshot().containsKey(time)) { + if (getSnapshots().containsKey(time)) { return false; } - getSnapshot().put(time, content); - - String folder = time.getYear() + "-" + time.getMonthValue() + "-" + time.getDayOfMonth() + " " + - time.getHour() + "." + time.getMinute() + "." + time.getSecond(); - String filename = "snapshots/" + folder + "/" + name + ".json"; + getSnapshots().put(time, content); + String[] identifier = getSnapshotDirAndFilename(name, time); try { File theDir = new File("snapshots"); @@ -100,12 +156,12 @@ public abstract class AbstractNode extends UntypedActor implements Serializable if (!theDir.exists()) { theDir.mkdir(); } - theDir = new File("snapshots/" + folder); + theDir = new File("snapshots/" + identifier[0]); if (!theDir.exists()) { theDir.mkdir(); } - File myFile = new File(filename); + File myFile = new File(identifier[1]); myFile.createNewFile(); FileOutputStream fOut = new FileOutputStream(myFile); OutputStreamWriter myOutWriter = new OutputStreamWriter(fOut); @@ -113,8 +169,26 @@ public abstract class AbstractNode extends UntypedActor implements Serializable myOutWriter.close(); fOut.close(); } catch (Exception e) { - e.printStackTrace(); + return false; } return true; } + + /** + * Create the file path to snapshot. + * @param name Name of the node + * @param time Snapshot name + * @return Array[snapshot folder name, path to snapshot include filename] + */ + private String[] getSnapshotDirAndFilename(String name, LocalDateTime time) { + String folder = time.getYear() + "-" + time.getMonthValue() + "-" + time.getDayOfMonth() + " " + + time.getHour() + "." + time.getMinute() + "." + time.getSecond(); + return new String[]{folder, "snapshots/" + folder + "/" + name + ".json"}; + } + + private Statistics statistics = new Statistics(); + + public Statistics getStatistics() { + return statistics; + } } \ No newline at end of file diff --git a/src/main/java/fucoin/wallet/QDigest.java b/src/main/java/fucoin/QDigest.java similarity index 99% rename from src/main/java/fucoin/wallet/QDigest.java rename to src/main/java/fucoin/QDigest.java index 36bc7fad9e6c54c168adaa768ae19dc4a00c5cc8..a1ed8e2eaf77338c153dfed9d4ca9b8c9618ac1c 100644 --- a/src/main/java/fucoin/wallet/QDigest.java +++ b/src/main/java/fucoin/QDigest.java @@ -1,4 +1,4 @@ -package fucoin.wallet; +package fucoin; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; diff --git a/src/main/java/fucoin/Statistics.java b/src/main/java/fucoin/Statistics.java new file mode 100644 index 0000000000000000000000000000000000000000..d1eb3a0141fe867392b0e503e923ed7a8f6bb013 --- /dev/null +++ b/src/main/java/fucoin/Statistics.java @@ -0,0 +1,22 @@ +package fucoin; + +import java.io.Serializable; + +public class Statistics implements Serializable { + /** + * Q-Digest Compression: When the node value at binary tree is higher then sCompression apply q-digest compress. + */ + private static final double sCompression = 5; + /** + * Q-Digest data structure to creating histograms or answer queries for mean or x-quantil. + */ + private transient QDigest qDigest = new QDigest(sCompression); + + public QDigest getqDigest() { + return qDigest; + } + + public void setqDigest(QDigest qDigest) { + this.qDigest = qDigest; + } +} diff --git a/src/main/java/fucoin/actions/control/ActionSuperVisorCreateSnapshot.java b/src/main/java/fucoin/actions/control/ActionSuperVisorCreateSnapshot.java index 26840270ff6ba4b1ca51fd952404029efc0613a3..faa271b6611f92a602d6f7033442b1ec46570ce6 100644 --- a/src/main/java/fucoin/actions/control/ActionSuperVisorCreateSnapshot.java +++ b/src/main/java/fucoin/actions/control/ActionSuperVisorCreateSnapshot.java @@ -21,7 +21,14 @@ public class ActionSuperVisorCreateSnapshot extends SuperVisorAction { protected void onAction(ActorRef sender, ActorRef self, UntypedActorContext context, SuperVisorImpl abstractNode) { //write the snapshot if (!abstractNode.writeSnapshot(time)) { + //snapshot already exists return; } + + //notify one in the network + for(ActorRef startPoint: abstractNode.getKnownNeighbors().values()) { + startPoint.tell(new ActionWalletCreateSnapshot(time), self); + break; + } } } diff --git a/src/main/java/fucoin/actions/transaction/ActionCommitDistributedCommittedTransfer.java b/src/main/java/fucoin/actions/transaction/ActionCommitDistributedCommittedTransfer.java index 30c5063519445d28aa81ee5293d41bc67dbbd5d8..d17f3f3417b1aa15e9d83ae2454d3475aae7e678 100644 --- a/src/main/java/fucoin/actions/transaction/ActionCommitDistributedCommittedTransfer.java +++ b/src/main/java/fucoin/actions/transaction/ActionCommitDistributedCommittedTransfer.java @@ -50,7 +50,7 @@ public class ActionCommitDistributedCommittedTransfer extends ClientAction { wallet.setAmount(wallet.getAmount() - amount); wallet.addTransactionLogMessageSuccess("Sent " + amount + " FUC to " + target.path().name()); } else if (target.compareTo(self) == 0) { - wallet.getqDigest().offer(amount); + wallet.getStatistics().getqDigest().offer(amount); wallet.setAmount(wallet.getAmount() + amount); wallet.addTransactionLogMessageSuccess("Received " + amount + " FUC from " + source.path().name()); } diff --git a/src/main/java/fucoin/configurations/AbstractConfiguration.java b/src/main/java/fucoin/configurations/AbstractConfiguration.java index dc77bc4679152fb969125ea1625d6ec94fbaa3d2..f391a7ed9d4c98e8d62743f0fff1ab3a4b5b5421 100644 --- a/src/main/java/fucoin/configurations/AbstractConfiguration.java +++ b/src/main/java/fucoin/configurations/AbstractConfiguration.java @@ -117,12 +117,23 @@ public abstract class AbstractConfiguration extends AbstractNode { } protected void nextRandomTransaction() { + nextRandomTransaction(true); + } + + protected void nextRandomTransaction(boolean startConcurrentTransactions) { if (remainingTransactions <= 0) { return; } System.out.println("Next Random Transaction, " + remainingTransactions + " left."); + if (startConcurrentTransactions) { + int startTransactions = ThreadLocalRandom.current().nextInt(5); + for (int i = 0; i < startTransactions; i++) { + nextRandomTransaction(false); + } + } + remainingTransactions--; try { randomTransaction(); diff --git a/src/main/java/fucoin/gui/SuperVisorGuiControlImpl.java b/src/main/java/fucoin/gui/SuperVisorGuiControlImpl.java index 64e7f9189d64375e902df27bfb289c15aeb203f5..429b2e1e53099341a7e3c9b746ef7fb7c522c8e0 100644 --- a/src/main/java/fucoin/gui/SuperVisorGuiControlImpl.java +++ b/src/main/java/fucoin/gui/SuperVisorGuiControlImpl.java @@ -25,7 +25,6 @@ public class SuperVisorGuiControlImpl implements SuperVisorGuiControl { private List<Snapshot> loadedSnapshotsOfWallets = new LinkedList<>(); - private LinkedList<LocalDateTime> snapshotTimes = new LinkedList<>(); private LocalDateTime loadedSnapshotDateTime; private SuperVisorThreadGUI threadGUI; @@ -39,16 +38,14 @@ public class SuperVisorGuiControlImpl implements SuperVisorGuiControl { } private void init() { - amountTableModel = new AmountTableModel(); threadGUI = new SuperVisorThreadGUI(this); threadGUI.init(); - } public void createSnapshot(LocalDateTime time) { - snapshotTimes.add(time); + superVisor.writeSnapshot(time); for (Map.Entry<String, ActorRef> item : superVisor.getKnownNeighbors().entrySet()) { item.getValue().tell(new ActionWalletCreateSnapshot(time), ActorRef.noSender()); break; @@ -86,6 +83,14 @@ public class SuperVisorGuiControlImpl implements SuperVisorGuiControl { return loadedSnapshotsOfWallets; } + public fucoin.supervisor.Snapshot getSupervisorSnapshot(LocalDateTime time) { + return superVisor.readSnapShot(time); + } + + public List<LocalDateTime> getSnapshotList() { + return superVisor.getSnapshotList(); + } + public void guiTerminated() { superVisor.exit(); } diff --git a/src/main/java/fucoin/gui/SuperVisorHistogramGUI.java b/src/main/java/fucoin/gui/SuperVisorHistogramGUI.java index e955dec33a8a740ce9162374ae6d4a079a801660..1c704a9b37a6c4cfc9b260ff7ac2bb9f79007db7 100644 --- a/src/main/java/fucoin/gui/SuperVisorHistogramGUI.java +++ b/src/main/java/fucoin/gui/SuperVisorHistogramGUI.java @@ -1,6 +1,6 @@ package fucoin.gui; -import fucoin.wallet.QDigest; +import fucoin.QDigest; import fucoin.wallet.Snapshot; import org.jfree.chart.ChartFactory; import org.jfree.chart.ChartPanel; diff --git a/src/main/java/fucoin/gui/SuperVisorThreadGUI.java b/src/main/java/fucoin/gui/SuperVisorThreadGUI.java index d1d3ef351597781b567a121734ec88f765f22e23..e33d89f1d4af98153e330f3a3f2f0110a05b23f1 100644 --- a/src/main/java/fucoin/gui/SuperVisorThreadGUI.java +++ b/src/main/java/fucoin/gui/SuperVisorThreadGUI.java @@ -68,6 +68,7 @@ public class SuperVisorThreadGUI { configPanel.add(showDebug); JPanel snapshotPanel = new JPanel(); + superVisorGuiControl.getSnapshotList().forEach(time -> dropdown.addItem(time)); snapshotPanel.add(dropdown); //logPanel.add(activateLogging, BorderLayout.NORTH); diff --git a/src/main/java/fucoin/supervisor/Snapshot.java b/src/main/java/fucoin/supervisor/Snapshot.java index f616d7d6d43b3fde9c896a028f93a3c1b5d4dfb1..396ed413fcc6c91985ce462884a8325fd16ab189 100644 --- a/src/main/java/fucoin/supervisor/Snapshot.java +++ b/src/main/java/fucoin/supervisor/Snapshot.java @@ -1,16 +1,38 @@ package fucoin.supervisor; +import fucoin.QDigest; +import fucoin.Statistics; + import java.io.Serializable; +import java.util.List; public class Snapshot implements Serializable { private final AmountTableModel amountTableModel; + private final Statistics statistics; + private final List<long[]> qDigestVal; + private final byte[] qDigest; public Snapshot(SuperVisorImpl superVisor) { this.amountTableModel = superVisor.getGui().getAmountTableModel(); + this.statistics = superVisor.getStatistics(); + this.qDigest = QDigest.serialize(superVisor.getStatistics().getqDigest()); + this.qDigestVal = superVisor.getStatistics().getqDigest().toAscRanges(); } public AmountTableModel getAmountTableModel() { return amountTableModel; } + + public Statistics getStatistics() { + return statistics; + } + + public List<long[]> getqDigestVal() { + return qDigestVal; + } + + public QDigest getqDigest() { + return QDigest.deserialize(this.qDigest); + } } diff --git a/src/main/java/fucoin/supervisor/SuperVisorImpl.java b/src/main/java/fucoin/supervisor/SuperVisorImpl.java index b7b85a16b160824b01184cc1055ded1e99a6ddcf..1f9f205efc9fdbecb534133cbdc098716bd6079b 100644 --- a/src/main/java/fucoin/supervisor/SuperVisorImpl.java +++ b/src/main/java/fucoin/supervisor/SuperVisorImpl.java @@ -3,6 +3,9 @@ package fucoin.supervisor; import akka.actor.ActorRef; import akka.actor.Props; import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import fucoin.AbstractNode; import fucoin.actions.Action; import fucoin.actions.control.ActionWalletCreationDone; import fucoin.actions.persist.ActionInvokeUpdate; @@ -10,16 +13,12 @@ import fucoin.actions.transaction.ActionGetAmountAnswer; import fucoin.actions.transaction.ActionNotifyObserver; import fucoin.actions.transaction.SuperVisorAction; import fucoin.gui.SuperVisorGuiControl; -import fucoin.AbstractNode; import fucoin.gui.TransactionLogger; -import fucoin.supervisor.Snapshot; import javax.swing.*; +import java.io.*; import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.Map.Entry; public class SuperVisorImpl extends AbstractNode implements TransactionLogger { @@ -34,7 +33,7 @@ public class SuperVisorImpl extends AbstractNode implements TransactionLogger { private ActorRef bankCommitObserver = null; public SuperVisorImpl() { - + readSnapshotList(); } public void setGuiControl(SuperVisorGuiControl gui) { @@ -175,11 +174,54 @@ public class SuperVisorImpl extends AbstractNode implements TransactionLogger { this.bankCommitObserver = bankCommitObserver; } + private List<LocalDateTime> snapshotList = new LinkedList<>(); + private final static String sSnapshotListName = "snapshots/list.json"; + + public List<LocalDateTime> getSnapshotList() { + return snapshotList; + } + + private void readSnapshotList() { + try { + File file = new File(sSnapshotListName); + if (!file.exists()) { + return; + } + Gson gson = new Gson(); + JsonReader reader = new JsonReader(new FileReader(sSnapshotListName)); + snapshotList = gson.fromJson(reader, new TypeToken<List<LocalDateTime>>() {}.getType()); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + } + + private void writeSnapshotList() { + try { + File myFile = new File(sSnapshotListName); + myFile.createNewFile(); + FileOutputStream fOut = new FileOutputStream(myFile); + OutputStreamWriter myOutWriter = new OutputStreamWriter(fOut); + myOutWriter.append(new Gson().toJson(snapshotList)); + myOutWriter.close(); + fOut.close(); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private void addSnapshot(LocalDateTime time) { + snapshotList.add(time); + writeSnapshotList(); + } + public boolean writeSnapshot(LocalDateTime time) { + addSnapshot(time); return super.writeSnapshot("SuperVisor", new Gson().toJson(new Snapshot(this)), time); } public Snapshot readSnapShot(LocalDateTime time) { - return new Gson().fromJson(super.readSnapshot(time), Snapshot.class); + return new Gson().fromJson(super.readSnapshot("SuperVisor", time), Snapshot.class); } } diff --git a/src/main/java/fucoin/wallet/AbstractWallet.java b/src/main/java/fucoin/wallet/AbstractWallet.java index f831e2ea4714c6196b00e557925148c5b97886d1..9b09de9d5521fef3d539018495edb97a8da56afa 100644 --- a/src/main/java/fucoin/wallet/AbstractWallet.java +++ b/src/main/java/fucoin/wallet/AbstractWallet.java @@ -4,6 +4,7 @@ import akka.actor.ActorRef; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import fucoin.AbstractNode; +import fucoin.QDigest; import fucoin.gui.TransactionLogger; import java.io.Serializable; @@ -13,10 +14,6 @@ import java.time.LocalDateTime; * */ public abstract class AbstractWallet extends AbstractNode implements Serializable, TransactionLogger { - - private static final double sCompression = 5; - private QDigest qDigest = new QDigest(sCompression); - /** * Currently amount of this wallet */ @@ -120,22 +117,12 @@ public abstract class AbstractWallet extends AbstractNode implements Serializabl */ public abstract void send(String address, int amount, ActorRef observer); - - public QDigest getqDigest() { - return qDigest; - } - - public void setqDigest(QDigest qDigest) { - this.qDigest = qDigest; - } - - public boolean writeSnapshot(LocalDateTime time) { return super.writeSnapshot(getName(), (new GsonBuilder().setPrettyPrinting().create()).toJson(new Snapshot(this)), time); } public Snapshot readSnapShot(LocalDateTime time) { - return new Gson().fromJson(super.readSnapshot(time), Snapshot.class); + return new Gson().fromJson(super.readSnapshot(getName(), time), Snapshot.class); } } diff --git a/src/main/java/fucoin/wallet/Snapshot.java b/src/main/java/fucoin/wallet/Snapshot.java index a2fc864b4804943baa19e9a4df987660fd95a4db..cb2d2f4366a34866f0e70da7a46500c8a6109112 100644 --- a/src/main/java/fucoin/wallet/Snapshot.java +++ b/src/main/java/fucoin/wallet/Snapshot.java @@ -1,6 +1,7 @@ package fucoin.wallet; -import akka.actor.ActorRef; +import fucoin.QDigest; +import fucoin.Statistics; import java.io.Serializable; import java.util.HashMap; @@ -12,6 +13,7 @@ public class Snapshot implements Serializable { private final int amount; private final Set<String> knownNeighbour; private final HashMap<String, Integer> knownNeighbourAmounts = new HashMap<>(); + private final Statistics statistics; private final List<long[]> qDigestVal; private final byte[] qDigest; @@ -19,12 +21,13 @@ public class Snapshot implements Serializable { public Snapshot(AbstractWallet wallet) { this.name = wallet.getName(); this.amount = wallet.getAmount(); - this.qDigest = QDigest.serialize(wallet.getqDigest()); - this.qDigestVal = wallet.getqDigest().toAscRanges(); + this.statistics = wallet.getStatistics(); this.knownNeighbour = wallet.getKnownNeighbors().keySet(); wallet.amounts.forEach((actorRef, amount) -> { knownNeighbourAmounts.put(actorRef.toString(), amount); }); + this.qDigest = QDigest.serialize(wallet.getStatistics().getqDigest()); + this.qDigestVal = wallet.getStatistics().getqDigest().toAscRanges(); } public String getName() { @@ -35,6 +38,10 @@ public class Snapshot implements Serializable { return amount; } + public Statistics getStatistics() { + return statistics; + } + public Set<String> getKnownNeighbour() { return knownNeighbour; }