diff --git a/Qora/src/api/QoraResource.java b/Qora/src/api/QoraResource.java index fd0ac75c..e9676f80 100644 --- a/Qora/src/api/QoraResource.java +++ b/Qora/src/api/QoraResource.java @@ -11,6 +11,8 @@ import org.json.simple.JSONValue; import controller.Controller; +import gui.ClosingDialog; +import gui.Gui; import lang.Lang; import settings.Settings; import utils.APIUtils; @@ -31,11 +33,11 @@ public String stop() if(Controller.getInstance().doesWalletExists() && !Controller.getInstance().isWalletUnlocked()) { throw ApiErrorFactory.getInstance().createError(ApiErrorFactory.ERROR_WALLET_LOCKED); } - + //STOP - Controller.getInstance().stopAll(); + Controller.getInstance().stopAll(); System.exit(0); - + //RETURN return String.valueOf(true); } diff --git a/Qora/src/controller/Controller.java b/Qora/src/controller/Controller.java index 4f25c873..69acf034 100644 --- a/Qora/src/controller/Controller.java +++ b/Qora/src/controller/Controller.java @@ -16,6 +16,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.Date; import java.util.Enumeration; import java.util.LinkedHashMap; @@ -85,17 +86,18 @@ import utils.ObserverMessage; import utils.Pair; import utils.SysTray; +import utils.TransactionTimestampComparator; import utils.UpdateUtil; import webserver.WebService; public class Controller extends Observable { private static final Logger LOGGER = LogManager.getLogger(Controller.class); - private String version = "0.26.10"; - private String buildTime = "2018-06-13 07:57:00 UTC"; + private String version = "0.26.11"; + private String buildTime = "2018-09-25 12:29:00 UTC"; private long buildTimestamp; - public static final String releaseVersion = "0.26.10"; + public static final String releaseVersion = "0.26.11"; // TODO ENUM would be better here public static final int STATUS_NO_CONNECTIONS = 0; @@ -408,7 +410,7 @@ public void run() { } }); - // TIMER TO SEND HEIGHT TO NETWORK EVERY 5 MIN + // TIMER TO SEND HEIGHT TO RANDOM PEER this.timerPeerHeightUpdate.cancel(); this.timerPeerHeightUpdate = new Timer(); @@ -426,7 +428,7 @@ public void run() { } }; - this.timerPeerHeightUpdate.schedule(action, 5 * 60 * 1000, 5 * 60 * 1000); + this.timerPeerHeightUpdate.schedule(action, 30 * 1000, 30 * 1000); // REGISTER DATABASE OBSERVER this.addObserver(DBSet.getInstance().getTransactionMap()); @@ -579,44 +581,50 @@ public void deleteWalletObserver(Observer o) { } private boolean isStopping = false; + private Object stoppingLock = new Object(); public void stopAll() { - // PREVENT MULTIPLE CALLS - if (!this.isStopping) { - this.isStopping = true; - - // STOP SENDING OUR HEIGHT TO PEERS - this.timerPeerHeightUpdate.cancel(); - - // STOP BLOCK PROCESSOR - LOGGER.info(Lang.getInstance().translate("Stopping block processor")); - ClosingDialog.getInstance().updateProgress("Stopping block processor"); - this.synchronizer.stop(); - - // STOP BLOCK GENERATOR - LOGGER.info(Lang.getInstance().translate("Stopping block generator")); - ClosingDialog.getInstance().updateProgress("Stopping block generator"); - this.blockGenerator.shutdown(); - - // STOP MESSAGE PROCESSOR - LOGGER.info(Lang.getInstance().translate("Stopping message processor")); - ClosingDialog.getInstance().updateProgress("Stopping message processor"); - this.network.stop(); - - // CLOSE DATABASE - LOGGER.info(Lang.getInstance().translate("Closing database")); - ClosingDialog.getInstance().updateProgress("Closing database"); - DBSet.getInstance().close(); + // Prevent multiple calls. + // This method can be called via JVM shutdown hook (e.g. signal), API 'stop' or GUI 'exit', among others. + // In particular, ClosingDialog.getInstance() below can trigger a call to stopAll(). + // We want all successive calls to block until the first call has finished. + synchronized (this.stoppingLock) { + if (!this.isStopping) { + this.isStopping = true; + + // STOP SENDING OUR HEIGHT TO PEERS + this.timerPeerHeightUpdate.cancel(); + + // STOP BLOCK PROCESSOR + LOGGER.info(Lang.getInstance().translate("Stopping block processor")); + ClosingDialog.getInstance().updateProgress("Stopping block processor"); + this.synchronizer.stop(); + + // STOP BLOCK GENERATOR + LOGGER.info(Lang.getInstance().translate("Stopping block generator")); + ClosingDialog.getInstance().updateProgress("Stopping block generator"); + this.blockGenerator.shutdown(); + + // STOP MESSAGE PROCESSOR + LOGGER.info(Lang.getInstance().translate("Stopping message processor")); + ClosingDialog.getInstance().updateProgress("Stopping message processor"); + this.network.stop(); + + // CLOSE DATABASE + LOGGER.info(Lang.getInstance().translate("Closing database")); + ClosingDialog.getInstance().updateProgress("Closing database"); + DBSet.getInstance().close(); - // CLOSE WALLET - LOGGER.info(Lang.getInstance().translate("Closing wallet")); - ClosingDialog.getInstance().updateProgress("Closing wallet"); - this.wallet.close(); + // CLOSE WALLET + LOGGER.info(Lang.getInstance().translate("Closing wallet")); + ClosingDialog.getInstance().updateProgress("Closing wallet"); + this.wallet.close(); - ClosingDialog.getInstance().updateProgress("Creating database backup"); - createDataCheckpoint(); + ClosingDialog.getInstance().updateProgress("Creating database backup"); + createDataCheckpoint(); - LOGGER.info(Lang.getInstance().translate("Closed.")); + LOGGER.info(Lang.getInstance().translate("Closed.")); + } } } @@ -649,6 +657,19 @@ public List getActivePeers() { } public void walletSyncStatusUpdate(int height) { + /* + * Prevent deadlock when a new block arrives from network while we're resyncing wallet. + * + * New block arrival locks Controller and wants Synchronizer, + * but it's possible Synchronizer is locked (e.g. by BlockGenerator) while performing a wallet sync + * and this.setChanged() would want a lock on Controller too, causing a deadlock. + * + * We avoid this by testing for block processing status and exiting early. + */ + + if (DBSet.getInstance().getBlockMap().isProcessing()) + return; + this.setChanged(); this.notifyObservers(new ObserverMessage(ObserverMessage.WALLET_SYNC_STATUS, height)); } @@ -671,9 +692,6 @@ public void onConnect(Peer peer) { if (DBSet.getInstance().isStopped()) return; - // GET HEIGHT - int height = this.blockChain.getHeight(); - if (NTP.getTime() >= Transaction.getPOWFIX_RELEASE()) { // SEND FOUNDMYSELF MESSAGE peer.sendMessage(MessageFactory.getInstance().createFindMyselfMessage(Controller.getInstance().getFoundMyselfID())); @@ -683,8 +701,19 @@ public void onConnect(Peer peer) { } // SEND HEIGHT MESSAGE - LOGGER.trace("Sending our height " + height + " to peer " + peer.getAddress()); - peer.sendMessage(MessageFactory.getInstance().createHeightMessage(height)); + getSendMyHeightToPeer(peer); + + // Resend any unconfirmed transactions + List transactions = DBSet.getInstance().getTransactionMap().getTransactions(); + + // Sort transactions chronologically + Collections.sort(transactions, new TransactionTimestampComparator()); + + // Send unconfirmed transactions + for (Transaction transaction : transactions) { + Message message = MessageFactory.getInstance().createTransactionMessage(transaction); + peer.sendMessage(message); + } if (this.status == STATUS_NO_CONNECTIONS) { // UPDATE STATUS @@ -743,12 +772,17 @@ public void onDisconnect(Peer peer) { // in case they attempt to access DB if (this.isStopping) return; - - // NOTIFY - this.setChanged(); - this.notifyObservers(new ObserverMessage(ObserverMessage.NETWORK_STATUS, this.status)); } } + + // NOTIFY, but in separate thread to avoid MapDB interrupt issue + new Thread() { + @Override + public void run() { + Controller.getInstance().setChanged(); + Controller.getInstance().notifyObservers(new ObserverMessage(ObserverMessage.NETWORK_STATUS, Controller.getInstance().status)); + } + }.start(); } public void onError(Peer peer) { @@ -830,10 +864,25 @@ public void onMessage(Message message) { case Message.BLOCK_TYPE: + // Don't process if we're synchronizing + if (this.status == STATUS_SYNCHRONIZING) + break; + BlockMessage blockMessage = (BlockMessage) message; - // ASK BLOCK FROM BLOCKCHAIN + // Get block from message block = blockMessage.getBlock(); + LOGGER.trace("Received block from peer " + message.getSender().getAddress()); + + // Compare to our blockchain tip + Block blockchainTip = this.blockChain.getLastBlock(); + if (blockchainTip.getHeight() == blockMessage.getHeight() && Arrays.equals(blockchainTip.getSignature(), block.getSignature())) { + // We have this block already but update our peer DB to reflect peer's height anyway + synchronized (this.peerHeight) { + this.peerHeight.put(message.getSender(), blockMessage.getHeight()); + } + break; + } boolean isNewBlockValid = this.blockChain.isNewBlockValid(block); @@ -846,6 +895,18 @@ public void onMessage(Message message) { if (this.isProcessingWalletSynchronize()) break; + /* + * Prevent deadlock when a new block arrives from network while we're resyncing wallet. + * + * New block arrival locks Controller and wants Synchronizer, + * but it's possible Synchronizer is locked (e.g. by BlockGenerator) while performing a wallet sync + * and this.setChanged() would want a lock on Controller too, causing a deadlock. + * + * We avoid this by testing for block processing status and exiting early. + */ + if (DBSet.getInstance().getBlockMap().isProcessing()) + break; + // CHECK IF VALID if (isNewBlockValid && this.synchronizer.process(block)) { LOGGER.info(Lang.getInstance().translate("received new valid block") + " " + block.getHeight()); @@ -858,6 +919,9 @@ public void onMessage(Message message) { excludes.add(message.getSender()); this.network.broadcast(message, excludes); + // Let sender know we've updated + this.getSendMyHeightToPeer(message.getSender()); + // UPDATE ALL PEER HEIGHTS TO OUR HEIGHT /* * synchronized(this.peerHeight) { for(Peer peer: this.peerHeight.keySet()) { this.peerHeight.put(peer, this.blockChain.getHeight()); } @@ -983,9 +1047,22 @@ public void update() { peer = this.getMaxHeightPeer(); if (peer != null) { + // Make a note of pre-sync height so we can tell if anything happened + int preSyncHeight = this.blockChain.getHeight(); + // SYNCHRONIZE FROM PEER LOGGER.info("Synchronizing using peer " + peer.getAddress().getHostAddress() + " with height " + peerHeight.get(peer) + " - ping " + peer.getPing() + "ms"); this.synchronizer.synchronize(peer); + + // If our height has changed, notify our peers + if (this.blockChain.getHeight() > preSyncHeight) { + Block blockchainTip = this.blockChain.getLastBlock(); + LOGGER.debug("Sending our new height " + blockchainTip.getHeight() + " to peers"); + Message message = MessageFactory.getInstance().createHeightMessage(blockchainTip.getHeight()); + + List excludes = new ArrayList(); + this.network.broadcast(message, excludes); + } } Thread.sleep(5 * 1000); @@ -999,6 +1076,8 @@ public void update() { // DISHONEST PEER this.network.onError(peer, e.getMessage()); } + + return; } if (this.peerHeight.size() == 0) { @@ -1354,10 +1433,17 @@ public long getNextBlockGeneratingBalance(Block parent) { // FORGE public void newBlockGenerated(Block newBlock) { - this.synchronizer.process(newBlock); + // Only process if we have connections and are not synchronizing + if (this.status == STATUS_OK) { + if (this.synchronizer.process(newBlock)) { + LOGGER.info("Forged new block " + newBlock.getHeight()); - // BROADCAST - this.broadcastBlock(newBlock); + // BROADCAST + this.broadcastBlock(newBlock); + } else { + LOGGER.info("Couldn't forge new block"); + } + } } public List getUnconfirmedTransactions() { @@ -1655,6 +1741,9 @@ public Pair sendMessage(PrivateKeyAccount sender, Account public Block getBlockByHeight(int parseInt) { byte[] b = DBSet.getInstance().getHeightMap().getBlockByHeight(parseInt); + if (b == null) + return null; + return DBSet.getInstance().getBlockMap().get(b); } diff --git a/Qora/src/database/DBListValueMap.java b/Qora/src/database/DBListValueMap.java index d02fa442..c2ed5e6a 100644 --- a/Qora/src/database/DBListValueMap.java +++ b/Qora/src/database/DBListValueMap.java @@ -78,7 +78,7 @@ protected L getListForAdd(K key) { L newList = this.newListValue(); // If we previous marked entry as deleted then we're essentially recreating a new list - if (this.deleted != null && this.deleted.contains(key)) + if (this.deletedContains(key)) return newList; // If the parent map contains a list for key then we need to duplicate its entries @@ -148,7 +148,7 @@ protected L getListForRemove(K key) { return this.map.get(key); // If we previously marked entry as deleted then we have no list - if (this.deleted != null && this.deleted.contains(key)) + if (this.deletedContains(key)) return null; // If we have no parent or parent has no entry then there's no list diff --git a/Qora/src/database/DBMap.java b/Qora/src/database/DBMap.java index cb4e8ab0..a512bf4a 100644 --- a/Qora/src/database/DBMap.java +++ b/Qora/src/database/DBMap.java @@ -1,6 +1,7 @@ package database; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; @@ -98,7 +99,7 @@ public U get(T key) { return this.map.get(key); // If we've deleted the entry for key, return default value - if (this.deleted != null && this.deleted.contains(key)) + if (this.deletedContains(key)) return this.getDefaultValue(); // We don't have any entry for key (deleted or otherwise) so defer to parent (if present) @@ -201,7 +202,7 @@ public boolean contains(T key) { return true; // Have we deleted the entry for the key? - if (this.deleted != null && this.deleted.contains(key)) + if (this.deletedContains(key)) return false; // deleted, so not present // Defer to parent map (if present) @@ -280,4 +281,36 @@ public void reset() { this.notifyObservers(new ObserverMessage(this.getObservableData().get(NOTIFY_LIST), list)); } } + + /** + * byte[]-aware version of this.deleted.contains(key) + *

