From a3ec5d2ed3b09c6b75c9dbd32a208e9cd6bfcc52 Mon Sep 17 00:00:00 2001 From: FIMKrypto Date: Sat, 20 Dec 2014 20:37:37 +0100 Subject: [PATCH] Completing 0.3.3 release --- changelogs-fim/fim-0.3.3.changelog.txt | 45 ++++++++++++++++-- conf/nxt-default.properties | 4 ++ src/java/nxt/BlockImpl.java | 42 +++-------------- src/java/nxt/BlockchainProcessorImpl.java | 17 ++++++- src/java/nxt/Constants.java | 2 + src/java/nxt/DbVersion.java | 32 +++++++++---- src/java/nxt/Locked.java | 23 ++++++--- src/java/nxt/peer/Peer.java | 2 + src/java/nxt/peer/PeerImpl.java | 57 ++++++++++++++++++++++- src/java/nxt/peer/PeerServlet.java | 8 ++-- src/java/nxt/peer/Peers.java | 17 ++++--- 11 files changed, 178 insertions(+), 71 deletions(-) diff --git a/changelogs-fim/fim-0.3.3.changelog.txt b/changelogs-fim/fim-0.3.3.changelog.txt index 2f3734f55..e1787cea2 100644 --- a/changelogs-fim/fim-0.3.3.changelog.txt +++ b/changelogs-fim/fim-0.3.3.changelog.txt @@ -1,7 +1,46 @@ FIMK 0.3.3 is based on NXT 1.2.6. -Temporarily blocked all transactions from acccounts with confirmed stolen funds -after a major theft of 57M FIMK +This is a hard fork everyone MUST update to this release. This release and all future releases blacklist any peer that is on or below -version 0.3.2. +version 0.3.2. + +On startup a one time scan and verification of the blockchain will be performed. +Deleting your folder blockchain is not required for this update. + +About the hard fork: + + On Friday December 19th 2014 we were informed of a theft of 57 Million FIMK + from a user's account by a hacker, who gained gained access to the accounts + through the user carelessly sending his account passphrases to a rigged public + node. The hacker transferred a total of approx. 57 Million FIMK from several + accounts to 59 other accounts, each account receiving roughly 1M FIMK. + In total this amounts to 13.9 % of all FIMK in existence at this time. + + As the portion of stolen funds is a significant portion of the total FIMK in + circulation, the association is obliged to intervene. To avoid a situation + where FIMK would at the mercy of a thief in possession of a large portion of + total funds, strengthening her stake by forging constantly and thus gaining + undue control of the network, AND likely dumping some of the large stash + constantly rendering the remaining FIMK worthless (as has happened before with + other coins in similar large scale incidents) the association decided to freeze + the hacker assets by releasing customized emergency version of the FIMKrypto + server which does just that. + + This release temporarily seals the stolen FIMK in the protocol making them + untouchable by no one, not even by the association. This is done to prevent + mixing of the stolen funds through further transfers to obfuscate the origin, + which we were able to prevent just in time. The following steps and measures + to be taken for the frozen stash are discussed with the community and decided + on the basis of those discussions. + +Mandatory update + + Earlier server releases (before 0.3.3) are blacklisted by default to prevent + the chance of future forks and keep the network coherent. + +Hacker accounts locked + + There are 59 accounts identified that hold the stolen FIMK, the public keys to + these accounts are locked from making outgoing transactions and are not allowed + to forge blocks. diff --git a/conf/nxt-default.properties b/conf/nxt-default.properties index c9166f3cb..0328a902e 100644 --- a/conf/nxt-default.properties +++ b/conf/nxt-default.properties @@ -28,6 +28,9 @@ nxt.wellKnownPeers= # Known bad peers to be blacklisted nxt.knownBlacklistedPeers= +# Known peers to be whitelisted +nxt.knownWhitelistedPeers= + # Peers used for testnet only. nxt.testnetPeers=178.62.176.45; 178.62.176.46 @@ -179,6 +182,7 @@ nxt.enableStackTraces=true # Jetty logging is now specified in logging properties. # Used for debugging peer to peer communications. +# 1=exceptions, 2=non 200 response, 4=200 response nxt.communicationLoggingMask=0 # Track balances of the following accounts and related events for debugging purposes. diff --git a/src/java/nxt/BlockImpl.java b/src/java/nxt/BlockImpl.java index 6ded3b081..dece08bb6 100644 --- a/src/java/nxt/BlockImpl.java +++ b/src/java/nxt/BlockImpl.java @@ -377,6 +377,13 @@ boolean verifyGenerationSignature() throws BlockchainProcessor.BlockOutOfOrderEx } } + /* XXX - Prevent stolen funds to forge blocks */ + if ( ! Locked.allowedToForge(account.getPublicKey())) { + + Logger.logMessage("Public key not allowed to forge blocks"); + return true; + } + /* XXX - fix for invalid generation signature block 96249 */ if (previousBlock.height == 96248 && Arrays.equals(GENERATION_SIG_96248, previousBlock.generationSignature) && @@ -407,41 +414,6 @@ boolean verifyGenerationSignature() throws BlockchainProcessor.BlockOutOfOrderEx } } -// -// class Whitelist { -// int previousBlockHeight; -// byte[] previousBlockGenerationSig; -// byte[] generationSignature; -// Whitelist(int previousBlockHeight, byte[] previousBlockGenerationSig, byte[] generationSignature) { -// this.previousBlockHeight = previousBlockHeight; -// this.previousBlockGenerationSig = previousBlockGenerationSig; -// this.generationSignature = generationSignature; -// } -// boolean match(BlockImpl previousBlock, byte[] generationSignature) { -// return previousBlock.height == this.previousBlockHeight && -// Arrays.equals(this.previousBlockGenerationSig, previousBlock.generationSignature) && -// Arrays.equals(this.generationSignature, generationSignature); -// } -// } -// -// final byte[] GENERATION_SIG_96248 = Convert.parseHexString("942b93195bcb48045019f38859606c1b4aefe98751dd97833631c7fab2c9edfd"); -// final byte[] GENERATION_SIG_96249 = Convert.parseHexString("5f5c132acef36a1d329b69c45d1d26b3c3941e7679a6254ce825685100e04dd4"); -// -// boolean isWhiteListed(BlockImpl previousBlock) { -// ArrayList list = new ArrayList(); -// -// list.add(new Whitelist(96248, -// Convert.parseHexString("942b93195bcb48045019f38859606c1b4aefe98751dd97833631c7fab2c9edfd"), -// Convert.parseHexString("5f5c132acef36a1d329b69c45d1d26b3c3941e7679a6254ce825685100e04dd4"))); -// -// for (Whitelist whitelist : list) { -// if (whitelist.match(previousBlock, generationSignature)) { -// return true; -// } -// } -// -// return false; -// } void apply() { /* XXX - Add the POS reward to the block forger */ diff --git a/src/java/nxt/BlockchainProcessorImpl.java b/src/java/nxt/BlockchainProcessorImpl.java index 33c8adc62..2c3c25a26 100644 --- a/src/java/nxt/BlockchainProcessorImpl.java +++ b/src/java/nxt/BlockchainProcessorImpl.java @@ -10,6 +10,7 @@ import nxt.util.Listeners; import nxt.util.Logger; import nxt.util.ThreadPool; + import org.json.simple.JSONArray; import org.json.simple.JSONObject; import org.json.simple.JSONStreamAware; @@ -34,6 +35,10 @@ import java.util.TreeSet; final class BlockchainProcessorImpl implements BlockchainProcessor { + + /* Rollback 57M theft */ + static final Long ASSET_FREEZE_57M_THEFT_BLOCK = Convert.parseUnsignedLong("13325683304515417100"); + static final int ASSET_FREEZE_57M_THEFT_HEIGHT = 282570; /* XXX - Remove CHECKSUM_TRANSPARENT_FORGING check */ private static final BlockchainProcessorImpl instance = new BlockchainProcessorImpl(); @@ -471,7 +476,7 @@ private byte[] calculateTransactionsChecksum() { } return digest.digest(); } - + private void pushBlock(final BlockImpl block) throws BlockNotAcceptedException { int curTime = Convert.getEpochTime(); @@ -502,6 +507,12 @@ private void pushBlock(final BlockImpl block) throws BlockNotAcceptedException { if (block.getId().equals(Long.valueOf(0L)) || BlockDb.hasBlock(block.getId())) { throw new BlockNotAcceptedException("Duplicate block or invalid id"); } + + /* Rollback 57M theft */ + if ( block.getId().equals(ASSET_FREEZE_57M_THEFT_BLOCK) && previousLastBlock.getHeight() == (ASSET_FREEZE_57M_THEFT_HEIGHT - 1)) { + throw new BlockNotAcceptedException("Asset freeze after 57M theft"); + } + if (! block.verifyGenerationSignature()) { throw new BlockNotAcceptedException("Generation signature verification failed"); } @@ -836,6 +847,10 @@ private void scan() { if (currentBlock.getVersion() != getBlockVersion(blockchain.getHeight())) { throw new NxtException.NotValidException("Invalid block version"); } + if ( currentBlock.getId().equals(ASSET_FREEZE_57M_THEFT_BLOCK) && currentBlock.getHeight() == ASSET_FREEZE_57M_THEFT_HEIGHT) { + throw new NxtException.NotValidException("Asset freeze after 57M theft"); + } + byte[] blockBytes = currentBlock.getBytes(); JSONObject blockJSON = (JSONObject) JSONValue.parse(currentBlock.getJSONObject().toJSONString()); if (! Arrays.equals(blockBytes, parseBlock(blockJSON).getBytes())) { diff --git a/src/java/nxt/Constants.java b/src/java/nxt/Constants.java index 43ca9881f..3fce41fc0 100644 --- a/src/java/nxt/Constants.java +++ b/src/java/nxt/Constants.java @@ -119,6 +119,8 @@ public final class Constants { calendar.set(Calendar.MILLISECOND, 0); EPOCH_BEGINNING = calendar.getTimeInMillis(); } + + public static final int[] MIN_VERSION = new int[] {0, 3, 3}; public static final String ALPHABET = "0123456789abcdefghijklmnopqrstuvwxyz"; public static final String NAMESPACED_ALPHABET = "0123456789abcdefghijklmnopqrstuvwxyz!#$%&()*+-./:;<=>?@[]_{|}"; diff --git a/src/java/nxt/DbVersion.java b/src/java/nxt/DbVersion.java index d6a28d840..bca2aecd6 100644 --- a/src/java/nxt/DbVersion.java +++ b/src/java/nxt/DbVersion.java @@ -142,16 +142,7 @@ private static void update(int nextUpdate) { case 36: apply("CREATE TABLE IF NOT EXISTS peer (address VARCHAR PRIMARY KEY)"); case 37: - /* XXX - Update known peers */ - if (!Constants.isTestnet) { - apply("INSERT INTO peer (address) VALUES " + - "('5.101.102.194'), ('5.101.102.195'), ('5.101.102.196'), ('5.101.102.197'), " + - "('5.101.102.199'), ('5.101.102.200'), ('5.101.102.201'), ('5.101.102.202'), " + - "('5.101.102.203'), ('5.101.102.204'), ('5.101.102.205'), ('107.170.73.9'), " + - "('107.170.123.54'), ('107.170.138.55'), ('107.170.149.231')"); - } else { - apply("INSERT INTO peer (address) VALUES " + "('178.62.176.45'), ('178.62.176.46')"); - } + apply(null); case 38: apply("ALTER TABLE transaction ADD COLUMN IF NOT EXISTS full_hash BINARY(32)"); case 39: @@ -246,6 +237,27 @@ private static void update(int nextUpdate) { case 68: apply("ALTER TABLE transaction ADD COLUMN IF NOT EXISTS has_encrypttoself_message BOOLEAN NOT NULL DEFAULT FALSE"); case 69: + /* Remove all peers since we start blacklisting peers from before 0.3.3 */ + apply("DELETE FROM peer"); + case 70: + if (!Constants.isTestnet) { + apply("INSERT INTO peer (address) VALUES " + + "('5.101.102.194'), ('5.101.102.195'), ('5.101.102.196'), ('5.101.102.197'), " + + "('5.101.102.199'), ('5.101.102.200'), ('5.101.102.201'), ('5.101.102.202'), " + + "('5.101.102.203'), ('5.101.102.204'), ('5.101.102.205'), ('107.170.73.9'), " + + "('107.170.123.54'), ('107.170.138.55')"); + } else { + apply("INSERT INTO peer (address) VALUES " + "('178.62.176.45'), ('178.62.176.46')"); + } + case 71: + apply(null); + case 72: + /* Validate the chain to be compatible with the 0.3.3 fork */ + if ( ! Constants.isTestnet) { + BlockchainProcessorImpl.getInstance().validateAtNextScan(); + } + apply(null); + case 73: return; default: throw new RuntimeException("Database inconsistent with code, probably trying to run older code on newer database"); diff --git a/src/java/nxt/Locked.java b/src/java/nxt/Locked.java index 6449c0b2e..82d8ae3c3 100644 --- a/src/java/nxt/Locked.java +++ b/src/java/nxt/Locked.java @@ -69,14 +69,23 @@ public class Locked { Convert.parseHexString("271ca64483b4f79b358baf242f309968415d42c28e47b61d420aa41de694e840"), Convert.parseHexString("2eed045000b4d824f4cac979f23b3a7ffc5a90b0d1e930db9d76b80f5a729e1f") }; + + private static boolean hit(byte[] senderPublickey) { + for (int i=0; i THEFT_BLOCK_57MIL) { - for (int i=0; i THEFT_BLOCK_57MIL && hit(senderPublickey)) { + throw new NxtException.NotValidException("Public key locked for outgoing transactions"); } - } + } + + public static boolean allowedToForge(byte[] senderPublickey) { + return hit(senderPublickey) == false; + } } diff --git a/src/java/nxt/peer/Peer.java b/src/java/nxt/peer/Peer.java index b1a6d40ba..474357f64 100644 --- a/src/java/nxt/peer/Peer.java +++ b/src/java/nxt/peer/Peer.java @@ -34,6 +34,8 @@ public static enum State { boolean isWellKnown(); boolean isBlacklisted(); + + boolean isWhitelisted(); void blacklist(Exception cause); diff --git a/src/java/nxt/peer/PeerImpl.java b/src/java/nxt/peer/PeerImpl.java index 245847af3..dcb92f084 100644 --- a/src/java/nxt/peer/PeerImpl.java +++ b/src/java/nxt/peer/PeerImpl.java @@ -3,11 +3,13 @@ import nxt.Account; import nxt.BlockchainProcessor; import nxt.Constants; +import nxt.Nxt; import nxt.NxtException; import nxt.util.Convert; import nxt.util.CountingInputStream; import nxt.util.CountingOutputStream; import nxt.util.Logger; + import org.json.simple.JSONObject; import org.json.simple.JSONStreamAware; import org.json.simple.JSONValue; @@ -52,6 +54,7 @@ final class PeerImpl implements Peer { private volatile long downloadedVolume; private volatile long uploadedVolume; private volatile int lastUpdated; + private volatile boolean isOldVersion; PeerImpl(String peerAddress, String announcedAddress) { this.peerAddress = peerAddress; @@ -116,7 +119,49 @@ public String getVersion() { } void setVersion(String version) { + if (version == null || version.equals("?")) { + Logger.logDebugMessage("Could not determine version of " + peerAddress); + this.version = version; + return; + } + + if (this.version != null && this.version.equals(version)) { + return; + } this.version = version; + + isOldVersion = false; + if (application != null && ! Nxt.APPLICATION.equals(application)) { + isOldVersion = true; + } + else if (Nxt.APPLICATION.equals(application) && version != null) { + String[] versions = version.split("\\."); + if (versions.length < Constants.MIN_VERSION.length) { + isOldVersion = true; + } + else { + for (int i = 0; i < Constants.MIN_VERSION.length; i++) { + try { + int v = Integer.parseInt(versions[i]); + if (v > Constants.MIN_VERSION[i]) { + isOldVersion = false; + break; + } + else if (v < Constants.MIN_VERSION[i]) { + isOldVersion = true; + break; + } + } + catch (NumberFormatException e) { + isOldVersion = true; + break; + } + } + } + } + if (isOldVersion) { + Logger.logDebugMessage(String.format("Blacklisting %s version %s", peerAddress, version)); + } } @Override @@ -205,7 +250,15 @@ public int getWeight() { @Override public boolean isBlacklisted() { - return blacklistingTime > 0 || Peers.knownBlacklistedPeers.contains(peerAddress) || Peers.blacklistedVersion(version); + if (isWhitelisted()) { + return false; + } + return blacklistingTime > 0 || isOldVersion || Peers.knownBlacklistedPeers.contains(peerAddress); + } + + @Override + public boolean isWhitelisted() { + return Peers.knownWhitelistedPeers.contains(peerAddress); } @Override @@ -378,7 +431,7 @@ void connect() { JSONObject response = send(Peers.myPeerInfoRequest); if (response != null) { application = (String)response.get("application"); - version = (String)response.get("version"); + setVersion((String) response.get("version")); platform = (String)response.get("platform"); shareAddress = Boolean.TRUE.equals(response.get("shareAddress")); String newAnnouncedAddress = Convert.emptyToNull((String)response.get("announcedAddress")); diff --git a/src/java/nxt/peer/PeerServlet.java b/src/java/nxt/peer/PeerServlet.java index 5139887ff..1b3a6ef7e 100644 --- a/src/java/nxt/peer/PeerServlet.java +++ b/src/java/nxt/peer/PeerServlet.java @@ -92,17 +92,17 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws S if (request == null) { return; } - + if (peer.getState() == Peer.State.DISCONNECTED) { - peer.setState(Peer.State.CONNECTED); - Peers.updateAddress(peer); + peer.setState(Peer.State.CONNECTED); + Peers.updateAddress(peer); } peer.updateDownloadedVolume(cis.getCount()); if (! peer.analyzeHallmark(peer.getPeerAddress(), (String)request.get("hallmark"))) { peer.blacklist(); return; } - + if (request.get("protocol") != null && ((Number)request.get("protocol")).intValue() == 1) { PeerRequestHandler peerRequestHandler = peerRequestHandlers.get(request.get("requestType")); if (peerRequestHandler != null) { diff --git a/src/java/nxt/peer/Peers.java b/src/java/nxt/peer/Peers.java index 475442a70..35613b7aa 100644 --- a/src/java/nxt/peer/Peers.java +++ b/src/java/nxt/peer/Peers.java @@ -52,8 +52,6 @@ public static enum Event { ADDED_ACTIVE_PEER, CHANGED_ACTIVE_PEER, NEW_PEER } - - static final String VERSION_0_3_3 = "0.3.3"; static final int LOGGING_MASK_EXCEPTIONS = 1; static final int LOGGING_MASK_NON200_RESPONSES = 2; @@ -62,6 +60,7 @@ public static enum Event { static final Set wellKnownPeers; static final Set knownBlacklistedPeers; + static final Set knownWhitelistedPeers; static final int connectTimeout; static final int readTimeout; @@ -171,6 +170,13 @@ public static enum Event { } else { knownBlacklistedPeers = Collections.unmodifiableSet(new HashSet<>(knownBlacklistedPeersList)); } + + List knownWhitelistedPeersList = Nxt.getStringListProperty("nxt.knownWhitelistedPeers"); + if (knownWhitelistedPeersList.isEmpty()) { + knownWhitelistedPeers = Collections.emptySet(); + } else { + knownWhitelistedPeers = Collections.unmodifiableSet(new HashSet<>(knownWhitelistedPeersList)); + } maxNumberOfConnectedPublicPeers = Nxt.getIntProperty("nxt.maxNumberOfConnectedPublicPeers"); connectTimeout = Nxt.getIntProperty("nxt.connectTimeout"); @@ -717,11 +723,4 @@ private static int getNumberOfConnectedPublicPeers() { private Peers() {} // never - public static boolean blacklistedVersion(String version) { - if (version != null && VERSION_0_3_3.equals(version)) { - return false; - } - return true; - } - }