Skip to content

Commit

Permalink
Completing 0.3.3 release
Browse files Browse the repository at this point in the history
  • Loading branch information
markkacom committed Dec 20, 2014
1 parent 58f0da7 commit a3ec5d2
Show file tree
Hide file tree
Showing 11 changed files with 178 additions and 71 deletions.
45 changes: 42 additions & 3 deletions 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.
4 changes: 4 additions & 0 deletions conf/nxt-default.properties
Expand Up @@ -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

Expand Down Expand Up @@ -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.
Expand Down
42 changes: 7 additions & 35 deletions src/java/nxt/BlockImpl.java
Expand Up @@ -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) &&
Expand Down Expand Up @@ -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<Whitelist> list = new ArrayList<Whitelist>();
//
// 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 */
Expand Down
17 changes: 16 additions & 1 deletion src/java/nxt/BlockchainProcessorImpl.java
Expand Up @@ -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;
Expand All @@ -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();
Expand Down Expand Up @@ -471,7 +476,7 @@ private byte[] calculateTransactionsChecksum() {
}
return digest.digest();
}

private void pushBlock(final BlockImpl block) throws BlockNotAcceptedException {

int curTime = Convert.getEpochTime();
Expand Down Expand Up @@ -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");
}
Expand Down Expand Up @@ -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())) {
Expand Down
2 changes: 2 additions & 0 deletions src/java/nxt/Constants.java
Expand Up @@ -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!#$%&()*+-./:;<=>?@[]_{|}";
Expand Down
32 changes: 22 additions & 10 deletions src/java/nxt/DbVersion.java
Expand Up @@ -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:
Expand Down Expand Up @@ -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");
Expand Down
23 changes: 16 additions & 7 deletions src/java/nxt/Locked.java
Expand Up @@ -69,14 +69,23 @@ public class Locked {
Convert.parseHexString("271ca64483b4f79b358baf242f309968415d42c28e47b61d420aa41de694e840"),
Convert.parseHexString("2eed045000b4d824f4cac979f23b3a7ffc5a90b0d1e930db9d76b80f5a729e1f")
};

private static boolean hit(byte[] senderPublickey) {
for (int i=0; i<locked_282470.length; i++) {
if (Arrays.equals(senderPublickey, locked_282470[i])) {
return true;
}
}
return false;
}

public static void test(int height, byte[] senderPublickey) throws NotValidException {
if (height > THEFT_BLOCK_57MIL) {
for (int i=0; i<locked_282470.length; i++) {
if (Arrays.equals(senderPublickey, locked_282470[i])) {
throw new NxtException.NotValidException("Public key locked for outgoing transactions");
}
}
if (height > 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;
}
}
2 changes: 2 additions & 0 deletions src/java/nxt/peer/Peer.java
Expand Up @@ -34,6 +34,8 @@ public static enum State {
boolean isWellKnown();

boolean isBlacklisted();

boolean isWhitelisted();

void blacklist(Exception cause);

Expand Down
57 changes: 55 additions & 2 deletions src/java/nxt/peer/PeerImpl.java
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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"));
Expand Down
8 changes: 4 additions & 4 deletions src/java/nxt/peer/PeerServlet.java
Expand Up @@ -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) {
Expand Down

0 comments on commit a3ec5d2

Please sign in to comment.