+ * We can't use simplistic this.deleted.contains(key) as the underlying ArrayList implementation + * uses Object.equals() to compare as this doesn't work for byte[]. + *

+ * This is only needed for this.deleted as this.parent is a more complex MapDB object. + *

+ * @param key + * @return true if this.deleted list contains key, false otherwise + */ + protected boolean deletedContains(T key) { + if (this.deleted == null || key == null) + return false; + + if (!(key instanceof byte[])) + return this.deleted.contains(key); + + // byte[]-safe + @SuppressWarnings("unchecked") + List deletedKeys = (List) this.deleted; + + byte[] byteKey = (byte[]) key; + + for (byte[] deletedKey : deletedKeys) + if (Arrays.equals(byteKey, deletedKey)) + return true; + + return false; + } + } diff --git a/Qora/src/database/DBMapValueMap.java b/Qora/src/database/DBMapValueMap.java index 1a70be1b..5a803216 100644 --- a/Qora/src/database/DBMapValueMap.java +++ b/Qora/src/database/DBMapValueMap.java @@ -86,7 +86,7 @@ protected M getMapForAdd(K dbKey) { M newMap = this.newMapValue(); // If we previous marked entry as deleted then we're essentially recreating a new map - if (this.deleted != null && this.deleted.contains(dbKey)) + if (this.deletedContains(dbKey)) return newMap; // If the parent DBMap contains a map for dbKey then we need to duplicate its entries @@ -156,7 +156,7 @@ protected M getMapForRemove(K dbKey) { return this.map.get(dbKey); // If we previously marked entry as deleted then we have no map - if (this.deleted != null && this.deleted.contains(dbKey)) + if (this.deletedContains(dbKey)) return null; // If we have no parent or parent has no entry then there's no map diff --git a/Qora/src/network/ConnectionAcceptor.java b/Qora/src/network/ConnectionAcceptor.java index 95bace0d..7750f100 100644 --- a/Qora/src/network/ConnectionAcceptor.java +++ b/Qora/src/network/ConnectionAcceptor.java @@ -27,6 +27,8 @@ public ConnectionAcceptor(ConnectionCallback callback) { } public void run() { + Thread.currentThread().setName("ConnAcceptor"); + this.isRun = true; while (isRun) { diff --git a/Qora/src/network/ConnectionCreator.java b/Qora/src/network/ConnectionCreator.java index 76bb7bfc..929ed79f 100644 --- a/Qora/src/network/ConnectionCreator.java +++ b/Qora/src/network/ConnectionCreator.java @@ -24,6 +24,8 @@ public ConnectionCreator(ConnectionCallback callback) { } public void run() { + Thread.currentThread().setName("ConnCreator"); + this.isRun = true; while (isRun) { diff --git a/Qora/src/network/Network.java b/Qora/src/network/Network.java index f5ff6199..720a54f0 100644 --- a/Qora/src/network/Network.java +++ b/Qora/src/network/Network.java @@ -16,6 +16,7 @@ import network.message.FindMyselfMessage; import network.message.Message; import network.message.MessageFactory; +import settings.Settings; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -115,7 +116,7 @@ public void onError(Peer peer, String error) { } // ADD TO BLACKLIST - PeerManager.getInstance().blacklistPeer(peer); + // PeerManager.getInstance().blacklistPeer(peer); // PASS TO CONTROLLER Controller.getInstance().onError(peer); @@ -233,7 +234,7 @@ public void onMessage(Message message) { } public void broadcast(Message message, List exclude) { - LOGGER.debug(Lang.getInstance().translate("Broadcasting")); + LOGGER.trace(Lang.getInstance().translate("Broadcasting") + " message type " + message.getType()); try { synchronized (this.connectedPeers) { @@ -249,7 +250,7 @@ public void broadcast(Message message, List exclude) { // Iterator fast-fail due to change in connectedPeers } - LOGGER.debug(Lang.getInstance().translate("Broadcasting end")); + LOGGER.trace(Lang.getInstance().translate("Broadcasting end") + " message type " + message.getType()); } @Override @@ -287,17 +288,10 @@ public void stop() { public static boolean isHostLocalAddress(InetAddress address) { // easy address checks first - if (address.isSiteLocalAddress() || address.isLoopbackAddress() || address.isAnyLocalAddress()) + if (address.isLoopbackAddress() || address.isAnyLocalAddress()) return true; - try { - // Is address bound to one of our interfaces? - NetworkInterface netIf = NetworkInterface.getByInetAddress(address); - return netIf != null; - } catch (Exception e) { - // Couldn't check - play safe and say it's local - return true; - } + return Settings.getInstance().isLocalAddress(address); } } diff --git a/Qora/src/network/Peer.java b/Qora/src/network/Peer.java index c30904ad..78adbadc 100644 --- a/Qora/src/network/Peer.java +++ b/Qora/src/network/Peer.java @@ -293,7 +293,7 @@ public void run() { if (socket == null || socket.isClosed()) { LOGGER.debug(Lang.getInstance().translate("Socket already closed") + " " + address); } else { - LOGGER.info(Lang.getInstance().translate("Socket issue with peer") + " " + address + e.getMessage()); + LOGGER.info(Lang.getInstance().translate("Socket issue with peer") + " " + address + ": " + e.getMessage()); } // Disconnect peer @@ -346,7 +346,7 @@ public boolean sendMessage(Message message) { // RETURN return true; } catch (Exception e) { - LOGGER.debug(e.getMessage(), e); + LOGGER.trace(e.getMessage(), e); // ERROR this.callback.onError(this, e.getMessage()); diff --git a/Qora/src/network/Pinger.java b/Qora/src/network/Pinger.java index d3be2906..3cc82b4f 100644 --- a/Qora/src/network/Pinger.java +++ b/Qora/src/network/Pinger.java @@ -127,16 +127,7 @@ public void run() { * @see Peer#close() */ public void stopPing() { - if (this.isAlive()) { + if (this.isAlive()) this.interrupt(); - - try { - this.join(); - } catch (InterruptedException e) { - // We've probably reached here from run() above calling - // Peer.onPingFailure() so when we return run() above will - // terminate - } - } } } diff --git a/Qora/src/qora/BlockBuffer.java b/Qora/src/qora/BlockBuffer.java index 750b0006..53cba1d6 100644 --- a/Qora/src/qora/BlockBuffer.java +++ b/Qora/src/qora/BlockBuffer.java @@ -91,11 +91,15 @@ public void run() } //CHECK BLOCK SIGNATURE + // Don't check block signature as blocks may arrive out-of-order + // and signature might depend on processing of previous blocks + /* if(!response.getBlock().isSignatureValid()) { error = true; return; } + */ //ADD TO LIST blockingQueue.add(response.getBlock()); diff --git a/Qora/src/qora/BlockGenerator.java b/Qora/src/qora/BlockGenerator.java index 540bc867..889a862a 100644 --- a/Qora/src/qora/BlockGenerator.java +++ b/Qora/src/qora/BlockGenerator.java @@ -183,6 +183,7 @@ public void run() if(!Controller.getInstance().isUpToDate() && !Controller.getInstance().isProcessingWalletSynchronize()) { Controller.getInstance().update(); + continue; } //CHECK IF WE HAVE CONNECTIONS diff --git a/Qora/src/qora/Synchronizer.java b/Qora/src/qora/Synchronizer.java index 5f4501cc..815b5fc2 100644 --- a/Qora/src/qora/Synchronizer.java +++ b/Qora/src/qora/Synchronizer.java @@ -266,8 +266,7 @@ private void orphanBackToCommonBlock(DBSet dbOrFork, Block lastCommonBlock, List lastBlock = dbOrFork.getBlockMap().getLastBlock(); } - // XXX: Why 11? - while (lastBlock.getHeight() >= height && lastBlock.getHeight() > 11) { + while (lastBlock.getHeight() >= height && lastBlock.getHeight() > 1) { // Optionally save orphaned transactions if caller has provided // storage if (orphanedTransactions != null) diff --git a/Qora/src/qora/block/Block.java b/Qora/src/qora/block/Block.java index 953934d5..3c7133c2 100644 --- a/Qora/src/qora/block/Block.java +++ b/Qora/src/qora/block/Block.java @@ -580,6 +580,10 @@ public boolean isValid(DBSet db) return false; } + // Check parent doesn't already have a child + if (this.getParent(db).getChild(db) != null) + return false; + //CHECK IF TIMESTAMP IS VALID -500 MS ERROR MARGIN TIME if(this.timestamp - 500 > NTP.getTime() || this.timestamp < this.getParent(db).timestamp) { diff --git a/Qora/src/settings/Settings.java b/Qora/src/settings/Settings.java index e74bb2b6..4ed20239 100644 --- a/Qora/src/settings/Settings.java +++ b/Qora/src/settings/Settings.java @@ -8,6 +8,7 @@ import java.net.NetworkInterface; import java.net.SocketException; import java.net.URL; +import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; @@ -121,7 +122,6 @@ public static void FreeInstance() private Settings() { - this.localAddress = this.getCurrentIp(); int alreadyPassed = 0; File file = new File(""); @@ -211,6 +211,9 @@ private Settings() LOGGER.error(e.getMessage(),e); this.peersJSON = new JSONObject(); } + + this.localAddress = this.getCurrentIp(); + LOGGER.debug("Our IP: " + this.localAddress); } public JSONObject Dump() @@ -785,7 +788,14 @@ public boolean isLocalAddress(InetAddress address) { } public InetAddress getCurrentIp() { - try { + if (this.settingsJSON.containsKey("localAddress")) + try { + return InetAddress.getByName((String) this.settingsJSON.get("localAddress")); + } catch (UnknownHostException e) { + // fall through to other methods + } + + try { Enumeration networkInterfaces = NetworkInterface .getNetworkInterfaces(); while (networkInterfaces.hasMoreElements()) { diff --git a/Qora/src/test/CryptoTests.java b/Qora/src/test/CryptoTests.java new file mode 100644 index 00000000..0b51f18c --- /dev/null +++ b/Qora/src/test/CryptoTests.java @@ -0,0 +1,36 @@ +package test; + +import java.io.IOException; + +import org.junit.Test; + +import qora.crypto.RIPEMD160; +import utils.Converter; + +public class CryptoTests { + + public static void main(String args[]) throws IOException { + final int nBytes = System.in.available(); + byte[] input = new byte[nBytes]; + System.in.read(input); + + RIPEMD160 ripEmd160 = new RIPEMD160(); + byte[] output = ripEmd160.digest(input); + + System.out.println(Converter.toHex(output)); + } + + @Test + public void testRIPEMD160() throws Exception { + // byte[] input = Converter.parseHexString("0000000000000000000000000000000000000000000000000000000000000000"); + // byte[] input = Converter.parseHexString("4186612a675689c1e20c8078da482576e9e99a3cc0602f88882d5e641eb38785"); + // byte[] input = "The quick brown fox jumps over the lazy cog".getBytes(); + byte[] input = Converter.parseHexString("ff"); + + RIPEMD160 ripEmd160 = new RIPEMD160(); + byte[] output = ripEmd160.digest(input); + + System.out.println(Converter.toHex(output)); + } + +}