diff --git a/compile.bat b/compile.bat new file mode 100644 index 000000000..4b9edb973 --- /dev/null +++ b/compile.bat @@ -0,0 +1,18 @@ +set CP=conf\;classes\;lib\* +set SP=src\java\ +set JRE=jdk1.8.0_40 + +md classes + +"C:\Program Files\Java\%JRE%\bin\javac.exe" -sourcepath %SP% -classpath %CP% -d classes\ src\java\nxt\*.java src\java\nxt\crypto\*.java src\java\nxt\util\*.java src\java\nxt\http\*.java src\java\fr\cryptohash\*.java src\java\nxt\at\*.java src\java\nxt\peer\*.java src\java\nxt\user\*.java src\java\nxt\db\*.java src\java\fr\cryptohash\test\*.java + + +:/bin/rm -f burst.jar +:this seems to be overwritten by the compiler so not really any need to remove it on windows + +"C:\Program Files\Java\%JRE%\bin\jar.exe" cf burst.jar -C classes . +:/bin/rm -rf classes +:seems to update fine even if not removed, so no immediate need to remove the directory + +echo "burst.jar generated successfully" +pause diff --git a/conf/nxt-default.properties b/conf/nxt-default.properties index dc03ccd7c..70a8ce31f 100644 --- a/conf/nxt-default.properties +++ b/conf/nxt-default.properties @@ -1,5 +1,6 @@ #### PEER NETWORKING #### + # Announce my IP address/hostname to peers and allow them to share it with other peers. # If disabled, peer networking servlet will not be started at all. nxt.shareMyAddress=true @@ -199,6 +200,12 @@ nxt.debugTraceQuote=" # Log changes to unconfirmed balances. nxt.debugLogUnconfirmed=false +# If set to larger than 0 then all deadlines received from miners, that are smaller than or equal to this number will be logged +# so to see all deadlines smaller than 200000 in the wallet window and in the log, change the 0 to 200000 +# a log line will look like this : +# 2014-11-13 20:27:29 INFO: Block:33730 Nonce: 157212105 Deadline: 22100 +nxt.MaxDeadlineToLog=0 + #### DATABASE #### @@ -247,7 +254,7 @@ nxt.forceValidate=false nxt.forceScan=false # Print a list of peers having this version on exit. -nxt.dumpPeersVersion= +nxt.dumpPeersVersion=false # Enable trimming of derived objects tables. nxt.trimDerivedTables=true diff --git a/run_repeat.bat b/run_repeat.bat new file mode 100644 index 000000000..fe8cf62cf --- /dev/null +++ b/run_repeat.bat @@ -0,0 +1,22 @@ +title BURST WALLET +set MAXRAM=700m +set LAUNCH=-Xmx%MAXRAM% -cp burst.jar;lib\*;conf nxt.Nxt +set JAVAVERSION=jre1.8.0_40 +set JAVAEXEC=\Java\%JAVAVERSION%\bin\java.exe +:runagain +@ECHO OFF +IF EXIST java ( + java %LAUNCH% +) ELSE ( + IF EXIST "%PROGRAMFILES%\Java\%JAVAVERSION%" ( + "%PROGRAMFILES%%JAVAEXEC%" %LAUNCH% + ) ELSE ( + IF EXIST "%PROGRAMFILES(X86)%\%JAVAVERSION%" ( + "%PROGRAMFILES(X86)%%JAVAEXEC%" %LAUNCH% + ) ELSE ( + ECHO Java software not found on your system. Please go to http://java.com/en/ to download a copy of Java. + PAUSE + ) + ) +) +goto runagain \ No newline at end of file diff --git a/src/java/nxt/Nxt.java b/src/java/nxt/Nxt.java index 6b1fc3a95..7e3dfbb1f 100644 --- a/src/java/nxt/Nxt.java +++ b/src/java/nxt/Nxt.java @@ -1,216 +1,216 @@ -package nxt; - -import nxt.db.Db; -import nxt.http.API; -import nxt.peer.Peers; -import nxt.user.Users; -import nxt.util.Logger; -import nxt.util.ThreadPool; -import nxt.util.Time; - -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Properties; - -public final class Nxt { - - public static final String VERSION = "1.2.3"; - public static final String APPLICATION = "NRS"; - - private static volatile Time time = new Time.EpochTime(); - - private static final Properties defaultProperties = new Properties(); - static { - System.out.println("Initializing Burst server version " + Nxt.VERSION); - try (InputStream is = ClassLoader.getSystemResourceAsStream("nxt-default.properties")) { - if (is != null) { - Nxt.defaultProperties.load(is); - } else { - String configFile = System.getProperty("nxt-default.properties"); - if (configFile != null) { - try (InputStream fis = new FileInputStream(configFile)) { - Nxt.defaultProperties.load(fis); - } catch (IOException e) { - throw new RuntimeException("Error loading nxt-default.properties from " + configFile); - } - } else { - throw new RuntimeException("nxt-default.properties not in classpath and system property nxt-default.properties not defined either"); - } - } - } catch (IOException e) { - throw new RuntimeException("Error loading nxt-default.properties", e); - } - } - private static final Properties properties = new Properties(defaultProperties); - static { - try (InputStream is = ClassLoader.getSystemResourceAsStream("nxt.properties")) { - if (is != null) { - Nxt.properties.load(is); - } // ignore if missing - } catch (IOException e) { - throw new RuntimeException("Error loading nxt.properties", e); - } - } - - public static int getIntProperty(String name) { - try { - int result = Integer.parseInt(properties.getProperty(name)); - Logger.logMessage(name + " = \"" + result + "\""); - return result; - } catch (NumberFormatException e) { - Logger.logMessage(name + " not defined, assuming 0"); - return 0; - } - } - - public static String getStringProperty(String name) { - return getStringProperty(name, null); - } - - public static String getStringProperty(String name, String defaultValue) { - String value = properties.getProperty(name); - if (value != null && ! "".equals(value)) { - Logger.logMessage(name + " = \"" + value + "\""); - return value; - } else { - Logger.logMessage(name + " not defined"); - return defaultValue; - } - } - - public static List getStringListProperty(String name) { - String value = getStringProperty(name); - if (value == null || value.length() == 0) { - return Collections.emptyList(); - } - List result = new ArrayList<>(); - for (String s : value.split(";")) { - s = s.trim(); - if (s.length() > 0) { - result.add(s); - } - } - return result; - } - - public static Boolean getBooleanProperty(String name) { - String value = properties.getProperty(name); - if (Boolean.TRUE.toString().equals(value)) { - Logger.logMessage(name + " = \"true\""); - return true; - } else if (Boolean.FALSE.toString().equals(value)) { - Logger.logMessage(name + " = \"false\""); - return false; - } - Logger.logMessage(name + " not defined, assuming false"); - return false; - } - - public static Blockchain getBlockchain() { - return BlockchainImpl.getInstance(); - } - - public static BlockchainProcessor getBlockchainProcessor() { - return BlockchainProcessorImpl.getInstance(); - } - - public static TransactionProcessor getTransactionProcessor() { - return TransactionProcessorImpl.getInstance(); - } - - public static int getEpochTime() { - return time.getTime(); - } - - static void setTime(Time time) { - Nxt.time = time; - } - - public static void main(String[] args) { - Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { - @Override - public void run() { - Nxt.shutdown(); - } - })); - init(); - } - - public static void init(Properties customProperties) { - properties.putAll(customProperties); - init(); - } - - public static void init() { - Init.init(); - } - - public static void shutdown() { - Logger.logShutdownMessage("Shutting down..."); - API.shutdown(); - Users.shutdown(); - Peers.shutdown(); - ThreadPool.shutdown(); - Db.shutdown(); - Logger.logShutdownMessage("Burst server " + VERSION + " stopped."); - Logger.shutdown(); - } - - private static class Init { - - static { - try { - long startTime = System.currentTimeMillis(); - Logger.init(); - Db.init(); - TransactionProcessorImpl.getInstance(); - BlockchainProcessorImpl.getInstance(); - DbVersion.init(); - Account.init(); - Alias.init(); - Asset.init(); - DigitalGoodsStore.init(); - Hub.init(); - Order.init(); - Poll.init(); - Trade.init(); - AssetTransfer.init(); - Vote.init(); - AT.init(); - Peers.init(); - Generator.init(); - API.init(); - Users.init(); - DebugTrace.init(); - int timeMultiplier = (Constants.isTestnet && Constants.isOffline) ? Math.max(Nxt.getIntProperty("nxt.timeMultiplier"), 1) : 1; - ThreadPool.start(timeMultiplier); - if (timeMultiplier > 1) { - setTime(new Time.FasterTime(Math.max(getEpochTime(), Nxt.getBlockchain().getLastBlock().getTimestamp()), timeMultiplier)); - Logger.logMessage("TIME WILL FLOW " + timeMultiplier + " TIMES FASTER!"); - } - - long currentTime = System.currentTimeMillis(); - Logger.logMessage("Initialization took " + (currentTime - startTime) / 1000 + " seconds"); - Logger.logMessage("Burst server " + VERSION + " started successfully."); - if (Constants.isTestnet) { - Logger.logMessage("RUNNING ON TESTNET - DO NOT USE REAL ACCOUNTS!"); - } - } catch (Exception e) { - Logger.logErrorMessage(e.getMessage(), e); - System.exit(1); - } - } - - private static void init() {} - - private Init() {} // never - - } - - private Nxt() {} // never - -} +package nxt; + +import nxt.db.Db; +import nxt.http.API; +import nxt.peer.Peers; +import nxt.user.Users; +import nxt.util.Logger; +import nxt.util.ThreadPool; +import nxt.util.Time; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Properties; + +public final class Nxt { + + public static final String VERSION = "1.2.3"; + public static final String APPLICATION = "NRS"; + + private static volatile Time time = new Time.EpochTime(); + + private static final Properties defaultProperties = new Properties(); + static { + System.out.println("Initializing Burst server version " + Nxt.VERSION); + try (InputStream is = ClassLoader.getSystemResourceAsStream("nxt-default.properties")) { + if (is != null) { + Nxt.defaultProperties.load(is); + } else { + String configFile = System.getProperty("nxt-default.properties"); + if (configFile != null) { + try (InputStream fis = new FileInputStream(configFile)) { + Nxt.defaultProperties.load(fis); + } catch (IOException e) { + throw new RuntimeException("Error loading nxt-default.properties from " + configFile); + } + } else { + throw new RuntimeException("nxt-default.properties not in classpath and system property nxt-default.properties not defined either"); + } + } + } catch (IOException e) { + throw new RuntimeException("Error loading nxt-default.properties", e); + } + } + private static final Properties properties = new Properties(defaultProperties); + static { + try (InputStream is = ClassLoader.getSystemResourceAsStream("nxt.properties")) { + if (is != null) { + Nxt.properties.load(is); + } // ignore if missing + } catch (IOException e) { + throw new RuntimeException("Error loading nxt.properties", e); + } + } + + public static int getIntProperty(String name) { + try { + int result = Integer.parseInt(properties.getProperty(name)); + Logger.logMessage(name + " = \"" + result + "\""); + return result; + } catch (NumberFormatException e) { + Logger.logMessage(name + " not defined, assuming 0"); + return 0; + } + } + + public static String getStringProperty(String name) { + return getStringProperty(name, null); + } + + public static String getStringProperty(String name, String defaultValue) { + String value = properties.getProperty(name); + if (value != null && ! "".equals(value)) { + Logger.logMessage(name + " = \"" + value + "\""); + return value; + } else { + Logger.logMessage(name + " not defined"); + return defaultValue; + } + } + + public static List getStringListProperty(String name) { + String value = getStringProperty(name); + if (value == null || value.length() == 0) { + return Collections.emptyList(); + } + List result = new ArrayList<>(); + for (String s : value.split(";")) { + s = s.trim(); + if (s.length() > 0) { + result.add(s); + } + } + return result; + } + + public static Boolean getBooleanProperty(String name) { + String value = properties.getProperty(name); + if (Boolean.TRUE.toString().equals(value)) { + Logger.logMessage(name + " = \"true\""); + return true; + } else if (Boolean.FALSE.toString().equals(value)) { + Logger.logMessage(name + " = \"false\""); + return false; + } + Logger.logMessage(name + " not defined, assuming false"); + return false; + } + + public static Blockchain getBlockchain() { + return BlockchainImpl.getInstance(); + } + + public static BlockchainProcessor getBlockchainProcessor() { + return BlockchainProcessorImpl.getInstance(); + } + + public static TransactionProcessor getTransactionProcessor() { + return TransactionProcessorImpl.getInstance(); + } + + public static int getEpochTime() { + return time.getTime(); + } + + static void setTime(Time time) { + Nxt.time = time; + } + + public static void main(String[] args) { + Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { + @Override + public void run() { + Nxt.shutdown(); + } + })); + init(); + } + + public static void init(Properties customProperties) { + properties.putAll(customProperties); + init(); + } + + public static void init() { + Init.init(); + } + + public static void shutdown() { + Logger.logShutdownMessage("Shutting down..."); + API.shutdown(); + Users.shutdown(); + Peers.shutdown(); + ThreadPool.shutdown(); + Db.shutdown(); + Logger.logShutdownMessage("Burst server " + VERSION + " stopped."); + Logger.shutdown(); + } + + private static class Init { + + static { + try { + long startTime = System.currentTimeMillis(); + Logger.init(); + Db.init(); + TransactionProcessorImpl.getInstance(); + BlockchainProcessorImpl.getInstance(); + DbVersion.init(); + Account.init(); + Alias.init(); + Asset.init(); + DigitalGoodsStore.init(); + Hub.init(); + Order.init(); + Poll.init(); + Trade.init(); + AssetTransfer.init(); + Vote.init(); + AT.init(); + Peers.init(); + Generator.init(); + API.init(); + Users.init(); + DebugTrace.init(); + int timeMultiplier = (Constants.isTestnet && Constants.isOffline) ? Math.max(Nxt.getIntProperty("nxt.timeMultiplier"), 1) : 1; + ThreadPool.start(timeMultiplier); + if (timeMultiplier > 1) { + setTime(new Time.FasterTime(Math.max(getEpochTime(), Nxt.getBlockchain().getLastBlock().getTimestamp()), timeMultiplier)); + Logger.logMessage("TIME WILL FLOW " + timeMultiplier + " TIMES FASTER!"); + } + + long currentTime = System.currentTimeMillis(); + Logger.logMessage("Initialization took " + (currentTime - startTime) / 1000 + " seconds"); + Logger.logMessage("Burst server " + VERSION + " started successfully."); + if (Constants.isTestnet) { + Logger.logMessage("RUNNING ON TESTNET - DO NOT USE REAL ACCOUNTS!"); + } + } catch (Exception e) { + Logger.logErrorMessage(e.getMessage(), e); + System.exit(1); + } + } + + private static void init() {} + + private Init() {} // never + + } + + private Nxt() {} // never + +} diff --git a/src/java/nxt/TransactionProcessorImpl.java b/src/java/nxt/TransactionProcessorImpl.java index db464ad19..458af9a44 100644 --- a/src/java/nxt/TransactionProcessorImpl.java +++ b/src/java/nxt/TransactionProcessorImpl.java @@ -1,537 +1,537 @@ -package nxt; - -import nxt.db.Db; -import nxt.db.DbClause; -import nxt.db.DbIterator; -import nxt.db.DbKey; -import nxt.db.EntityDbTable; -import nxt.peer.Peer; -import nxt.peer.Peers; -import nxt.util.JSON; -import nxt.util.Listener; -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; - -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - -final class TransactionProcessorImpl implements TransactionProcessor { - - private static final boolean enableTransactionRebroadcasting = Nxt.getBooleanProperty("nxt.enableTransactionRebroadcasting"); - private static final boolean testUnconfirmedTransactions = Nxt.getBooleanProperty("nxt.testUnconfirmedTransactions"); - - private static final TransactionProcessorImpl instance = new TransactionProcessorImpl(); - - static TransactionProcessorImpl getInstance() { - return instance; - } - - final DbKey.LongKeyFactory unconfirmedTransactionDbKeyFactory = new DbKey.LongKeyFactory("id") { - - @Override - public DbKey newKey(TransactionImpl transaction) { - return transaction.getDbKey(); - } - - }; - - private final EntityDbTable unconfirmedTransactionTable = new EntityDbTable("unconfirmed_transaction", unconfirmedTransactionDbKeyFactory) { - - @Override - protected TransactionImpl load(Connection con, ResultSet rs) throws SQLException { - byte[] transactionBytes = rs.getBytes("transaction_bytes"); - try { - TransactionImpl transaction = TransactionImpl.parseTransaction(transactionBytes); - transaction.setHeight(rs.getInt("transaction_height")); - return transaction; - } catch (NxtException.ValidationException e) { - throw new RuntimeException(e.toString(), e); - } - } - - @Override - protected void save(Connection con, TransactionImpl transaction) throws SQLException { - try (PreparedStatement pstmt = con.prepareStatement("INSERT INTO unconfirmed_transaction (id, transaction_height, " - + "fee_per_byte, timestamp, expiration, transaction_bytes, height) " - + "VALUES (?, ?, ?, ?, ?, ?, ?)")) { - int i = 0; - pstmt.setLong(++i, transaction.getId()); - pstmt.setInt(++i, transaction.getHeight()); - pstmt.setLong(++i, transaction.getFeeNQT() / transaction.getSize()); - pstmt.setInt(++i, transaction.getTimestamp()); - pstmt.setInt(++i, transaction.getExpiration()); - pstmt.setBytes(++i, transaction.getBytes()); - pstmt.setInt(++i, Nxt.getBlockchain().getHeight()); - pstmt.executeUpdate(); - } - } - - @Override - public void rollback(int height) { - List transactions = new ArrayList<>(); - try (Connection con = Db.getConnection(); - PreparedStatement pstmt = con.prepareStatement("SELECT * FROM unconfirmed_transaction WHERE height > ?")) { - pstmt.setInt(1, height); - try (ResultSet rs = pstmt.executeQuery()) { - while (rs.next()) { - transactions.add(load(con, rs)); - } - } - } catch (SQLException e) { - throw new RuntimeException(e.toString(), e); - } - super.rollback(height); - processLater(transactions); - } - - @Override - protected String defaultSort() { - return " ORDER BY transaction_height ASC, fee_per_byte DESC, timestamp ASC, id ASC "; - } - - }; - - private final Set nonBroadcastedTransactions = Collections.newSetFromMap(new ConcurrentHashMap()); - private final Listeners,Event> transactionListeners = new Listeners<>(); - private final Set lostTransactions = new HashSet<>(); - - private final Runnable removeUnconfirmedTransactionsThread = new Runnable() { - - private final DbClause expiredClause = new DbClause(" expiration < ? ") { - @Override - protected int set(PreparedStatement pstmt, int index) throws SQLException { - pstmt.setInt(index, Nxt.getEpochTime()); - return index + 1; - } - }; - - @Override - public void run() { - - try { - try { - List expiredTransactions = new ArrayList<>(); - try (DbIterator iterator = unconfirmedTransactionTable.getManyBy(expiredClause, 0, -1, "")) { - while (iterator.hasNext()) { - expiredTransactions.add(iterator.next()); - } - } - if (expiredTransactions.size() > 0) { - synchronized (BlockchainImpl.getInstance()) { - try { - Db.beginTransaction(); - for (TransactionImpl transaction : expiredTransactions) { - removeUnconfirmedTransaction(transaction); - } - Db.commitTransaction(); - } catch (Exception e) { - Logger.logErrorMessage(e.toString(), e); - Db.rollbackTransaction(); - throw e; - } finally { - Db.endTransaction(); - } - } // synchronized - } - } catch (Exception e) { - Logger.logDebugMessage("Error removing unconfirmed transactions", e); - } - } catch (Throwable t) { - Logger.logMessage("CRITICAL ERROR. PLEASE REPORT TO THE DEVELOPERS.\n" + t.toString()); - t.printStackTrace(); - System.exit(1); - } - - } - - }; - - private final Runnable rebroadcastTransactionsThread = new Runnable() { - - @Override - public void run() { - - try { - try { - List transactionList = new ArrayList<>(); - int curTime = Nxt.getEpochTime(); - for (TransactionImpl transaction : nonBroadcastedTransactions) { - if (TransactionDb.hasTransaction(transaction.getId()) || transaction.getExpiration() < curTime) { - nonBroadcastedTransactions.remove(transaction); - } else if (transaction.getTimestamp() < curTime - 30) { - transactionList.add(transaction); - } - } - - if (transactionList.size() > 0) { - Peers.sendToSomePeers(transactionList); - } - - } catch (Exception e) { - Logger.logDebugMessage("Error in transaction re-broadcasting thread", e); - } - } catch (Throwable t) { - Logger.logMessage("CRITICAL ERROR. PLEASE REPORT TO THE DEVELOPERS.\n" + t.toString()); - t.printStackTrace(); - System.exit(1); - } - - } - - }; - - private final Runnable processTransactionsThread = new Runnable() { - - private final JSONStreamAware getUnconfirmedTransactionsRequest; - { - JSONObject request = new JSONObject(); - request.put("requestType", "getUnconfirmedTransactions"); - getUnconfirmedTransactionsRequest = JSON.prepareRequest(request); - } - - @Override - public void run() { - try { - try { - synchronized (BlockchainImpl.getInstance()) { - processTransactions(lostTransactions, false); - lostTransactions.clear(); - } - Peer peer = Peers.getAnyPeer(Peer.State.CONNECTED, true); - if (peer == null) { - return; - } - JSONObject response = peer.send(getUnconfirmedTransactionsRequest); - if (response == null) { - return; - } - JSONArray transactionsData = (JSONArray)response.get("unconfirmedTransactions"); - if (transactionsData == null || transactionsData.size() == 0) { - return; - } - try { - processPeerTransactions(transactionsData); - } catch (NxtException.ValidationException|RuntimeException e) { - peer.blacklist(e); - } - } catch (Exception e) { - Logger.logDebugMessage("Error processing unconfirmed transactions", e); - } - } catch (Throwable t) { - Logger.logMessage("CRITICAL ERROR. PLEASE REPORT TO THE DEVELOPERS.\n" + t.toString()); - t.printStackTrace(); - System.exit(1); - } - } - - }; - - private TransactionProcessorImpl() { - ThreadPool.scheduleThread("ProcessTransactions", processTransactionsThread, 5); - ThreadPool.scheduleThread("RemoveUnconfirmedTransactions", removeUnconfirmedTransactionsThread, 1); - if (enableTransactionRebroadcasting) { - ThreadPool.scheduleThread("RebroadcastTransactions", rebroadcastTransactionsThread, 60); - ThreadPool.runAfterStart(new Runnable() { - @Override - public void run() { - try (DbIterator oldNonBroadcastedTransactions = getAllUnconfirmedTransactions()) { - for (TransactionImpl transaction : oldNonBroadcastedTransactions) { - nonBroadcastedTransactions.add(transaction); - } - } - } - }); - } - } - - @Override - public boolean addListener(Listener> listener, Event eventType) { - return transactionListeners.addListener(listener, eventType); - } - - @Override - public boolean removeListener(Listener> listener, Event eventType) { - return transactionListeners.removeListener(listener, eventType); - } - - void notifyListeners(List transactions, Event eventType) { - transactionListeners.notify(transactions, eventType); - } - - @Override - public DbIterator getAllUnconfirmedTransactions() { - return unconfirmedTransactionTable.getAll(0, -1); - } - - @Override - public Transaction getUnconfirmedTransaction(long transactionId) { - return unconfirmedTransactionTable.get(unconfirmedTransactionDbKeyFactory.newKey(transactionId)); - } - - public Transaction.Builder newTransactionBuilder(byte[] senderPublicKey, long amountNQT, long feeNQT, short deadline, - Attachment attachment) { - byte version = (byte) getTransactionVersion(Nxt.getBlockchain().getHeight()); - int timestamp = Nxt.getEpochTime(); - TransactionImpl.BuilderImpl builder = new TransactionImpl.BuilderImpl(version, senderPublicKey, amountNQT, feeNQT, timestamp, - deadline, (Attachment.AbstractAttachment)attachment); - if (version > 0) { - Block ecBlock = EconomicClustering.getECBlock(timestamp); - builder.ecBlockHeight(ecBlock.getHeight()); - builder.ecBlockId(ecBlock.getId()); - } - return builder; - } - - @Override - public void broadcast(Transaction transaction) throws NxtException.ValidationException { - if (! transaction.verifySignature()) { - throw new NxtException.NotValidException("Transaction signature verification failed"); - } - List processedTransactions; - synchronized (BlockchainImpl.getInstance()) { - if (TransactionDb.hasTransaction(transaction.getId())) { - Logger.logMessage("Transaction " + transaction.getStringId() + " already in blockchain, will not broadcast again"); - return; - } - if (unconfirmedTransactionTable.get(((TransactionImpl) transaction).getDbKey()) != null) { - if (enableTransactionRebroadcasting) { - nonBroadcastedTransactions.add((TransactionImpl) transaction); - Logger.logMessage("Transaction " + transaction.getStringId() + " already in unconfirmed pool, will re-broadcast"); - } else { - Logger.logMessage("Transaction " + transaction.getStringId() + " already in unconfirmed pool, will not broadcast again"); - } - return; - } - processedTransactions = processTransactions(Collections.singleton((TransactionImpl) transaction), true); - } - if (processedTransactions.contains(transaction)) { - if (enableTransactionRebroadcasting) { - nonBroadcastedTransactions.add((TransactionImpl) transaction); - } - Logger.logDebugMessage("Accepted new transaction " + transaction.getStringId()); - } else { - Logger.logDebugMessage("Could not accept new transaction " + transaction.getStringId()); - throw new NxtException.NotValidException("Invalid transaction " + transaction.getStringId()); - } - } - - @Override - public void processPeerTransactions(JSONObject request) throws NxtException.ValidationException { - JSONArray transactionsData = (JSONArray)request.get("transactions"); - processPeerTransactions(transactionsData); - } - - @Override - public Transaction parseTransaction(byte[] bytes) throws NxtException.ValidationException { - return TransactionImpl.parseTransaction(bytes); - } - - @Override - public TransactionImpl parseTransaction(JSONObject transactionData) throws NxtException.NotValidException { - return TransactionImpl.parseTransaction(transactionData); - } - - @Override - public void clearUnconfirmedTransactions() { - synchronized (BlockchainImpl.getInstance()) { - List removed = new ArrayList<>(); - try { - Db.beginTransaction(); - try (DbIterator unconfirmedTransactions = getAllUnconfirmedTransactions()) { - for (TransactionImpl transaction : unconfirmedTransactions) { - transaction.undoUnconfirmed(); - removed.add(transaction); - } - } - unconfirmedTransactionTable.truncate(); - Db.commitTransaction(); - } catch (Exception e) { - Logger.logErrorMessage(e.toString(), e); - Db.rollbackTransaction(); - throw e; - } finally { - Db.endTransaction(); - } - lostTransactions.clear(); - transactionListeners.notify(removed, Event.REMOVED_UNCONFIRMED_TRANSACTIONS); - } - } - - void requeueAllUnconfirmedTransactions() { - List removed = new ArrayList<>(); - try (DbIterator unconfirmedTransactions = getAllUnconfirmedTransactions()) { - for (TransactionImpl transaction : unconfirmedTransactions) { - transaction.undoUnconfirmed(); - removed.add(transaction); - lostTransactions.add(transaction); - } - } - unconfirmedTransactionTable.truncate(); - transactionListeners.notify(removed, Event.REMOVED_UNCONFIRMED_TRANSACTIONS); - } - - void removeUnconfirmedTransaction(TransactionImpl transaction) { - if (!Db.isInTransaction()) { - try { - Db.beginTransaction(); - removeUnconfirmedTransaction(transaction); - Db.commitTransaction(); - } catch (Exception e) { - Logger.logErrorMessage(e.toString(), e); - Db.rollbackTransaction(); - throw e; - } finally { - Db.endTransaction(); - } - return; - } - try (Connection con = Db.getConnection(); - PreparedStatement pstmt = con.prepareStatement("DELETE FROM unconfirmed_transaction WHERE id = ?")) { - pstmt.setLong(1, transaction.getId()); - int deleted = pstmt.executeUpdate(); - if (deleted > 0) { - transaction.undoUnconfirmed(); - transactionListeners.notify(Collections.singletonList(transaction), Event.REMOVED_UNCONFIRMED_TRANSACTIONS); - } - } catch (SQLException e) { - Logger.logErrorMessage(e.toString(), e); - throw new RuntimeException(e.toString(), e); - } - } - - int getTransactionVersion(int previousBlockHeight) { - return previousBlockHeight < Constants.DIGITAL_GOODS_STORE_BLOCK ? 0 : 1; - } - - void processLater(Collection transactions) { - synchronized (BlockchainImpl.getInstance()) { - for (TransactionImpl transaction : transactions) { - lostTransactions.add(transaction); - } - } - } - - private void processPeerTransactions(JSONArray transactionsData) throws NxtException.ValidationException { - if (Nxt.getBlockchain().getLastBlock().getTimestamp() < Nxt.getEpochTime() - 60 * 1440 && ! testUnconfirmedTransactions) { - return; - } - if (Nxt.getBlockchain().getHeight() <= Constants.NQT_BLOCK) { - return; - } - List transactions = new ArrayList<>(); - for (Object transactionData : transactionsData) { - try { - TransactionImpl transaction = parseTransaction((JSONObject) transactionData); - transaction.validate(); - if(!EconomicClustering.verifyFork(transaction)) { - /*if(Nxt.getBlockchain().getHeight() >= Constants.EC_CHANGE_BLOCK_1) { - throw new NxtException.NotValidException("Transaction from wrong fork"); - }*/ - continue; - } - transactions.add(transaction); - } catch (NxtException.NotCurrentlyValidException ignore) { - } catch (NxtException.NotValidException e) { - Logger.logDebugMessage("Invalid transaction from peer: " + ((JSONObject) transactionData).toJSONString()); - throw e; - } - } - processTransactions(transactions, true); - nonBroadcastedTransactions.removeAll(transactions); - } - - List processTransactions(Collection transactions, final boolean sendToPeers) { - if (transactions.isEmpty()) { - return Collections.emptyList(); - } - List sendToPeersTransactions = new ArrayList<>(); - List addedUnconfirmedTransactions = new ArrayList<>(); - List addedDoubleSpendingTransactions = new ArrayList<>(); - - for (TransactionImpl transaction : transactions) { - - try { - - int curTime = Nxt.getEpochTime(); - if (transaction.getTimestamp() > curTime + 15 || transaction.getExpiration() < curTime - || transaction.getDeadline() > 1440) { - continue; - } - //if (transaction.getVersion() < 1) { - // continue; - //} - - synchronized (BlockchainImpl.getInstance()) { - try { - Db.beginTransaction(); - if (Nxt.getBlockchain().getHeight() < Constants.NQT_BLOCK) { - break; // not ready to process transactions - } - - if (TransactionDb.hasTransaction(transaction.getId()) || unconfirmedTransactionTable.get(transaction.getDbKey()) != null) { - continue; - } - - if (! transaction.verifySignature()) { - if (Account.getAccount(transaction.getSenderId()) != null) { - Logger.logDebugMessage("Transaction " + transaction.getJSONObject().toJSONString() + " failed to verify"); - } - continue; - } - - if (transaction.applyUnconfirmed()) { - if (sendToPeers) { - if (nonBroadcastedTransactions.contains(transaction)) { - Logger.logDebugMessage("Received back transaction " + transaction.getStringId() - + " that we generated, will not forward to peers"); - nonBroadcastedTransactions.remove(transaction); - } else { - sendToPeersTransactions.add(transaction); - } - } - unconfirmedTransactionTable.insert(transaction); - addedUnconfirmedTransactions.add(transaction); - } else { - addedDoubleSpendingTransactions.add(transaction); - } - Db.commitTransaction(); - } catch (Exception e) { - Db.rollbackTransaction(); - throw e; - } finally { - Db.endTransaction(); - } - } - } catch (RuntimeException e) { - Logger.logMessage("Error processing transaction", e); - } - - } - - if (sendToPeersTransactions.size() > 0) { - Peers.sendToSomePeers(sendToPeersTransactions); - } - - if (addedUnconfirmedTransactions.size() > 0) { - transactionListeners.notify(addedUnconfirmedTransactions, Event.ADDED_UNCONFIRMED_TRANSACTIONS); - } - if (addedDoubleSpendingTransactions.size() > 0) { - transactionListeners.notify(addedDoubleSpendingTransactions, Event.ADDED_DOUBLESPENDING_TRANSACTIONS); - } - return addedUnconfirmedTransactions; - } - -} +package nxt; + +import nxt.db.Db; +import nxt.db.DbClause; +import nxt.db.DbIterator; +import nxt.db.DbKey; +import nxt.db.EntityDbTable; +import nxt.peer.Peer; +import nxt.peer.Peers; +import nxt.util.JSON; +import nxt.util.Listener; +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; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +final class TransactionProcessorImpl implements TransactionProcessor { + + private static final boolean enableTransactionRebroadcasting = Nxt.getBooleanProperty("nxt.enableTransactionRebroadcasting"); + private static final boolean testUnconfirmedTransactions = Nxt.getBooleanProperty("nxt.testUnconfirmedTransactions"); + + private static final TransactionProcessorImpl instance = new TransactionProcessorImpl(); + + static TransactionProcessorImpl getInstance() { + return instance; + } + + final DbKey.LongKeyFactory unconfirmedTransactionDbKeyFactory = new DbKey.LongKeyFactory("id") { + + @Override + public DbKey newKey(TransactionImpl transaction) { + return transaction.getDbKey(); + } + + }; + + private final EntityDbTable unconfirmedTransactionTable = new EntityDbTable("unconfirmed_transaction", unconfirmedTransactionDbKeyFactory) { + + @Override + protected TransactionImpl load(Connection con, ResultSet rs) throws SQLException { + byte[] transactionBytes = rs.getBytes("transaction_bytes"); + try { + TransactionImpl transaction = TransactionImpl.parseTransaction(transactionBytes); + transaction.setHeight(rs.getInt("transaction_height")); + return transaction; + } catch (NxtException.ValidationException e) { + throw new RuntimeException(e.toString(), e); + } + } + + @Override + protected void save(Connection con, TransactionImpl transaction) throws SQLException { + try (PreparedStatement pstmt = con.prepareStatement("INSERT INTO unconfirmed_transaction (id, transaction_height, " + + "fee_per_byte, timestamp, expiration, transaction_bytes, height) " + + "VALUES (?, ?, ?, ?, ?, ?, ?)")) { + int i = 0; + pstmt.setLong(++i, transaction.getId()); + pstmt.setInt(++i, transaction.getHeight()); + pstmt.setLong(++i, transaction.getFeeNQT() / transaction.getSize()); + pstmt.setInt(++i, transaction.getTimestamp()); + pstmt.setInt(++i, transaction.getExpiration()); + pstmt.setBytes(++i, transaction.getBytes()); + pstmt.setInt(++i, Nxt.getBlockchain().getHeight()); + pstmt.executeUpdate(); + } + } + + @Override + public void rollback(int height) { + List transactions = new ArrayList<>(); + try (Connection con = Db.getConnection(); + PreparedStatement pstmt = con.prepareStatement("SELECT * FROM unconfirmed_transaction WHERE height > ?")) { + pstmt.setInt(1, height); + try (ResultSet rs = pstmt.executeQuery()) { + while (rs.next()) { + transactions.add(load(con, rs)); + } + } + } catch (SQLException e) { + throw new RuntimeException(e.toString(), e); + } + super.rollback(height); + processLater(transactions); + } + + @Override + protected String defaultSort() { + return " ORDER BY transaction_height ASC, fee_per_byte DESC, timestamp ASC, id ASC "; + } + + }; + + private final Set nonBroadcastedTransactions = Collections.newSetFromMap(new ConcurrentHashMap()); + private final Listeners,Event> transactionListeners = new Listeners<>(); + private final Set lostTransactions = new HashSet<>(); + + private final Runnable removeUnconfirmedTransactionsThread = new Runnable() { + + private final DbClause expiredClause = new DbClause(" expiration < ? ") { + @Override + protected int set(PreparedStatement pstmt, int index) throws SQLException { + pstmt.setInt(index, Nxt.getEpochTime()); + return index + 1; + } + }; + + @Override + public void run() { + + try { + try { + List expiredTransactions = new ArrayList<>(); + try (DbIterator iterator = unconfirmedTransactionTable.getManyBy(expiredClause, 0, -1, "")) { + while (iterator.hasNext()) { + expiredTransactions.add(iterator.next()); + } + } + if (expiredTransactions.size() > 0) { + synchronized (BlockchainImpl.getInstance()) { + try { + Db.beginTransaction(); + for (TransactionImpl transaction : expiredTransactions) { + removeUnconfirmedTransaction(transaction); + } + Db.commitTransaction(); + } catch (Exception e) { + Logger.logErrorMessage(e.toString(), e); + Db.rollbackTransaction(); + throw e; + } finally { + Db.endTransaction(); + } + } // synchronized + } + } catch (Exception e) { + Logger.logDebugMessage("Error removing unconfirmed transactions", e); + } + } catch (Throwable t) { + Logger.logMessage("CRITICAL ERROR. PLEASE REPORT TO THE DEVELOPERS.\n" + t.toString()); + t.printStackTrace(); + System.exit(1); + } + + } + + }; + + private final Runnable rebroadcastTransactionsThread = new Runnable() { + + @Override + public void run() { + + try { + try { + List transactionList = new ArrayList<>(); + int curTime = Nxt.getEpochTime(); + for (TransactionImpl transaction : nonBroadcastedTransactions) { + if (TransactionDb.hasTransaction(transaction.getId()) || transaction.getExpiration() < curTime) { + nonBroadcastedTransactions.remove(transaction); + } else if (transaction.getTimestamp() < curTime - 30) { + transactionList.add(transaction); + } + } + + if (transactionList.size() > 0) { + Peers.sendToSomePeers(transactionList); + } + + } catch (Exception e) { + Logger.logDebugMessage("Error in transaction re-broadcasting thread", e); + } + } catch (Throwable t) { + Logger.logMessage("CRITICAL ERROR. PLEASE REPORT TO THE DEVELOPERS.\n" + t.toString()); + t.printStackTrace(); + System.exit(1); + } + + } + + }; + + private final Runnable processTransactionsThread = new Runnable() { + + private final JSONStreamAware getUnconfirmedTransactionsRequest; + { + JSONObject request = new JSONObject(); + request.put("requestType", "getUnconfirmedTransactions"); + getUnconfirmedTransactionsRequest = JSON.prepareRequest(request); + } + + @Override + public void run() { + try { + try { + synchronized (BlockchainImpl.getInstance()) { + processTransactions(lostTransactions, false); + lostTransactions.clear(); + } + Peer peer = Peers.getAnyPeer(Peer.State.CONNECTED, true); + if (peer == null) { + return; + } + JSONObject response = peer.send(getUnconfirmedTransactionsRequest); + if (response == null) { + return; + } + JSONArray transactionsData = (JSONArray)response.get("unconfirmedTransactions"); + if (transactionsData == null || transactionsData.size() == 0) { + return; + } + try { + processPeerTransactions(transactionsData); + } catch (NxtException.ValidationException|RuntimeException e) { + peer.blacklist(e); + } + } catch (Exception e) { + Logger.logDebugMessage("Error processing unconfirmed transactions", e); + } + } catch (Throwable t) { + Logger.logMessage("CRITICAL ERROR. PLEASE REPORT TO THE DEVELOPERS.\n" + t.toString()); + t.printStackTrace(); + System.exit(1); + } + } + + }; + + private TransactionProcessorImpl() { + ThreadPool.scheduleThread("ProcessTransactions", processTransactionsThread, 5); + ThreadPool.scheduleThread("RemoveUnconfirmedTransactions", removeUnconfirmedTransactionsThread, 1); + if (enableTransactionRebroadcasting) { + ThreadPool.scheduleThread("RebroadcastTransactions", rebroadcastTransactionsThread, 60); + ThreadPool.runAfterStart(new Runnable() { + @Override + public void run() { + try (DbIterator oldNonBroadcastedTransactions = getAllUnconfirmedTransactions()) { + for (TransactionImpl transaction : oldNonBroadcastedTransactions) { + nonBroadcastedTransactions.add(transaction); + } + } + } + }); + } + } + + @Override + public boolean addListener(Listener> listener, Event eventType) { + return transactionListeners.addListener(listener, eventType); + } + + @Override + public boolean removeListener(Listener> listener, Event eventType) { + return transactionListeners.removeListener(listener, eventType); + } + + void notifyListeners(List transactions, Event eventType) { + transactionListeners.notify(transactions, eventType); + } + + @Override + public DbIterator getAllUnconfirmedTransactions() { + return unconfirmedTransactionTable.getAll(0, -1); + } + + @Override + public Transaction getUnconfirmedTransaction(long transactionId) { + return unconfirmedTransactionTable.get(unconfirmedTransactionDbKeyFactory.newKey(transactionId)); + } + + public Transaction.Builder newTransactionBuilder(byte[] senderPublicKey, long amountNQT, long feeNQT, short deadline, + Attachment attachment) { + byte version = (byte) getTransactionVersion(Nxt.getBlockchain().getHeight()); + int timestamp = Nxt.getEpochTime(); + TransactionImpl.BuilderImpl builder = new TransactionImpl.BuilderImpl(version, senderPublicKey, amountNQT, feeNQT, timestamp, + deadline, (Attachment.AbstractAttachment)attachment); + if (version > 0) { + Block ecBlock = EconomicClustering.getECBlock(timestamp); + builder.ecBlockHeight(ecBlock.getHeight()); + builder.ecBlockId(ecBlock.getId()); + } + return builder; + } + + @Override + public void broadcast(Transaction transaction) throws NxtException.ValidationException { + if (! transaction.verifySignature()) { + throw new NxtException.NotValidException("Transaction signature verification failed"); + } + List processedTransactions; + synchronized (BlockchainImpl.getInstance()) { + if (TransactionDb.hasTransaction(transaction.getId())) { + Logger.logMessage("Transaction " + transaction.getStringId() + " already in blockchain, will not broadcast again"); + return; + } + if (unconfirmedTransactionTable.get(((TransactionImpl) transaction).getDbKey()) != null) { + if (enableTransactionRebroadcasting) { + nonBroadcastedTransactions.add((TransactionImpl) transaction); + Logger.logMessage("Transaction " + transaction.getStringId() + " already in unconfirmed pool, will re-broadcast"); + } else { + Logger.logMessage("Transaction " + transaction.getStringId() + " already in unconfirmed pool, will not broadcast again"); + } + return; + } + processedTransactions = processTransactions(Collections.singleton((TransactionImpl) transaction), true); + } + if (processedTransactions.contains(transaction)) { + if (enableTransactionRebroadcasting) { + nonBroadcastedTransactions.add((TransactionImpl) transaction); + } + Logger.logDebugMessage("Accepted new transaction " + transaction.getStringId()); + } else { + Logger.logDebugMessage("Could not accept new transaction " + transaction.getStringId()); + throw new NxtException.NotValidException("Invalid transaction " + transaction.getStringId()); + } + } + + @Override + public void processPeerTransactions(JSONObject request) throws NxtException.ValidationException { + JSONArray transactionsData = (JSONArray)request.get("transactions"); + processPeerTransactions(transactionsData); + } + + @Override + public Transaction parseTransaction(byte[] bytes) throws NxtException.ValidationException { + return TransactionImpl.parseTransaction(bytes); + } + + @Override + public TransactionImpl parseTransaction(JSONObject transactionData) throws NxtException.NotValidException { + return TransactionImpl.parseTransaction(transactionData); + } + + @Override + public void clearUnconfirmedTransactions() { + synchronized (BlockchainImpl.getInstance()) { + List removed = new ArrayList<>(); + try { + Db.beginTransaction(); + try (DbIterator unconfirmedTransactions = getAllUnconfirmedTransactions()) { + for (TransactionImpl transaction : unconfirmedTransactions) { + transaction.undoUnconfirmed(); + removed.add(transaction); + } + } + unconfirmedTransactionTable.truncate(); + Db.commitTransaction(); + } catch (Exception e) { + Logger.logErrorMessage(e.toString(), e); + Db.rollbackTransaction(); + throw e; + } finally { + Db.endTransaction(); + } + lostTransactions.clear(); + transactionListeners.notify(removed, Event.REMOVED_UNCONFIRMED_TRANSACTIONS); + } + } + + void requeueAllUnconfirmedTransactions() { + List removed = new ArrayList<>(); + try (DbIterator unconfirmedTransactions = getAllUnconfirmedTransactions()) { + for (TransactionImpl transaction : unconfirmedTransactions) { + transaction.undoUnconfirmed(); + removed.add(transaction); + lostTransactions.add(transaction); + } + } + unconfirmedTransactionTable.truncate(); + transactionListeners.notify(removed, Event.REMOVED_UNCONFIRMED_TRANSACTIONS); + } + + void removeUnconfirmedTransaction(TransactionImpl transaction) { + if (!Db.isInTransaction()) { + try { + Db.beginTransaction(); + removeUnconfirmedTransaction(transaction); + Db.commitTransaction(); + } catch (Exception e) { + Logger.logErrorMessage(e.toString(), e); + Db.rollbackTransaction(); + throw e; + } finally { + Db.endTransaction(); + } + return; + } + try (Connection con = Db.getConnection(); + PreparedStatement pstmt = con.prepareStatement("DELETE FROM unconfirmed_transaction WHERE id = ?")) { + pstmt.setLong(1, transaction.getId()); + int deleted = pstmt.executeUpdate(); + if (deleted > 0) { + transaction.undoUnconfirmed(); + transactionListeners.notify(Collections.singletonList(transaction), Event.REMOVED_UNCONFIRMED_TRANSACTIONS); + } + } catch (SQLException e) { + Logger.logErrorMessage(e.toString(), e); + throw new RuntimeException(e.toString(), e); + } + } + + int getTransactionVersion(int previousBlockHeight) { + return previousBlockHeight < Constants.DIGITAL_GOODS_STORE_BLOCK ? 0 : 1; + } + + void processLater(Collection transactions) { + synchronized (BlockchainImpl.getInstance()) { + for (TransactionImpl transaction : transactions) { + lostTransactions.add(transaction); + } + } + } + + private void processPeerTransactions(JSONArray transactionsData) throws NxtException.ValidationException { + if (Nxt.getBlockchain().getLastBlock().getTimestamp() < Nxt.getEpochTime() - 60 * 1440 && ! testUnconfirmedTransactions) { + return; + } + if (Nxt.getBlockchain().getHeight() <= Constants.NQT_BLOCK) { + return; + } + List transactions = new ArrayList<>(); + for (Object transactionData : transactionsData) { + try { + TransactionImpl transaction = parseTransaction((JSONObject) transactionData); + transaction.validate(); + if(!EconomicClustering.verifyFork(transaction)) { + /*if(Nxt.getBlockchain().getHeight() >= Constants.EC_CHANGE_BLOCK_1) { + throw new NxtException.NotValidException("Transaction from wrong fork"); + }*/ + continue; + } + transactions.add(transaction); + } catch (NxtException.NotCurrentlyValidException ignore) { + } catch (NxtException.NotValidException e) { + Logger.logDebugMessage("Invalid transaction from peer: " + ((JSONObject) transactionData).toJSONString()); + throw e; + } + } + processTransactions(transactions, true); + nonBroadcastedTransactions.removeAll(transactions); + } + + List processTransactions(Collection transactions, final boolean sendToPeers) { + if (transactions.isEmpty()) { + return Collections.emptyList(); + } + List sendToPeersTransactions = new ArrayList<>(); + List addedUnconfirmedTransactions = new ArrayList<>(); + List addedDoubleSpendingTransactions = new ArrayList<>(); + + for (TransactionImpl transaction : transactions) { + + try { + + int curTime = Nxt.getEpochTime(); + if (transaction.getTimestamp() > curTime + 15 || transaction.getExpiration() < curTime + || transaction.getDeadline() > 1440) { + continue; + } + //if (transaction.getVersion() < 1) { + // continue; + //} + + synchronized (BlockchainImpl.getInstance()) { + try { + Db.beginTransaction(); + if (Nxt.getBlockchain().getHeight() < Constants.NQT_BLOCK) { + break; // not ready to process transactions + } + + if (TransactionDb.hasTransaction(transaction.getId()) || unconfirmedTransactionTable.get(transaction.getDbKey()) != null) { + continue; + } + + if (! transaction.verifySignature()) { + if (Account.getAccount(transaction.getSenderId()) != null) { + Logger.logDebugMessage("Transaction " + transaction.getJSONObject().toJSONString() + " failed to verify"); + } + continue; + } + + if (transaction.applyUnconfirmed()) { + if (sendToPeers) { + if (nonBroadcastedTransactions.contains(transaction)) { + Logger.logDebugMessage("Received back transaction " + transaction.getStringId() + + " that we generated, will not forward to peers"); + nonBroadcastedTransactions.remove(transaction); + } else { + sendToPeersTransactions.add(transaction); + } + } + unconfirmedTransactionTable.insert(transaction); + addedUnconfirmedTransactions.add(transaction); + } else { + addedDoubleSpendingTransactions.add(transaction); + } + Db.commitTransaction(); + } catch (Exception e) { + Db.rollbackTransaction(); + throw e; + } finally { + Db.endTransaction(); + } + } + } catch (RuntimeException e) { + Logger.logMessage("Error processing transaction", e); + } + + } + + if (sendToPeersTransactions.size() > 0) { + Peers.sendToSomePeers(sendToPeersTransactions); + } + + if (addedUnconfirmedTransactions.size() > 0) { + transactionListeners.notify(addedUnconfirmedTransactions, Event.ADDED_UNCONFIRMED_TRANSACTIONS); + } + if (addedDoubleSpendingTransactions.size() > 0) { + transactionListeners.notify(addedDoubleSpendingTransactions, Event.ADDED_DOUBLESPENDING_TRANSACTIONS); + } + return addedUnconfirmedTransactions; + } + +} diff --git a/src/java/nxt/TransactionType.java b/src/java/nxt/TransactionType.java index bab43f6de..49edf9731 100644 --- a/src/java/nxt/TransactionType.java +++ b/src/java/nxt/TransactionType.java @@ -1,2410 +1,2410 @@ -package nxt; - -import nxt.Attachment.AbstractAttachment; -import nxt.Attachment.AutomatedTransactionsCreation; -import nxt.NxtException.NotValidException; -import nxt.NxtException.ValidationException; -import nxt.at.AT_Constants; -import nxt.at.AT_Controller; -import nxt.at.AT_Exception; -import nxt.util.Convert; - -import org.json.simple.JSONObject; - -import java.nio.ByteBuffer; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - - -public abstract class TransactionType { - - private static final byte TYPE_PAYMENT = 0; - private static final byte TYPE_MESSAGING = 1; - private static final byte TYPE_COLORED_COINS = 2; - private static final byte TYPE_DIGITAL_GOODS = 3; - private static final byte TYPE_ACCOUNT_CONTROL = 4; - - private static final byte TYPE_BURST_MINING = 20; // jump some for easier nxt updating - private static final byte TYPE_ADVANCED_PAYMENT = 21; - private static final byte TYPE_AUTOMATED_TRANSACTIONS = 22; - - private static final byte SUBTYPE_PAYMENT_ORDINARY_PAYMENT = 0; - - private static final byte SUBTYPE_MESSAGING_ARBITRARY_MESSAGE = 0; - private static final byte SUBTYPE_MESSAGING_ALIAS_ASSIGNMENT = 1; - private static final byte SUBTYPE_MESSAGING_POLL_CREATION = 2; - private static final byte SUBTYPE_MESSAGING_VOTE_CASTING = 3; - private static final byte SUBTYPE_MESSAGING_HUB_ANNOUNCEMENT = 4; - private static final byte SUBTYPE_MESSAGING_ACCOUNT_INFO = 5; - private static final byte SUBTYPE_MESSAGING_ALIAS_SELL = 6; - private static final byte SUBTYPE_MESSAGING_ALIAS_BUY = 7; - - private static final byte SUBTYPE_COLORED_COINS_ASSET_ISSUANCE = 0; - private static final byte SUBTYPE_COLORED_COINS_ASSET_TRANSFER = 1; - private static final byte SUBTYPE_COLORED_COINS_ASK_ORDER_PLACEMENT = 2; - private static final byte SUBTYPE_COLORED_COINS_BID_ORDER_PLACEMENT = 3; - private static final byte SUBTYPE_COLORED_COINS_ASK_ORDER_CANCELLATION = 4; - private static final byte SUBTYPE_COLORED_COINS_BID_ORDER_CANCELLATION = 5; - - private static final byte SUBTYPE_DIGITAL_GOODS_LISTING = 0; - private static final byte SUBTYPE_DIGITAL_GOODS_DELISTING = 1; - private static final byte SUBTYPE_DIGITAL_GOODS_PRICE_CHANGE = 2; - private static final byte SUBTYPE_DIGITAL_GOODS_QUANTITY_CHANGE = 3; - private static final byte SUBTYPE_DIGITAL_GOODS_PURCHASE = 4; - private static final byte SUBTYPE_DIGITAL_GOODS_DELIVERY = 5; - private static final byte SUBTYPE_DIGITAL_GOODS_FEEDBACK = 6; - private static final byte SUBTYPE_DIGITAL_GOODS_REFUND = 7; - - private static final byte SUBTYPE_AT_CREATION = 0; - private static final byte SUBTYPE_AT_NXT_PAYMENT = 1; - - private static final byte SUBTYPE_ACCOUNT_CONTROL_EFFECTIVE_BALANCE_LEASING = 0; - - private static final byte SUBTYPE_BURST_MINING_REWARD_RECIPIENT_ASSIGNMENT = 0; - - private static final byte SUBTYPE_ADVANCED_PAYMENT_ESCROW_CREATION = 0; - private static final byte SUBTYPE_ADVANCED_PAYMENT_ESCROW_SIGN = 1; - private static final byte SUBTYPE_ADVANCED_PAYMENT_ESCROW_RESULT = 2; - private static final byte SUBTYPE_ADVANCED_PAYMENT_SUBSCRIPTION_SUBSCRIBE = 3; - private static final byte SUBTYPE_ADVANCED_PAYMENT_SUBSCRIPTION_CANCEL = 4; - private static final byte SUBTYPE_ADVANCED_PAYMENT_SUBSCRIPTION_PAYMENT = 5; - - private static final int BASELINE_FEE_HEIGHT = 1; // At release time must be less than current block - 1440 - private static final Fee BASELINE_FEE = new Fee(Constants.ONE_NXT, 0); - private static final Fee BASELINE_ASSET_ISSUANCE_FEE = new Fee(1000 * Constants.ONE_NXT, 0); - private static final int NEXT_FEE_HEIGHT = Integer.MAX_VALUE; - private static final Fee NEXT_FEE = new Fee(Constants.ONE_NXT, 0); - private static final Fee NEXT_ASSET_ISSUANCE_FEE = new Fee(1000 * Constants.ONE_NXT, 0); - - public static TransactionType findTransactionType(byte type, byte subtype) { - switch (type) { - case TYPE_PAYMENT: - switch (subtype) { - case SUBTYPE_PAYMENT_ORDINARY_PAYMENT: - return Payment.ORDINARY; - default: - return null; - } - case TYPE_MESSAGING: - switch (subtype) { - case SUBTYPE_MESSAGING_ARBITRARY_MESSAGE: - return Messaging.ARBITRARY_MESSAGE; - case SUBTYPE_MESSAGING_ALIAS_ASSIGNMENT: - return Messaging.ALIAS_ASSIGNMENT; - case SUBTYPE_MESSAGING_POLL_CREATION: - return Messaging.POLL_CREATION; - case SUBTYPE_MESSAGING_VOTE_CASTING: - return Messaging.VOTE_CASTING; - case SUBTYPE_MESSAGING_HUB_ANNOUNCEMENT: - return Messaging.HUB_ANNOUNCEMENT; - case SUBTYPE_MESSAGING_ACCOUNT_INFO: - return Messaging.ACCOUNT_INFO; - case SUBTYPE_MESSAGING_ALIAS_SELL: - return Messaging.ALIAS_SELL; - case SUBTYPE_MESSAGING_ALIAS_BUY: - return Messaging.ALIAS_BUY; - default: - return null; - } - case TYPE_COLORED_COINS: - switch (subtype) { - case SUBTYPE_COLORED_COINS_ASSET_ISSUANCE: - return ColoredCoins.ASSET_ISSUANCE; - case SUBTYPE_COLORED_COINS_ASSET_TRANSFER: - return ColoredCoins.ASSET_TRANSFER; - case SUBTYPE_COLORED_COINS_ASK_ORDER_PLACEMENT: - return ColoredCoins.ASK_ORDER_PLACEMENT; - case SUBTYPE_COLORED_COINS_BID_ORDER_PLACEMENT: - return ColoredCoins.BID_ORDER_PLACEMENT; - case SUBTYPE_COLORED_COINS_ASK_ORDER_CANCELLATION: - return ColoredCoins.ASK_ORDER_CANCELLATION; - case SUBTYPE_COLORED_COINS_BID_ORDER_CANCELLATION: - return ColoredCoins.BID_ORDER_CANCELLATION; - default: - return null; - } - case TYPE_DIGITAL_GOODS: - switch (subtype) { - case SUBTYPE_DIGITAL_GOODS_LISTING: - return DigitalGoods.LISTING; - case SUBTYPE_DIGITAL_GOODS_DELISTING: - return DigitalGoods.DELISTING; - case SUBTYPE_DIGITAL_GOODS_PRICE_CHANGE: - return DigitalGoods.PRICE_CHANGE; - case SUBTYPE_DIGITAL_GOODS_QUANTITY_CHANGE: - return DigitalGoods.QUANTITY_CHANGE; - case SUBTYPE_DIGITAL_GOODS_PURCHASE: - return DigitalGoods.PURCHASE; - case SUBTYPE_DIGITAL_GOODS_DELIVERY: - return DigitalGoods.DELIVERY; - case SUBTYPE_DIGITAL_GOODS_FEEDBACK: - return DigitalGoods.FEEDBACK; - case SUBTYPE_DIGITAL_GOODS_REFUND: - return DigitalGoods.REFUND; - default: - return null; - } - case TYPE_ACCOUNT_CONTROL: - switch (subtype) { - case SUBTYPE_ACCOUNT_CONTROL_EFFECTIVE_BALANCE_LEASING: - return AccountControl.EFFECTIVE_BALANCE_LEASING; - default: - return null; - } - case TYPE_BURST_MINING: - switch (subtype) { - case SUBTYPE_BURST_MINING_REWARD_RECIPIENT_ASSIGNMENT: - return BurstMining.REWARD_RECIPIENT_ASSIGNMENT; - default: - return null; - } - case TYPE_ADVANCED_PAYMENT: - switch (subtype) { - case SUBTYPE_ADVANCED_PAYMENT_ESCROW_CREATION: - return AdvancedPayment.ESCROW_CREATION; - case SUBTYPE_ADVANCED_PAYMENT_ESCROW_SIGN: - return AdvancedPayment.ESCROW_SIGN; - case SUBTYPE_ADVANCED_PAYMENT_ESCROW_RESULT: - return AdvancedPayment.ESCROW_RESULT; - case SUBTYPE_ADVANCED_PAYMENT_SUBSCRIPTION_SUBSCRIBE: - return AdvancedPayment.SUBSCRIPTION_SUBSCRIBE; - case SUBTYPE_ADVANCED_PAYMENT_SUBSCRIPTION_CANCEL: - return AdvancedPayment.SUBSCRIPTION_CANCEL; - case SUBTYPE_ADVANCED_PAYMENT_SUBSCRIPTION_PAYMENT: - return AdvancedPayment.SUBSCRIPTION_PAYMENT; - default: - return null; - } - case TYPE_AUTOMATED_TRANSACTIONS: - switch (subtype) { - case SUBTYPE_AT_CREATION: - return AutomatedTransactions.AUTOMATED_TRANSACTION_CREATION; - case SUBTYPE_AT_NXT_PAYMENT: - return AutomatedTransactions.AT_PAYMENT; - default: - return null; - } - default: - return null; - } - } - - private TransactionType() { - } - - public abstract byte getType(); - - public abstract byte getSubtype(); - - abstract Attachment.AbstractAttachment parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException; - - abstract Attachment.AbstractAttachment parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException; - - abstract void validateAttachment(Transaction transaction) throws NxtException.ValidationException; - - // return false iff double spending - final boolean applyUnconfirmed(Transaction transaction, Account senderAccount) { - long totalAmountNQT = Convert.safeAdd(transaction.getAmountNQT(), transaction.getFeeNQT()); - if (transaction.getReferencedTransactionFullHash() != null - && transaction.getTimestamp() > Constants.REFERENCED_TRANSACTION_FULL_HASH_BLOCK_TIMESTAMP) { - totalAmountNQT = Convert.safeAdd(totalAmountNQT, Constants.UNCONFIRMED_POOL_DEPOSIT_NQT); - } - if (senderAccount.getUnconfirmedBalanceNQT() < totalAmountNQT) { - return false; - } - senderAccount.addToUnconfirmedBalanceNQT(-totalAmountNQT); - if (!applyAttachmentUnconfirmed(transaction, senderAccount)) { - senderAccount.addToUnconfirmedBalanceNQT(totalAmountNQT); - return false; - } - return true; - } - - abstract boolean applyAttachmentUnconfirmed(Transaction transaction, Account senderAccount); - - final void apply(Transaction transaction, Account senderAccount, Account recipientAccount) { - senderAccount.addToBalanceNQT(- (Convert.safeAdd(transaction.getAmountNQT(), transaction.getFeeNQT()))); - if (transaction.getReferencedTransactionFullHash() != null - && transaction.getTimestamp() > Constants.REFERENCED_TRANSACTION_FULL_HASH_BLOCK_TIMESTAMP) { - senderAccount.addToUnconfirmedBalanceNQT(Constants.UNCONFIRMED_POOL_DEPOSIT_NQT); - } - if (recipientAccount != null) { - recipientAccount.addToBalanceAndUnconfirmedBalanceNQT(transaction.getAmountNQT()); - } - applyAttachment(transaction, senderAccount, recipientAccount); - } - - abstract void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount); - - final void undoUnconfirmed(Transaction transaction, Account senderAccount) { - undoAttachmentUnconfirmed(transaction, senderAccount); - senderAccount.addToUnconfirmedBalanceNQT(Convert.safeAdd(transaction.getAmountNQT(), transaction.getFeeNQT())); - if (transaction.getReferencedTransactionFullHash() != null - && transaction.getTimestamp() > Constants.REFERENCED_TRANSACTION_FULL_HASH_BLOCK_TIMESTAMP) { - senderAccount.addToUnconfirmedBalanceNQT(Constants.UNCONFIRMED_POOL_DEPOSIT_NQT); - } - } - - abstract void undoAttachmentUnconfirmed(Transaction transaction, Account senderAccount); - - boolean isDuplicate(Transaction transaction, Map> duplicates) { - return false; - } - - static boolean isDuplicate(TransactionType uniqueType, String key, Map> duplicates) { - Set typeDuplicates = duplicates.get(uniqueType); - if (typeDuplicates == null) { - typeDuplicates = new HashSet<>(); - duplicates.put(uniqueType, typeDuplicates); - } - return ! typeDuplicates.add(key); - } - - public abstract boolean hasRecipient(); - - public boolean isSigned() { - return true; - } - - @Override - public final String toString() { - return "type: " + getType() + ", subtype: " + getSubtype(); - } - - /* - Collection getPhasingTransactionTypes() { - return Collections.emptyList(); - } - - Collection getPhasedTransactionTypes() { - return Collections.emptyList(); - } - */ - - public static abstract class Payment extends TransactionType { - - private Payment() { - } - - @Override - public final byte getType() { - return TransactionType.TYPE_PAYMENT; - } - - @Override - final boolean applyAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { - return true; - } - - @Override - final void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { - } - - @Override - final void undoAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { - } - - @Override - final public boolean hasRecipient() { - return true; - } - - public static final TransactionType ORDINARY = new Payment() { - - @Override - public final byte getSubtype() { - return TransactionType.SUBTYPE_PAYMENT_ORDINARY_PAYMENT; - } - - @Override - Attachment.EmptyAttachment parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { - return Attachment.ORDINARY_PAYMENT; - } - - @Override - Attachment.EmptyAttachment parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { - return Attachment.ORDINARY_PAYMENT; - } - - @Override - void validateAttachment(Transaction transaction) throws NxtException.ValidationException { - if (transaction.getAmountNQT() <= 0 || transaction.getAmountNQT() >= Constants.MAX_BALANCE_NQT) { - throw new NxtException.NotValidException("Invalid ordinary payment"); - } - } - - }; - - } - - public static abstract class Messaging extends TransactionType { - - private Messaging() { - } - - @Override - public final byte getType() { - return TransactionType.TYPE_MESSAGING; - } - - @Override - final boolean applyAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { - return true; - } - - @Override - final void undoAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { - } - - public final static TransactionType ARBITRARY_MESSAGE = new Messaging() { - - @Override - public final byte getSubtype() { - return TransactionType.SUBTYPE_MESSAGING_ARBITRARY_MESSAGE; - } - - @Override - Attachment.EmptyAttachment parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { - return Attachment.ARBITRARY_MESSAGE; - } - - @Override - Attachment.EmptyAttachment parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { - return Attachment.ARBITRARY_MESSAGE; - } - - @Override - void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { - } - - @Override - void validateAttachment(Transaction transaction) throws NxtException.ValidationException { - Attachment attachment = transaction.getAttachment(); - if (transaction.getAmountNQT() != 0) { - throw new NxtException.NotValidException("Invalid arbitrary message: " + attachment.getJSONObject()); - } - if (Nxt.getBlockchain().getHeight() < Constants.DIGITAL_GOODS_STORE_BLOCK && transaction.getMessage() == null) { - throw new NxtException.NotCurrentlyValidException("Missing message appendix not allowed before DGS block"); - } - } - - @Override - public boolean hasRecipient() { - return true; - } - - }; - - public static final TransactionType ALIAS_ASSIGNMENT = new Messaging() { - - @Override - public final byte getSubtype() { - return TransactionType.SUBTYPE_MESSAGING_ALIAS_ASSIGNMENT; - } - - @Override - Attachment.MessagingAliasAssignment parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { - return new Attachment.MessagingAliasAssignment(buffer, transactionVersion); - } - - @Override - Attachment.MessagingAliasAssignment parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { - return new Attachment.MessagingAliasAssignment(attachmentData); - } - - @Override - void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { - Attachment.MessagingAliasAssignment attachment = (Attachment.MessagingAliasAssignment) transaction.getAttachment(); - Alias.addOrUpdateAlias(transaction, attachment); - } - - @Override - boolean isDuplicate(Transaction transaction, Map> duplicates) { - Attachment.MessagingAliasAssignment attachment = (Attachment.MessagingAliasAssignment) transaction.getAttachment(); - return isDuplicate(Messaging.ALIAS_ASSIGNMENT, attachment.getAliasName().toLowerCase(), duplicates); - } - - @Override - void validateAttachment(Transaction transaction) throws NxtException.ValidationException { - Attachment.MessagingAliasAssignment attachment = (Attachment.MessagingAliasAssignment) transaction.getAttachment(); - if (attachment.getAliasName().length() == 0 - || attachment.getAliasName().length() > Constants.MAX_ALIAS_LENGTH - || attachment.getAliasURI().length() > Constants.MAX_ALIAS_URI_LENGTH) { - throw new NxtException.NotValidException("Invalid alias assignment: " + attachment.getJSONObject()); - } - String normalizedAlias = attachment.getAliasName().toLowerCase(); - for (int i = 0; i < normalizedAlias.length(); i++) { - if (Constants.ALPHABET.indexOf(normalizedAlias.charAt(i)) < 0) { - throw new NxtException.NotValidException("Invalid alias name: " + normalizedAlias); - } - } - Alias alias = Alias.getAlias(normalizedAlias); - if (alias != null && alias.getAccountId() != transaction.getSenderId()) { - throw new NxtException.NotCurrentlyValidException("Alias already owned by another account: " + normalizedAlias); - } - } - - @Override - public boolean hasRecipient() { - return false; - } - - }; - - public static final TransactionType ALIAS_SELL = new Messaging() { - - @Override - public final byte getSubtype() { - return TransactionType.SUBTYPE_MESSAGING_ALIAS_SELL; - } - - @Override - Attachment.MessagingAliasSell parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { - return new Attachment.MessagingAliasSell(buffer, transactionVersion); - } - - @Override - Attachment.MessagingAliasSell parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { - return new Attachment.MessagingAliasSell(attachmentData); - } - - @Override - void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { - final Attachment.MessagingAliasSell attachment = - (Attachment.MessagingAliasSell) transaction.getAttachment(); - Alias.sellAlias(transaction, attachment); - } - - @Override - boolean isDuplicate(Transaction transaction, Map> duplicates) { - Attachment.MessagingAliasSell attachment = (Attachment.MessagingAliasSell) transaction.getAttachment(); - // not a bug, uniqueness is based on Messaging.ALIAS_ASSIGNMENT - return isDuplicate(Messaging.ALIAS_ASSIGNMENT, attachment.getAliasName().toLowerCase(), duplicates); - } - - @Override - void validateAttachment(Transaction transaction) throws NxtException.ValidationException { - if (Nxt.getBlockchain().getLastBlock().getHeight() < Constants.DIGITAL_GOODS_STORE_BLOCK) { - throw new NxtException.NotYetEnabledException("Alias transfer not yet enabled at height " + Nxt.getBlockchain().getLastBlock().getHeight()); - } - if (transaction.getAmountNQT() != 0) { - throw new NxtException.NotValidException("Invalid sell alias transaction: " + - transaction.getJSONObject()); - } - final Attachment.MessagingAliasSell attachment = - (Attachment.MessagingAliasSell) transaction.getAttachment(); - final String aliasName = attachment.getAliasName(); - if (aliasName == null || aliasName.length() == 0) { - throw new NxtException.NotValidException("Missing alias name"); - } - long priceNQT = attachment.getPriceNQT(); - if (priceNQT < 0 || priceNQT > Constants.MAX_BALANCE_NQT) { - throw new NxtException.NotValidException("Invalid alias sell price: " + priceNQT); - } - if (priceNQT == 0) { - if (Genesis.CREATOR_ID == transaction.getRecipientId()) { - throw new NxtException.NotValidException("Transferring aliases to Genesis account not allowed"); - } else if (transaction.getRecipientId() == 0) { - throw new NxtException.NotValidException("Missing alias transfer recipient"); - } - } - final Alias alias = Alias.getAlias(aliasName); - if (alias == null) { - throw new NxtException.NotCurrentlyValidException("Alias hasn't been registered yet: " + aliasName); - } else if (alias.getAccountId() != transaction.getSenderId()) { - throw new NxtException.NotCurrentlyValidException("Alias doesn't belong to sender: " + aliasName); - } - } - - @Override - public boolean hasRecipient() { - return true; - } - - }; - - public static final TransactionType ALIAS_BUY = new Messaging() { - - @Override - public final byte getSubtype() { - return TransactionType.SUBTYPE_MESSAGING_ALIAS_BUY; - } - - @Override - Attachment.MessagingAliasBuy parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { - return new Attachment.MessagingAliasBuy(buffer, transactionVersion); - } - - @Override - Attachment.MessagingAliasBuy parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { - return new Attachment.MessagingAliasBuy(attachmentData); - } - - @Override - void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { - final Attachment.MessagingAliasBuy attachment = - (Attachment.MessagingAliasBuy) transaction.getAttachment(); - final String aliasName = attachment.getAliasName(); - Alias.changeOwner(transaction.getSenderId(), aliasName, transaction.getBlockTimestamp()); - } - - @Override - boolean isDuplicate(Transaction transaction, Map> duplicates) { - Attachment.MessagingAliasBuy attachment = (Attachment.MessagingAliasBuy) transaction.getAttachment(); - // not a bug, uniqueness is based on Messaging.ALIAS_ASSIGNMENT - return isDuplicate(Messaging.ALIAS_ASSIGNMENT, attachment.getAliasName().toLowerCase(), duplicates); - } - - @Override - void validateAttachment(Transaction transaction) throws NxtException.ValidationException { - if (Nxt.getBlockchain().getLastBlock().getHeight() < Constants.DIGITAL_GOODS_STORE_BLOCK) { - throw new NxtException.NotYetEnabledException("Alias transfer not yet enabled at height " + Nxt.getBlockchain().getLastBlock().getHeight()); - } - final Attachment.MessagingAliasBuy attachment = - (Attachment.MessagingAliasBuy) transaction.getAttachment(); - final String aliasName = attachment.getAliasName(); - final Alias alias = Alias.getAlias(aliasName); - if (alias == null) { - throw new NxtException.NotCurrentlyValidException("Alias hasn't been registered yet: " + aliasName); - } else if (alias.getAccountId() != transaction.getRecipientId()) { - throw new NxtException.NotCurrentlyValidException("Alias is owned by account other than recipient: " - + Convert.toUnsignedLong(alias.getAccountId())); - } - Alias.Offer offer = Alias.getOffer(alias); - if (offer == null) { - throw new NxtException.NotCurrentlyValidException("Alias is not for sale: " + aliasName); - } - if (transaction.getAmountNQT() < offer.getPriceNQT()) { - String msg = "Price is too low for: " + aliasName + " (" - + transaction.getAmountNQT() + " < " + offer.getPriceNQT() + ")"; - throw new NxtException.NotCurrentlyValidException(msg); - } - if (offer.getBuyerId() != 0 && offer.getBuyerId() != transaction.getSenderId()) { - throw new NxtException.NotCurrentlyValidException("Wrong buyer for " + aliasName + ": " - + Convert.toUnsignedLong(transaction.getSenderId()) + " expected: " - + Convert.toUnsignedLong(offer.getBuyerId())); - } - } - - @Override - public boolean hasRecipient() { - return true; - } - - }; - - public final static TransactionType POLL_CREATION = new Messaging() { - @Override - public final byte getSubtype() { - return TransactionType.SUBTYPE_MESSAGING_POLL_CREATION; - } - - @Override - Attachment.MessagingPollCreation parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { - return new Attachment.MessagingPollCreation(buffer, transactionVersion); - } - - @Override - Attachment.MessagingPollCreation parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { - return new Attachment.MessagingPollCreation(attachmentData); - } - - @Override - void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { - Attachment.MessagingPollCreation attachment = (Attachment.MessagingPollCreation) transaction.getAttachment(); - Poll.addPoll(transaction, attachment); - } - - @Override - void validateAttachment(Transaction transaction) throws NxtException.ValidationException { - if (Nxt.getBlockchain().getLastBlock().getHeight() < Constants.VOTING_SYSTEM_BLOCK) { - throw new NxtException.NotYetEnabledException("Voting System not yet enabled at height " + Nxt.getBlockchain().getLastBlock().getHeight()); - } - Attachment.MessagingPollCreation attachment = (Attachment.MessagingPollCreation) transaction.getAttachment(); - for (int i = 0; i < attachment.getPollOptions().length; i++) { - if (attachment.getPollOptions()[i].length() > Constants.MAX_POLL_OPTION_LENGTH) { - throw new NxtException.NotValidException("Invalid poll options length: " + attachment.getJSONObject()); - } - } - if (attachment.getPollName().length() > Constants.MAX_POLL_NAME_LENGTH - || attachment.getPollDescription().length() > Constants.MAX_POLL_DESCRIPTION_LENGTH - || attachment.getPollOptions().length > Constants.MAX_POLL_OPTION_COUNT) { - throw new NxtException.NotValidException("Invalid poll attachment: " + attachment.getJSONObject()); - } - } - - @Override - public boolean hasRecipient() { - return false; - } - - }; - - public final static TransactionType VOTE_CASTING = new Messaging() { - - @Override - public final byte getSubtype() { - return TransactionType.SUBTYPE_MESSAGING_VOTE_CASTING; - } - - @Override - Attachment.MessagingVoteCasting parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { - return new Attachment.MessagingVoteCasting(buffer, transactionVersion); - } - - @Override - Attachment.MessagingVoteCasting parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { - return new Attachment.MessagingVoteCasting(attachmentData); - } - - @Override - void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { - Attachment.MessagingVoteCasting attachment = (Attachment.MessagingVoteCasting) transaction.getAttachment(); - Poll poll = Poll.getPoll(attachment.getPollId()); - if (poll != null) { - Vote.addVote(transaction, attachment); - } - } - - @Override - void validateAttachment(Transaction transaction) throws NxtException.ValidationException { - if (Nxt.getBlockchain().getLastBlock().getHeight() < Constants.VOTING_SYSTEM_BLOCK) { - throw new NxtException.NotYetEnabledException("Voting System not yet enabled at height " + Nxt.getBlockchain().getLastBlock().getHeight()); - } - Attachment.MessagingVoteCasting attachment = (Attachment.MessagingVoteCasting) transaction.getAttachment(); - if (attachment.getPollId() == 0 || attachment.getPollVote() == null - || attachment.getPollVote().length > Constants.MAX_POLL_OPTION_COUNT) { - throw new NxtException.NotValidException("Invalid vote casting attachment: " + attachment.getJSONObject()); - } - if (Poll.getPoll(attachment.getPollId()) == null) { - throw new NxtException.NotCurrentlyValidException("Invalid poll: " + Convert.toUnsignedLong(attachment.getPollId())); - } - } - - @Override - public boolean hasRecipient() { - return false; - } - - }; - - public static final TransactionType HUB_ANNOUNCEMENT = new Messaging() { - - @Override - public final byte getSubtype() { - return TransactionType.SUBTYPE_MESSAGING_HUB_ANNOUNCEMENT; - } - - @Override - Attachment.MessagingHubAnnouncement parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { - return new Attachment.MessagingHubAnnouncement(buffer, transactionVersion); - } - - @Override - Attachment.MessagingHubAnnouncement parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { - return new Attachment.MessagingHubAnnouncement(attachmentData); - } - - @Override - void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { - Attachment.MessagingHubAnnouncement attachment = (Attachment.MessagingHubAnnouncement) transaction.getAttachment(); - Hub.addOrUpdateHub(transaction, attachment); - } - - @Override - void validateAttachment(Transaction transaction) throws NxtException.ValidationException { - if (Nxt.getBlockchain().getLastBlock().getHeight() < Constants.TRANSPARENT_FORGING_BLOCK_7) { - throw new NxtException.NotYetEnabledException("Hub terminal announcement not yet enabled at height " + Nxt.getBlockchain().getLastBlock().getHeight()); - } - Attachment.MessagingHubAnnouncement attachment = (Attachment.MessagingHubAnnouncement) transaction.getAttachment(); - if (attachment.getMinFeePerByteNQT() < 0 || attachment.getMinFeePerByteNQT() > Constants.MAX_BALANCE_NQT - || attachment.getUris().length > Constants.MAX_HUB_ANNOUNCEMENT_URIS) { - // cfb: "0" is allowed to show that another way to determine the min fee should be used - throw new NxtException.NotValidException("Invalid hub terminal announcement: " + attachment.getJSONObject()); - } - for (String uri : attachment.getUris()) { - if (uri.length() > Constants.MAX_HUB_ANNOUNCEMENT_URI_LENGTH) { - throw new NxtException.NotValidException("Invalid URI length: " + uri.length()); - } - //TODO: also check URI validity here? - } - } - - @Override - public boolean hasRecipient() { - return false; - } - - }; - - public static final Messaging ACCOUNT_INFO = new Messaging() { - - @Override - public byte getSubtype() { - return TransactionType.SUBTYPE_MESSAGING_ACCOUNT_INFO; - } - - @Override - Attachment.MessagingAccountInfo parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { - return new Attachment.MessagingAccountInfo(buffer, transactionVersion); - } - - @Override - Attachment.MessagingAccountInfo parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { - return new Attachment.MessagingAccountInfo(attachmentData); - } - - @Override - void validateAttachment(Transaction transaction) throws NxtException.ValidationException { - Attachment.MessagingAccountInfo attachment = (Attachment.MessagingAccountInfo)transaction.getAttachment(); - if (attachment.getName().length() > Constants.MAX_ACCOUNT_NAME_LENGTH - || attachment.getDescription().length() > Constants.MAX_ACCOUNT_DESCRIPTION_LENGTH - ) { - throw new NxtException.NotValidException("Invalid account info issuance: " + attachment.getJSONObject()); - } - } - - @Override - void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { - Attachment.MessagingAccountInfo attachment = (Attachment.MessagingAccountInfo) transaction.getAttachment(); - senderAccount.setAccountInfo(attachment.getName(), attachment.getDescription()); - } - - @Override - public boolean hasRecipient() { - return false; - } - - }; - - } - - public static abstract class ColoredCoins extends TransactionType { - - private ColoredCoins() {} - - @Override - public final byte getType() { - return TransactionType.TYPE_COLORED_COINS; - } - - public static final TransactionType ASSET_ISSUANCE = new ColoredCoins() { - - @Override - public final byte getSubtype() { - return TransactionType.SUBTYPE_COLORED_COINS_ASSET_ISSUANCE; - } - - @Override - public Fee getBaselineFee() { - return BASELINE_ASSET_ISSUANCE_FEE; - } - - @Override - public Fee getNextFee() { - return NEXT_ASSET_ISSUANCE_FEE; - } - - @Override - Attachment.ColoredCoinsAssetIssuance parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { - return new Attachment.ColoredCoinsAssetIssuance(buffer, transactionVersion); - } - - @Override - Attachment.ColoredCoinsAssetIssuance parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { - return new Attachment.ColoredCoinsAssetIssuance(attachmentData); - } - - @Override - boolean applyAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { - return true; - } - - @Override - void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { - Attachment.ColoredCoinsAssetIssuance attachment = (Attachment.ColoredCoinsAssetIssuance) transaction.getAttachment(); - long assetId = transaction.getId(); - Asset.addAsset(transaction, attachment); - senderAccount.addToAssetAndUnconfirmedAssetBalanceQNT(assetId, attachment.getQuantityQNT()); - } - - @Override - void undoAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { - } - - @Override - void validateAttachment(Transaction transaction) throws NxtException.ValidationException { - Attachment.ColoredCoinsAssetIssuance attachment = (Attachment.ColoredCoinsAssetIssuance)transaction.getAttachment(); - if (attachment.getName().length() < Constants.MIN_ASSET_NAME_LENGTH - || attachment.getName().length() > Constants.MAX_ASSET_NAME_LENGTH - || attachment.getDescription().length() > Constants.MAX_ASSET_DESCRIPTION_LENGTH - || attachment.getDecimals() < 0 || attachment.getDecimals() > 8 - || attachment.getQuantityQNT() <= 0 - || attachment.getQuantityQNT() > Constants.MAX_ASSET_QUANTITY_QNT - ) { - throw new NxtException.NotValidException("Invalid asset issuance: " + attachment.getJSONObject()); - } - String normalizedName = attachment.getName().toLowerCase(); - for (int i = 0; i < normalizedName.length(); i++) { - if (Constants.ALPHABET.indexOf(normalizedName.charAt(i)) < 0) { - throw new NxtException.NotValidException("Invalid asset name: " + normalizedName); - } - } - } - - @Override - public boolean hasRecipient() { - return false; - } - - }; - - public static final TransactionType ASSET_TRANSFER = new ColoredCoins() { - - @Override - public final byte getSubtype() { - return TransactionType.SUBTYPE_COLORED_COINS_ASSET_TRANSFER; - } - - @Override - Attachment.ColoredCoinsAssetTransfer parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { - return new Attachment.ColoredCoinsAssetTransfer(buffer, transactionVersion); - } - - @Override - Attachment.ColoredCoinsAssetTransfer parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { - return new Attachment.ColoredCoinsAssetTransfer(attachmentData); - } - - @Override - boolean applyAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { - Attachment.ColoredCoinsAssetTransfer attachment = (Attachment.ColoredCoinsAssetTransfer) transaction.getAttachment(); - long unconfirmedAssetBalance = senderAccount.getUnconfirmedAssetBalanceQNT(attachment.getAssetId()); - if (unconfirmedAssetBalance >= 0 && unconfirmedAssetBalance >= attachment.getQuantityQNT()) { - senderAccount.addToUnconfirmedAssetBalanceQNT(attachment.getAssetId(), -attachment.getQuantityQNT()); - return true; - } - return false; - } - - @Override - void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { - Attachment.ColoredCoinsAssetTransfer attachment = (Attachment.ColoredCoinsAssetTransfer) transaction.getAttachment(); - senderAccount.addToAssetBalanceQNT(attachment.getAssetId(), -attachment.getQuantityQNT()); - recipientAccount.addToAssetAndUnconfirmedAssetBalanceQNT(attachment.getAssetId(), attachment.getQuantityQNT()); - AssetTransfer.addAssetTransfer(transaction, attachment); - } - - @Override - void undoAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { - Attachment.ColoredCoinsAssetTransfer attachment = (Attachment.ColoredCoinsAssetTransfer) transaction.getAttachment(); - senderAccount.addToUnconfirmedAssetBalanceQNT(attachment.getAssetId(), attachment.getQuantityQNT()); - } - - @Override - void validateAttachment(Transaction transaction) throws NxtException.ValidationException { - Attachment.ColoredCoinsAssetTransfer attachment = (Attachment.ColoredCoinsAssetTransfer)transaction.getAttachment(); - if (transaction.getAmountNQT() != 0 - || attachment.getComment() != null && attachment.getComment().length() > Constants.MAX_ASSET_TRANSFER_COMMENT_LENGTH - || attachment.getAssetId() == 0) { - throw new NxtException.NotValidException("Invalid asset transfer amount or comment: " + attachment.getJSONObject()); - } - if (transaction.getVersion() > 0 && attachment.getComment() != null) { - throw new NxtException.NotValidException("Asset transfer comments no longer allowed, use message " + - "or encrypted message appendix instead"); - } - Asset asset = Asset.getAsset(attachment.getAssetId()); - if (attachment.getQuantityQNT() <= 0 || (asset != null && attachment.getQuantityQNT() > asset.getQuantityQNT())) { - throw new NxtException.NotValidException("Invalid asset transfer asset or quantity: " + attachment.getJSONObject()); - } - if (asset == null) { - throw new NxtException.NotCurrentlyValidException("Asset " + Convert.toUnsignedLong(attachment.getAssetId()) + - " does not exist yet"); - } - } - - @Override - public boolean hasRecipient() { - return true; - } - - }; - - abstract static class ColoredCoinsOrderPlacement extends ColoredCoins { - - @Override - final void validateAttachment(Transaction transaction) throws NxtException.ValidationException { - Attachment.ColoredCoinsOrderPlacement attachment = (Attachment.ColoredCoinsOrderPlacement)transaction.getAttachment(); - if (attachment.getPriceNQT() <= 0 || attachment.getPriceNQT() > Constants.MAX_BALANCE_NQT - || attachment.getAssetId() == 0) { - throw new NxtException.NotValidException("Invalid asset order placement: " + attachment.getJSONObject()); - } - Asset asset = Asset.getAsset(attachment.getAssetId()); - if (attachment.getQuantityQNT() <= 0 || (asset != null && attachment.getQuantityQNT() > asset.getQuantityQNT())) { - throw new NxtException.NotValidException("Invalid asset order placement asset or quantity: " + attachment.getJSONObject()); - } - if (asset == null) { - throw new NxtException.NotCurrentlyValidException("Asset " + Convert.toUnsignedLong(attachment.getAssetId()) + - " does not exist yet"); - } - } - - @Override - final public boolean hasRecipient() { - return false; - } - - } - - public static final TransactionType ASK_ORDER_PLACEMENT = new ColoredCoins.ColoredCoinsOrderPlacement() { - - @Override - public final byte getSubtype() { - return TransactionType.SUBTYPE_COLORED_COINS_ASK_ORDER_PLACEMENT; - } - - @Override - Attachment.ColoredCoinsAskOrderPlacement parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { - return new Attachment.ColoredCoinsAskOrderPlacement(buffer, transactionVersion); - } - - @Override - Attachment.ColoredCoinsAskOrderPlacement parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { - return new Attachment.ColoredCoinsAskOrderPlacement(attachmentData); - } - - @Override - boolean applyAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { - Attachment.ColoredCoinsAskOrderPlacement attachment = (Attachment.ColoredCoinsAskOrderPlacement) transaction.getAttachment(); - long unconfirmedAssetBalance = senderAccount.getUnconfirmedAssetBalanceQNT(attachment.getAssetId()); - if (unconfirmedAssetBalance >= 0 && unconfirmedAssetBalance >= attachment.getQuantityQNT()) { - senderAccount.addToUnconfirmedAssetBalanceQNT(attachment.getAssetId(), -attachment.getQuantityQNT()); - return true; - } - return false; - } - - @Override - void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { - Attachment.ColoredCoinsAskOrderPlacement attachment = (Attachment.ColoredCoinsAskOrderPlacement) transaction.getAttachment(); - if (Asset.getAsset(attachment.getAssetId()) != null) { - Order.Ask.addOrder(transaction, attachment); - } - } - - @Override - void undoAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { - Attachment.ColoredCoinsAskOrderPlacement attachment = (Attachment.ColoredCoinsAskOrderPlacement) transaction.getAttachment(); - senderAccount.addToUnconfirmedAssetBalanceQNT(attachment.getAssetId(), attachment.getQuantityQNT()); - } - - }; - - public final static TransactionType BID_ORDER_PLACEMENT = new ColoredCoins.ColoredCoinsOrderPlacement() { - - @Override - public final byte getSubtype() { - return TransactionType.SUBTYPE_COLORED_COINS_BID_ORDER_PLACEMENT; - } - - @Override - Attachment.ColoredCoinsBidOrderPlacement parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { - return new Attachment.ColoredCoinsBidOrderPlacement(buffer, transactionVersion); - } - - @Override - Attachment.ColoredCoinsBidOrderPlacement parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { - return new Attachment.ColoredCoinsBidOrderPlacement(attachmentData); - } - - @Override - boolean applyAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { - Attachment.ColoredCoinsBidOrderPlacement attachment = (Attachment.ColoredCoinsBidOrderPlacement) transaction.getAttachment(); - if (senderAccount.getUnconfirmedBalanceNQT() >= Convert.safeMultiply(attachment.getQuantityQNT(), attachment.getPriceNQT())) { - senderAccount.addToUnconfirmedBalanceNQT(-Convert.safeMultiply(attachment.getQuantityQNT(), attachment.getPriceNQT())); - return true; - } - return false; - } - - @Override - void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { - Attachment.ColoredCoinsBidOrderPlacement attachment = (Attachment.ColoredCoinsBidOrderPlacement) transaction.getAttachment(); - if (Asset.getAsset(attachment.getAssetId()) != null) { - Order.Bid.addOrder(transaction, attachment); - } - } - - @Override - void undoAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { - Attachment.ColoredCoinsBidOrderPlacement attachment = (Attachment.ColoredCoinsBidOrderPlacement) transaction.getAttachment(); - senderAccount.addToUnconfirmedBalanceNQT(Convert.safeMultiply(attachment.getQuantityQNT(), attachment.getPriceNQT())); - } - - }; - - abstract static class ColoredCoinsOrderCancellation extends ColoredCoins { - - @Override - final boolean applyAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { - return true; - } - - @Override - final void undoAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { - } - - @Override - public boolean hasRecipient() { - return false; - } - - } - - public static final TransactionType ASK_ORDER_CANCELLATION = new ColoredCoins.ColoredCoinsOrderCancellation() { - - @Override - public final byte getSubtype() { - return TransactionType.SUBTYPE_COLORED_COINS_ASK_ORDER_CANCELLATION; - } - - @Override - Attachment.ColoredCoinsAskOrderCancellation parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { - return new Attachment.ColoredCoinsAskOrderCancellation(buffer, transactionVersion); - } - - @Override - Attachment.ColoredCoinsAskOrderCancellation parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { - return new Attachment.ColoredCoinsAskOrderCancellation(attachmentData); - } - - @Override - void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { - Attachment.ColoredCoinsAskOrderCancellation attachment = (Attachment.ColoredCoinsAskOrderCancellation) transaction.getAttachment(); - Order order = Order.Ask.getAskOrder(attachment.getOrderId()); - Order.Ask.removeOrder(attachment.getOrderId()); - if (order != null) { - senderAccount.addToUnconfirmedAssetBalanceQNT(order.getAssetId(), order.getQuantityQNT()); - } - } - - @Override - void validateAttachment(Transaction transaction) throws NxtException.ValidationException { - Attachment.ColoredCoinsAskOrderCancellation attachment = (Attachment.ColoredCoinsAskOrderCancellation) transaction.getAttachment(); - Order ask = Order.Ask.getAskOrder(attachment.getOrderId()); - if(ask == null) { - throw new NxtException.NotCurrentlyValidException("Invalid ask order: " + Convert.toUnsignedLong(attachment.getOrderId())); - } - if(ask.getAccountId() != transaction.getSenderId()) { - throw new NxtException.NotValidException("Order " + Convert.toUnsignedLong(attachment.getOrderId()) + " was created by account " - + Convert.toUnsignedLong(ask.getAccountId())); - } - } - - }; - - public static final TransactionType BID_ORDER_CANCELLATION = new ColoredCoins.ColoredCoinsOrderCancellation() { - - @Override - public final byte getSubtype() { - return TransactionType.SUBTYPE_COLORED_COINS_BID_ORDER_CANCELLATION; - } - - @Override - Attachment.ColoredCoinsBidOrderCancellation parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { - return new Attachment.ColoredCoinsBidOrderCancellation(buffer, transactionVersion); - } - - @Override - Attachment.ColoredCoinsBidOrderCancellation parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { - return new Attachment.ColoredCoinsBidOrderCancellation(attachmentData); - } - - @Override - void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { - Attachment.ColoredCoinsBidOrderCancellation attachment = (Attachment.ColoredCoinsBidOrderCancellation) transaction.getAttachment(); - Order order = Order.Bid.getBidOrder(attachment.getOrderId()); - Order.Bid.removeOrder(attachment.getOrderId()); - if (order != null) { - senderAccount.addToUnconfirmedBalanceNQT(Convert.safeMultiply(order.getQuantityQNT(), order.getPriceNQT())); - } - } - - @Override - void validateAttachment(Transaction transaction) throws NxtException.ValidationException { - Attachment.ColoredCoinsBidOrderCancellation attachment = (Attachment.ColoredCoinsBidOrderCancellation) transaction.getAttachment(); - Order bid = Order.Bid.getBidOrder(attachment.getOrderId()); - if(bid == null) { - throw new NxtException.NotCurrentlyValidException("Invalid bid order: " + Convert.toUnsignedLong(attachment.getOrderId())); - } - if(bid.getAccountId() != transaction.getSenderId()) { - throw new NxtException.NotValidException("Order " + Convert.toUnsignedLong(attachment.getOrderId()) + " was created by account " - + Convert.toUnsignedLong(bid.getAccountId())); - } - } - - }; - } - - public static abstract class DigitalGoods extends TransactionType { - - private DigitalGoods() { - } - - @Override - public final byte getType() { - return TransactionType.TYPE_DIGITAL_GOODS; - } - - @Override - boolean applyAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { - return true; - } - - @Override - void undoAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { - } - - @Override - final void validateAttachment(Transaction transaction) throws NxtException.ValidationException { - if (Nxt.getBlockchain().getLastBlock().getHeight() < Constants.DIGITAL_GOODS_STORE_BLOCK) { - throw new NxtException.NotYetEnabledException("Digital goods listing not yet enabled at height " + Nxt.getBlockchain().getLastBlock().getHeight()); - } - if (transaction.getAmountNQT() != 0) { - throw new NxtException.NotValidException("Invalid digital goods transaction"); - } - doValidateAttachment(transaction); - } - - abstract void doValidateAttachment(Transaction transaction) throws NxtException.ValidationException; - - - public static final TransactionType LISTING = new DigitalGoods() { - - @Override - public final byte getSubtype() { - return TransactionType.SUBTYPE_DIGITAL_GOODS_LISTING; - } - - @Override - Attachment.DigitalGoodsListing parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { - return new Attachment.DigitalGoodsListing(buffer, transactionVersion); - } - - @Override - Attachment.DigitalGoodsListing parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { - return new Attachment.DigitalGoodsListing(attachmentData); - } - - @Override - void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { - Attachment.DigitalGoodsListing attachment = (Attachment.DigitalGoodsListing) transaction.getAttachment(); - DigitalGoodsStore.listGoods(transaction, attachment); - } - - @Override - void doValidateAttachment(Transaction transaction) throws NxtException.ValidationException { - Attachment.DigitalGoodsListing attachment = (Attachment.DigitalGoodsListing) transaction.getAttachment(); - if (attachment.getName().length() == 0 - || attachment.getName().length() > Constants.MAX_DGS_LISTING_NAME_LENGTH - || attachment.getDescription().length() > Constants.MAX_DGS_LISTING_DESCRIPTION_LENGTH - || attachment.getTags().length() > Constants.MAX_DGS_LISTING_TAGS_LENGTH - || attachment.getQuantity() < 0 || attachment.getQuantity() > Constants.MAX_DGS_LISTING_QUANTITY - || attachment.getPriceNQT() <= 0 || attachment.getPriceNQT() > Constants.MAX_BALANCE_NQT) { - throw new NxtException.NotValidException("Invalid digital goods listing: " + attachment.getJSONObject()); - } - } - - @Override - public boolean hasRecipient() { - return false; - } - - }; - - public static final TransactionType DELISTING = new DigitalGoods() { - - @Override - public final byte getSubtype() { - return TransactionType.SUBTYPE_DIGITAL_GOODS_DELISTING; - } - - @Override - Attachment.DigitalGoodsDelisting parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { - return new Attachment.DigitalGoodsDelisting(buffer, transactionVersion); - } - - @Override - Attachment.DigitalGoodsDelisting parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { - return new Attachment.DigitalGoodsDelisting(attachmentData); - } - - @Override - void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { - Attachment.DigitalGoodsDelisting attachment = (Attachment.DigitalGoodsDelisting) transaction.getAttachment(); - DigitalGoodsStore.delistGoods(attachment.getGoodsId()); - } - - @Override - void doValidateAttachment(Transaction transaction) throws NxtException.ValidationException { - Attachment.DigitalGoodsDelisting attachment = (Attachment.DigitalGoodsDelisting) transaction.getAttachment(); - DigitalGoodsStore.Goods goods = DigitalGoodsStore.getGoods(attachment.getGoodsId()); - if (goods != null && transaction.getSenderId() != goods.getSellerId()) { - throw new NxtException.NotValidException("Invalid digital goods delisting - seller is different: " + attachment.getJSONObject()); - } - if (goods == null || goods.isDelisted()) { - throw new NxtException.NotCurrentlyValidException("Goods " + Convert.toUnsignedLong(attachment.getGoodsId()) + - "not yet listed or already delisted"); - } - } - - @Override - boolean isDuplicate(Transaction transaction, Map> duplicates) { - Attachment.DigitalGoodsDelisting attachment = (Attachment.DigitalGoodsDelisting) transaction.getAttachment(); - return isDuplicate(DigitalGoods.DELISTING, Convert.toUnsignedLong(attachment.getGoodsId()), duplicates); - } - - @Override - public boolean hasRecipient() { - return false; - } - - }; - - public static final TransactionType PRICE_CHANGE = new DigitalGoods() { - - @Override - public final byte getSubtype() { - return TransactionType.SUBTYPE_DIGITAL_GOODS_PRICE_CHANGE; - } - - @Override - Attachment.DigitalGoodsPriceChange parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { - return new Attachment.DigitalGoodsPriceChange(buffer, transactionVersion); - } - - @Override - Attachment.DigitalGoodsPriceChange parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { - return new Attachment.DigitalGoodsPriceChange(attachmentData); - } - - @Override - void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { - Attachment.DigitalGoodsPriceChange attachment = (Attachment.DigitalGoodsPriceChange) transaction.getAttachment(); - DigitalGoodsStore.changePrice(attachment.getGoodsId(), attachment.getPriceNQT()); - } - - @Override - void doValidateAttachment(Transaction transaction) throws NxtException.ValidationException { - Attachment.DigitalGoodsPriceChange attachment = (Attachment.DigitalGoodsPriceChange) transaction.getAttachment(); - DigitalGoodsStore.Goods goods = DigitalGoodsStore.getGoods(attachment.getGoodsId()); - if (attachment.getPriceNQT() <= 0 || attachment.getPriceNQT() > Constants.MAX_BALANCE_NQT - || (goods != null && transaction.getSenderId() != goods.getSellerId())) { - throw new NxtException.NotValidException("Invalid digital goods price change: " + attachment.getJSONObject()); - } - if (goods == null || goods.isDelisted()) { - throw new NxtException.NotCurrentlyValidException("Goods " + Convert.toUnsignedLong(attachment.getGoodsId()) + - "not yet listed or already delisted"); - } - } - - @Override - boolean isDuplicate(Transaction transaction, Map> duplicates) { - Attachment.DigitalGoodsPriceChange attachment = (Attachment.DigitalGoodsPriceChange) transaction.getAttachment(); - // not a bug, uniqueness is based on DigitalGoods.DELISTING - return isDuplicate(DigitalGoods.DELISTING, Convert.toUnsignedLong(attachment.getGoodsId()), duplicates); - } - - @Override - public boolean hasRecipient() { - return false; - } - - }; - - public static final TransactionType QUANTITY_CHANGE = new DigitalGoods() { - - @Override - public final byte getSubtype() { - return TransactionType.SUBTYPE_DIGITAL_GOODS_QUANTITY_CHANGE; - } - - @Override - Attachment.DigitalGoodsQuantityChange parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { - return new Attachment.DigitalGoodsQuantityChange(buffer, transactionVersion); - } - - @Override - Attachment.DigitalGoodsQuantityChange parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { - return new Attachment.DigitalGoodsQuantityChange(attachmentData); - } - - @Override - void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { - Attachment.DigitalGoodsQuantityChange attachment = (Attachment.DigitalGoodsQuantityChange) transaction.getAttachment(); - DigitalGoodsStore.changeQuantity(attachment.getGoodsId(), attachment.getDeltaQuantity()); - } - - @Override - void doValidateAttachment(Transaction transaction) throws NxtException.ValidationException { - Attachment.DigitalGoodsQuantityChange attachment = (Attachment.DigitalGoodsQuantityChange) transaction.getAttachment(); - DigitalGoodsStore.Goods goods = DigitalGoodsStore.getGoods(attachment.getGoodsId()); - if (attachment.getDeltaQuantity() < -Constants.MAX_DGS_LISTING_QUANTITY - || attachment.getDeltaQuantity() > Constants.MAX_DGS_LISTING_QUANTITY - || (goods != null && transaction.getSenderId() != goods.getSellerId())) { - throw new NxtException.NotValidException("Invalid digital goods quantity change: " + attachment.getJSONObject()); - } - if (goods == null || goods.isDelisted()) { - throw new NxtException.NotCurrentlyValidException("Goods " + Convert.toUnsignedLong(attachment.getGoodsId()) + - "not yet listed or already delisted"); - } - } - - @Override - boolean isDuplicate(Transaction transaction, Map> duplicates) { - Attachment.DigitalGoodsQuantityChange attachment = (Attachment.DigitalGoodsQuantityChange) transaction.getAttachment(); - // not a bug, uniqueness is based on DigitalGoods.DELISTING - return isDuplicate(DigitalGoods.DELISTING, Convert.toUnsignedLong(attachment.getGoodsId()), duplicates); - } - - @Override - public boolean hasRecipient() { - return false; - } - - }; - - public static final TransactionType PURCHASE = new DigitalGoods() { - - @Override - public final byte getSubtype() { - return TransactionType.SUBTYPE_DIGITAL_GOODS_PURCHASE; - } - - @Override - Attachment.DigitalGoodsPurchase parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { - return new Attachment.DigitalGoodsPurchase(buffer, transactionVersion); - } - - @Override - Attachment.DigitalGoodsPurchase parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { - return new Attachment.DigitalGoodsPurchase(attachmentData); - } - - @Override - boolean applyAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { - Attachment.DigitalGoodsPurchase attachment = (Attachment.DigitalGoodsPurchase) transaction.getAttachment(); - if (senderAccount.getUnconfirmedBalanceNQT() >= Convert.safeMultiply(attachment.getQuantity(), attachment.getPriceNQT())) { - senderAccount.addToUnconfirmedBalanceNQT(-Convert.safeMultiply(attachment.getQuantity(), attachment.getPriceNQT())); - return true; - } - return false; - } - - @Override - void undoAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { - Attachment.DigitalGoodsPurchase attachment = (Attachment.DigitalGoodsPurchase) transaction.getAttachment(); - senderAccount.addToUnconfirmedBalanceNQT(Convert.safeMultiply(attachment.getQuantity(), attachment.getPriceNQT())); - } - - @Override - void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { - Attachment.DigitalGoodsPurchase attachment = (Attachment.DigitalGoodsPurchase) transaction.getAttachment(); - DigitalGoodsStore.purchase(transaction, attachment); - } - - @Override - void doValidateAttachment(Transaction transaction) throws NxtException.ValidationException { - Attachment.DigitalGoodsPurchase attachment = (Attachment.DigitalGoodsPurchase) transaction.getAttachment(); - DigitalGoodsStore.Goods goods = DigitalGoodsStore.getGoods(attachment.getGoodsId()); - if (attachment.getQuantity() <= 0 || attachment.getQuantity() > Constants.MAX_DGS_LISTING_QUANTITY - || attachment.getPriceNQT() <= 0 || attachment.getPriceNQT() > Constants.MAX_BALANCE_NQT - || (goods != null && goods.getSellerId() != transaction.getRecipientId())) { - throw new NxtException.NotValidException("Invalid digital goods purchase: " + attachment.getJSONObject()); - } - if (transaction.getEncryptedMessage() != null && ! transaction.getEncryptedMessage().isText()) { - throw new NxtException.NotValidException("Only text encrypted messages allowed"); - } - if (goods == null || goods.isDelisted()) { - throw new NxtException.NotCurrentlyValidException("Goods " + Convert.toUnsignedLong(attachment.getGoodsId()) + - "not yet listed or already delisted"); - } - if (attachment.getQuantity() > goods.getQuantity() || attachment.getPriceNQT() != goods.getPriceNQT()) { - throw new NxtException.NotCurrentlyValidException("Goods price or quantity changed: " + attachment.getJSONObject()); - } - if (attachment.getDeliveryDeadlineTimestamp() <= Nxt.getBlockchain().getLastBlock().getTimestamp()) { - throw new NxtException.NotCurrentlyValidException("Delivery deadline has already expired: " + attachment.getDeliveryDeadlineTimestamp()); - } - } - - @Override - public boolean hasRecipient() { - return true; - } - - }; - - public static final TransactionType DELIVERY = new DigitalGoods() { - - @Override - public final byte getSubtype() { - return TransactionType.SUBTYPE_DIGITAL_GOODS_DELIVERY; - } - - @Override - Attachment.DigitalGoodsDelivery parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { - return new Attachment.DigitalGoodsDelivery(buffer, transactionVersion); - } - - @Override - Attachment.DigitalGoodsDelivery parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { - return new Attachment.DigitalGoodsDelivery(attachmentData); - } - - @Override - void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { - Attachment.DigitalGoodsDelivery attachment = (Attachment.DigitalGoodsDelivery)transaction.getAttachment(); - DigitalGoodsStore.deliver(transaction, attachment); - } - - @Override - void doValidateAttachment(Transaction transaction) throws NxtException.ValidationException { - Attachment.DigitalGoodsDelivery attachment = (Attachment.DigitalGoodsDelivery) transaction.getAttachment(); - DigitalGoodsStore.Purchase purchase = DigitalGoodsStore.getPendingPurchase(attachment.getPurchaseId()); - if (attachment.getGoods().getData().length > Constants.MAX_DGS_GOODS_LENGTH - || attachment.getGoods().getData().length == 0 - || attachment.getGoods().getNonce().length != 32 - || attachment.getDiscountNQT() < 0 || attachment.getDiscountNQT() > Constants.MAX_BALANCE_NQT - || (purchase != null && - (purchase.getBuyerId() != transaction.getRecipientId() - || transaction.getSenderId() != purchase.getSellerId() - || attachment.getDiscountNQT() > Convert.safeMultiply(purchase.getPriceNQT(), purchase.getQuantity())))) { - throw new NxtException.NotValidException("Invalid digital goods delivery: " + attachment.getJSONObject()); - } - if (purchase == null || purchase.getEncryptedGoods() != null) { - throw new NxtException.NotCurrentlyValidException("Purchase does not exist yet, or already delivered: " - + attachment.getJSONObject()); - } - } - - @Override - boolean isDuplicate(Transaction transaction, Map> duplicates) { - Attachment.DigitalGoodsDelivery attachment = (Attachment.DigitalGoodsDelivery) transaction.getAttachment(); - return isDuplicate(DigitalGoods.DELIVERY, Convert.toUnsignedLong(attachment.getPurchaseId()), duplicates); - } - - @Override - public boolean hasRecipient() { - return true; - } - - }; - - public static final TransactionType FEEDBACK = new DigitalGoods() { - - @Override - public final byte getSubtype() { - return TransactionType.SUBTYPE_DIGITAL_GOODS_FEEDBACK; - } - - @Override - Attachment.DigitalGoodsFeedback parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { - return new Attachment.DigitalGoodsFeedback(buffer, transactionVersion); - } - - @Override - Attachment.DigitalGoodsFeedback parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { - return new Attachment.DigitalGoodsFeedback(attachmentData); - } - - @Override - void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { - Attachment.DigitalGoodsFeedback attachment = (Attachment.DigitalGoodsFeedback)transaction.getAttachment(); - DigitalGoodsStore.feedback(attachment.getPurchaseId(), transaction.getEncryptedMessage(), transaction.getMessage()); - } - - @Override - void doValidateAttachment(Transaction transaction) throws NxtException.ValidationException { - Attachment.DigitalGoodsFeedback attachment = (Attachment.DigitalGoodsFeedback) transaction.getAttachment(); - DigitalGoodsStore.Purchase purchase = DigitalGoodsStore.getPurchase(attachment.getPurchaseId()); - if (purchase != null && - (purchase.getSellerId() != transaction.getRecipientId() - || transaction.getSenderId() != purchase.getBuyerId())) { - throw new NxtException.NotValidException("Invalid digital goods feedback: " + attachment.getJSONObject()); - } - if (transaction.getEncryptedMessage() == null && transaction.getMessage() == null) { - throw new NxtException.NotValidException("Missing feedback message"); - } - if (transaction.getEncryptedMessage() != null && ! transaction.getEncryptedMessage().isText()) { - throw new NxtException.NotValidException("Only text encrypted messages allowed"); - } - if (transaction.getMessage() != null && ! transaction.getMessage().isText()) { - throw new NxtException.NotValidException("Only text public messages allowed"); - } - if (purchase == null || purchase.getEncryptedGoods() == null) { - throw new NxtException.NotCurrentlyValidException("Purchase does not exist yet or not yet delivered"); - } - } - - @Override - boolean isDuplicate(Transaction transaction, Map> duplicates) { - Attachment.DigitalGoodsFeedback attachment = (Attachment.DigitalGoodsFeedback) transaction.getAttachment(); - return isDuplicate(DigitalGoods.FEEDBACK, Convert.toUnsignedLong(attachment.getPurchaseId()), duplicates); - } - - @Override - public boolean hasRecipient() { - return true; - } - - }; - - public static final TransactionType REFUND = new DigitalGoods() { - - @Override - public final byte getSubtype() { - return TransactionType.SUBTYPE_DIGITAL_GOODS_REFUND; - } - - @Override - Attachment.DigitalGoodsRefund parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { - return new Attachment.DigitalGoodsRefund(buffer, transactionVersion); - } - - @Override - Attachment.DigitalGoodsRefund parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { - return new Attachment.DigitalGoodsRefund(attachmentData); - } - - @Override - boolean applyAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { - Attachment.DigitalGoodsRefund attachment = (Attachment.DigitalGoodsRefund) transaction.getAttachment(); - if (senderAccount.getUnconfirmedBalanceNQT() >= attachment.getRefundNQT()) { - senderAccount.addToUnconfirmedBalanceNQT(-attachment.getRefundNQT()); - return true; - } - return false; - } - - @Override - void undoAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { - Attachment.DigitalGoodsRefund attachment = (Attachment.DigitalGoodsRefund) transaction.getAttachment(); - senderAccount.addToUnconfirmedBalanceNQT(attachment.getRefundNQT()); - } - - @Override - void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { - Attachment.DigitalGoodsRefund attachment = (Attachment.DigitalGoodsRefund) transaction.getAttachment(); - DigitalGoodsStore.refund(transaction.getSenderId(), attachment.getPurchaseId(), - attachment.getRefundNQT(), transaction.getEncryptedMessage()); - } - - @Override - void doValidateAttachment(Transaction transaction) throws NxtException.ValidationException { - Attachment.DigitalGoodsRefund attachment = (Attachment.DigitalGoodsRefund) transaction.getAttachment(); - DigitalGoodsStore.Purchase purchase = DigitalGoodsStore.getPurchase(attachment.getPurchaseId()); - if (attachment.getRefundNQT() < 0 || attachment.getRefundNQT() > Constants.MAX_BALANCE_NQT - || (purchase != null && - (purchase.getBuyerId() != transaction.getRecipientId() - || transaction.getSenderId() != purchase.getSellerId()))) { - throw new NxtException.NotValidException("Invalid digital goods refund: " + attachment.getJSONObject()); - } - if (transaction.getEncryptedMessage() != null && ! transaction.getEncryptedMessage().isText()) { - throw new NxtException.NotValidException("Only text encrypted messages allowed"); - } - if (purchase == null || purchase.getEncryptedGoods() == null || purchase.getRefundNQT() != 0) { - throw new NxtException.NotCurrentlyValidException("Purchase does not exist or is not delivered or is already refunded"); - } - } - - @Override - boolean isDuplicate(Transaction transaction, Map> duplicates) { - Attachment.DigitalGoodsRefund attachment = (Attachment.DigitalGoodsRefund) transaction.getAttachment(); - return isDuplicate(DigitalGoods.REFUND, Convert.toUnsignedLong(attachment.getPurchaseId()), duplicates); - } - - @Override - public boolean hasRecipient() { - return true; - } - - }; - - } - - public static abstract class AccountControl extends TransactionType { - - private AccountControl() { - } - - @Override - public final byte getType() { - return TransactionType.TYPE_ACCOUNT_CONTROL; - } - - @Override - final boolean applyAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { - return true; - } - - @Override - final void undoAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { - } - - public static final TransactionType EFFECTIVE_BALANCE_LEASING = new AccountControl() { - - @Override - public final byte getSubtype() { - return TransactionType.SUBTYPE_ACCOUNT_CONTROL_EFFECTIVE_BALANCE_LEASING; - } - - @Override - Attachment.AccountControlEffectiveBalanceLeasing parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { - return new Attachment.AccountControlEffectiveBalanceLeasing(buffer, transactionVersion); - } - - @Override - Attachment.AccountControlEffectiveBalanceLeasing parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { - return new Attachment.AccountControlEffectiveBalanceLeasing(attachmentData); - } - - @Override - void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { - Attachment.AccountControlEffectiveBalanceLeasing attachment = (Attachment.AccountControlEffectiveBalanceLeasing) transaction.getAttachment(); - Account.getAccount(transaction.getSenderId()).leaseEffectiveBalance(transaction.getRecipientId(), attachment.getPeriod()); - } - - @Override - void validateAttachment(Transaction transaction) throws NxtException.ValidationException { - Attachment.AccountControlEffectiveBalanceLeasing attachment = (Attachment.AccountControlEffectiveBalanceLeasing)transaction.getAttachment(); - Account recipientAccount = Account.getAccount(transaction.getRecipientId()); - if (transaction.getSenderId() == transaction.getRecipientId() - || transaction.getAmountNQT() != 0 - || attachment.getPeriod() < 1440) { - throw new NxtException.NotValidException("Invalid effective balance leasing: " - + transaction.getJSONObject() + " transaction " + transaction.getStringId()); - } - if (recipientAccount == null - || (recipientAccount.getPublicKey() == null && ! transaction.getStringId().equals("5081403377391821646"))) { - throw new NxtException.NotCurrentlyValidException("Invalid effective balance leasing: " - + " recipient account " + transaction.getRecipientId() + " not found or no public key published"); - } - } - - @Override - public boolean hasRecipient() { - return true; - } - - }; - - } - - public static abstract class BurstMining extends TransactionType { - - private BurstMining() {} - - @Override - public final byte getType() { - return TransactionType.TYPE_BURST_MINING; - } - - @Override - final boolean applyAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { - return true; - } - - @Override - final void undoAttachmentUnconfirmed(Transaction transaction, Account senderAccount) {} - - public static final TransactionType REWARD_RECIPIENT_ASSIGNMENT = new BurstMining() { - - @Override - public final byte getSubtype() { - return TransactionType.SUBTYPE_BURST_MINING_REWARD_RECIPIENT_ASSIGNMENT; - } - - @Override - Attachment.BurstMiningRewardRecipientAssignment parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { - return new Attachment.BurstMiningRewardRecipientAssignment(buffer, transactionVersion); - } - - @Override - Attachment.BurstMiningRewardRecipientAssignment parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { - return new Attachment.BurstMiningRewardRecipientAssignment(attachmentData); - } - - @Override - void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { - senderAccount.setRewardRecipientAssignment(recipientAccount.getId()); - } - - @Override - boolean isDuplicate(Transaction transaction, Map> duplicates) { - if(Nxt.getBlockchain().getHeight() < Constants.DIGITAL_GOODS_STORE_BLOCK) { - return false; // sync fails after 7007 without this - } - return isDuplicate(BurstMining.REWARD_RECIPIENT_ASSIGNMENT, Convert.toUnsignedLong(transaction.getSenderId()), duplicates); - } - - @Override - void validateAttachment(Transaction transaction) throws NxtException.ValidationException { - long height = Nxt.getBlockchain().getLastBlock().getHeight() + 1; - Account sender = Account.getAccount(transaction.getSenderId()); - Account.RewardRecipientAssignment rewardAssignment = sender.getRewardRecipientAssignment(); - if(rewardAssignment != null && rewardAssignment.getFromHeight() >= height) { - throw new NxtException.NotValidException("Cannot reassign reward recipient before previous goes into effect: " + transaction.getJSONObject()); - } - Account recip = Account.getAccount(transaction.getRecipientId()); - if(recip == null || recip.getPublicKey() == null) { - throw new NxtException.NotValidException("Reward recipient must have public key saved in blockchain: " + transaction.getJSONObject()); - } - if(transaction.getAmountNQT() != 0 || transaction.getFeeNQT() != Constants.ONE_NXT) { - throw new NxtException.NotValidException("Reward recipient assisnment transaction must have 0 send amount and 1 fee: " + transaction.getJSONObject()); - } - if(height < Constants.BURST_REWARD_RECIPIENT_ASSIGNMENT_START_BLOCK) { - throw new NxtException.NotCurrentlyValidException("Reward recipient assignment not allowed before block " + Constants.BURST_REWARD_RECIPIENT_ASSIGNMENT_START_BLOCK); - } - } - - @Override - public boolean hasRecipient() { - return true; - } - }; - } - - public static abstract class AdvancedPayment extends TransactionType { - - private AdvancedPayment() {} - - @Override - public final byte getType() { - return TransactionType.TYPE_ADVANCED_PAYMENT; - } - - public final static TransactionType ESCROW_CREATION = new AdvancedPayment() { - - @Override - public final byte getSubtype() { - return TransactionType.SUBTYPE_ADVANCED_PAYMENT_ESCROW_CREATION; - } - - @Override - Attachment.AdvancedPaymentEscrowCreation parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { - return new Attachment.AdvancedPaymentEscrowCreation(buffer, transactionVersion); - } - - @Override - Attachment.AdvancedPaymentEscrowCreation parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { - return new Attachment.AdvancedPaymentEscrowCreation(attachmentData); - } - - @Override - final boolean applyAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { - Attachment.AdvancedPaymentEscrowCreation attachment = (Attachment.AdvancedPaymentEscrowCreation) transaction.getAttachment(); - Long totalAmountNQT = Convert.safeAdd(attachment.getAmountNQT(), attachment.getTotalSigners() * Constants.ONE_NXT); - if(senderAccount.getUnconfirmedBalanceNQT() < totalAmountNQT.longValue()) { - return false; - } - senderAccount.addToUnconfirmedBalanceNQT(-totalAmountNQT); - return true; - } - - @Override - final void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { - Attachment.AdvancedPaymentEscrowCreation attachment = (Attachment.AdvancedPaymentEscrowCreation) transaction.getAttachment(); - Long totalAmountNQT = Convert.safeAdd(attachment.getAmountNQT(), attachment.getTotalSigners() * Constants.ONE_NXT); - senderAccount.addToBalanceNQT(-totalAmountNQT); - Collection signers = attachment.getSigners(); - for(Long signer : signers) { - Account.addOrGetAccount(signer).addToBalanceAndUnconfirmedBalanceNQT(Constants.ONE_NXT); - } - Escrow.addEscrowTransaction(senderAccount, - recipientAccount, - transaction.getId(), - attachment.getAmountNQT(), - attachment.getRequiredSigners(), - attachment.getSigners(), - transaction.getTimestamp() + attachment.getDeadline(), - attachment.getDeadlineAction()); - } - - @Override - final void undoAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { - Attachment.AdvancedPaymentEscrowCreation attachment = (Attachment.AdvancedPaymentEscrowCreation) transaction.getAttachment(); - Long totalAmountNQT = Convert.safeAdd(attachment.getAmountNQT(), attachment.getTotalSigners() * Constants.ONE_NXT); - senderAccount.addToUnconfirmedBalanceNQT(totalAmountNQT); - } - - @Override - boolean isDuplicate(Transaction transaction, Map> duplicates) { - return false; - } - - @Override - void validateAttachment(Transaction transaction) throws NxtException.ValidationException { - Attachment.AdvancedPaymentEscrowCreation attachment = (Attachment.AdvancedPaymentEscrowCreation) transaction.getAttachment(); - Long totalAmountNQT = Convert.safeAdd(attachment.getAmountNQT(), transaction.getFeeNQT()); - if(transaction.getSenderId() == transaction.getRecipientId()) { - throw new NxtException.NotValidException("Escrow must have different sender and recipient"); - } - totalAmountNQT = Convert.safeAdd(totalAmountNQT, attachment.getTotalSigners() * Constants.ONE_NXT); - if(transaction.getAmountNQT() != 0) { - throw new NxtException.NotValidException("Transaction sent amount must be 0 for escrow"); - } - if(totalAmountNQT.compareTo(0L) < 0 || - totalAmountNQT.compareTo(Constants.MAX_BALANCE_NQT) > 0) - { - throw new NxtException.NotValidException("Invalid escrow creation amount"); - } - if(transaction.getFeeNQT() < Constants.ONE_NXT) { - throw new NxtException.NotValidException("Escrow transaction must have a fee at least 1 burst"); - } - if(attachment.getRequiredSigners() < 1 || attachment.getRequiredSigners() > 10) { - throw new NxtException.NotValidException("Escrow required signers much be 1 - 10"); - } - if(attachment.getRequiredSigners() > attachment.getTotalSigners()) { - throw new NxtException.NotValidException("Cannot have more required than signers on escrow"); - } - if(attachment.getTotalSigners() < 1 || attachment.getTotalSigners() > 10) { - throw new NxtException.NotValidException("Escrow transaction requires 1 - 10 signers"); - } - if(attachment.getDeadline() < 1 || attachment.getDeadline() > 7776000) { // max deadline 3 months - throw new NxtException.NotValidException("Escrow deadline must be 1 - 7776000 seconds"); - } - if(attachment.getDeadlineAction() == null || attachment.getDeadlineAction() == Escrow.DecisionType.UNDECIDED) { - throw new NxtException.NotValidException("Invalid deadline action for escrow"); - } - if(attachment.getSigners().contains(transaction.getSenderId()) || - attachment.getSigners().contains(transaction.getRecipientId())) { - throw new NxtException.NotValidException("Escrow sender and recipient cannot be signers"); - } - if(!Escrow.isEnabled()) { - throw new NxtException.NotYetEnabledException("Escrow not yet enabled"); - } - } - - @Override - final public boolean hasRecipient() { - return true; - } - }; - - public final static TransactionType ESCROW_SIGN = new AdvancedPayment() { - - @Override - public final byte getSubtype() { - return TransactionType.SUBTYPE_ADVANCED_PAYMENT_ESCROW_SIGN; - } - - @Override - Attachment.AdvancedPaymentEscrowSign parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { - return new Attachment.AdvancedPaymentEscrowSign(buffer, transactionVersion); - } - - @Override - Attachment.AdvancedPaymentEscrowSign parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { - return new Attachment.AdvancedPaymentEscrowSign(attachmentData); - } - - @Override - final boolean applyAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { - return true; - } - - @Override - final void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { - Attachment.AdvancedPaymentEscrowSign attachment = (Attachment.AdvancedPaymentEscrowSign) transaction.getAttachment(); - Escrow escrow = Escrow.getEscrowTransaction(attachment.getEscrowId()); - escrow.sign(senderAccount.getId(), attachment.getDecision()); - } - - @Override - final void undoAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { - } - - @Override - boolean isDuplicate(Transaction transaction, Map> duplicates) { - Attachment.AdvancedPaymentEscrowSign attachment = (Attachment.AdvancedPaymentEscrowSign) transaction.getAttachment(); - String uniqueString = Convert.toUnsignedLong(attachment.getEscrowId()) + ":" + - Convert.toUnsignedLong(transaction.getSenderId()); - return isDuplicate(AdvancedPayment.ESCROW_SIGN, uniqueString, duplicates); - } - - @Override - void validateAttachment(Transaction transaction) throws NxtException.ValidationException { - Attachment.AdvancedPaymentEscrowSign attachment = (Attachment.AdvancedPaymentEscrowSign) transaction.getAttachment(); - if(transaction.getAmountNQT() != 0 || transaction.getFeeNQT() != Constants.ONE_NXT) { - throw new NxtException.NotValidException("Escrow signing must have amount 0 and fee of 1"); - } - if(attachment.getEscrowId() == null || attachment.getDecision() == null) { - throw new NxtException.NotValidException("Escrow signing requires escrow id and decision set"); - } - Escrow escrow = Escrow.getEscrowTransaction(attachment.getEscrowId()); - if(escrow == null) { - throw new NxtException.NotValidException("Escrow transaction not found"); - } - if(!escrow.isIdSigner(transaction.getSenderId()) && - !escrow.getSenderId().equals(transaction.getSenderId()) && - !escrow.getRecipientId().equals(transaction.getSenderId())) { - throw new NxtException.NotValidException("Sender is not a participant in specified escrow"); - } - if(escrow.getSenderId().equals(transaction.getSenderId()) && attachment.getDecision() != Escrow.DecisionType.RELEASE) { - throw new NxtException.NotValidException("Escrow sender can only release"); - } - if(escrow.getRecipientId().equals(transaction.getSenderId()) && attachment.getDecision() != Escrow.DecisionType.REFUND) { - throw new NxtException.NotValidException("Escrow recipient can only refund"); - } - if(!Escrow.isEnabled()) { - throw new NxtException.NotYetEnabledException("Escrow not yet enabled"); - } - } - - @Override - final public boolean hasRecipient() { - return false; - } - }; - - public final static TransactionType ESCROW_RESULT = new AdvancedPayment() { - - @Override - public final byte getSubtype() { - return TransactionType.SUBTYPE_ADVANCED_PAYMENT_ESCROW_RESULT; - } - - @Override - Attachment.AdvancedPaymentEscrowResult parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { - return new Attachment.AdvancedPaymentEscrowResult(buffer, transactionVersion); - } - - @Override - Attachment.AdvancedPaymentEscrowResult parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { - return new Attachment.AdvancedPaymentEscrowResult(attachmentData); - } - - @Override - final boolean applyAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { - return false; - } - - @Override - final void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { - } - - @Override - final void undoAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { - } - - @Override - boolean isDuplicate(Transaction transaction, Map> duplicates) { - return true; - } - - @Override - void validateAttachment(Transaction transaction) throws NxtException.ValidationException { - throw new NxtException.NotValidException("Escrow result never validates"); - } - - @Override - final public boolean hasRecipient() { - return true; - } - - @Override - final public boolean isSigned() { - return false; - } - }; - - public final static TransactionType SUBSCRIPTION_SUBSCRIBE = new AdvancedPayment() { - - @Override - public final byte getSubtype() { - return TransactionType.SUBTYPE_ADVANCED_PAYMENT_SUBSCRIPTION_SUBSCRIBE; - } - - @Override - Attachment.AdvancedPaymentSubscriptionSubscribe parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { - return new Attachment.AdvancedPaymentSubscriptionSubscribe(buffer, transactionVersion); - } - - @Override - Attachment.AdvancedPaymentSubscriptionSubscribe parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { - return new Attachment.AdvancedPaymentSubscriptionSubscribe(attachmentData); - } - - @Override - final boolean applyAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { - return true; - } - - @Override - final void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { - Attachment.AdvancedPaymentSubscriptionSubscribe attachment = (Attachment.AdvancedPaymentSubscriptionSubscribe) transaction.getAttachment(); - Subscription.addSubscription(senderAccount, recipientAccount, transaction.getId(), transaction.getAmountNQT(), transaction.getTimestamp(), attachment.getFrequency()); - } - - @Override - final void undoAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { - } - - @Override - boolean isDuplicate(Transaction transaction, Map> duplicates) { - return false; - } - - @Override - void validateAttachment(Transaction transaction) throws NxtException.ValidationException { - Attachment.AdvancedPaymentSubscriptionSubscribe attachment = (Attachment.AdvancedPaymentSubscriptionSubscribe) transaction.getAttachment(); - if(attachment.getFrequency() == null || - attachment.getFrequency().intValue() < Constants.BURST_SUBSCRIPTION_MIN_FREQ || - attachment.getFrequency().intValue() > Constants.BURST_SUBSCRIPTION_MAX_FREQ) { - throw new NxtException.NotValidException("Invalid subscription frequency"); - } - if(transaction.getAmountNQT() < Constants.ONE_NXT || transaction.getAmountNQT() > Constants.MAX_BALANCE_NQT) { - throw new NxtException.NotValidException("Subscriptions must be at least one burst"); - } - if(transaction.getSenderId() == transaction.getRecipientId()) { - throw new NxtException.NotValidException("Cannot create subscription to same address"); - } - if(!Subscription.isEnabled()) { - throw new NxtException.NotYetEnabledException("Subscriptions not yet enabled"); - } - } - - @Override - final public boolean hasRecipient() { - return true; - } - }; - - public final static TransactionType SUBSCRIPTION_CANCEL = new AdvancedPayment() { - - @Override - public final byte getSubtype() { - return TransactionType.SUBTYPE_ADVANCED_PAYMENT_SUBSCRIPTION_CANCEL; - } - - @Override - Attachment.AdvancedPaymentSubscriptionCancel parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { - return new Attachment.AdvancedPaymentSubscriptionCancel(buffer, transactionVersion); - } - - @Override - Attachment.AdvancedPaymentSubscriptionCancel parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { - return new Attachment.AdvancedPaymentSubscriptionCancel(attachmentData); - } - - @Override - final boolean applyAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { - Attachment.AdvancedPaymentSubscriptionCancel attachment = (Attachment.AdvancedPaymentSubscriptionCancel) transaction.getAttachment(); - Subscription.addRemoval(attachment.getSubscriptionId()); - return true; - } - - @Override - final void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { - Attachment.AdvancedPaymentSubscriptionCancel attachment = (Attachment.AdvancedPaymentSubscriptionCancel) transaction.getAttachment(); - Subscription.removeSubscription(attachment.getSubscriptionId()); - } - - @Override - final void undoAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { - } - - @Override - boolean isDuplicate(Transaction transaction, Map> duplicates) { - Attachment.AdvancedPaymentSubscriptionCancel attachment = (Attachment.AdvancedPaymentSubscriptionCancel) transaction.getAttachment(); - return isDuplicate(AdvancedPayment.SUBSCRIPTION_CANCEL, Convert.toUnsignedLong(attachment.getSubscriptionId()), duplicates); - } - - @Override - void validateAttachment(Transaction transaction) throws NxtException.ValidationException { - Attachment.AdvancedPaymentSubscriptionCancel attachment = (Attachment.AdvancedPaymentSubscriptionCancel) transaction.getAttachment(); - if(attachment.getSubscriptionId() == null) { - throw new NxtException.NotValidException("Subscription cancel must include subscription id"); - } - - Subscription subscription = Subscription.getSubscription(attachment.getSubscriptionId()); - if(subscription == null) { - throw new NxtException.NotValidException("Subscription cancel must contain current subscription id"); - } - - if(!subscription.getSenderId().equals(transaction.getSenderId()) && - !subscription.getRecipientId().equals(transaction.getSenderId())) { - throw new NxtException.NotValidException("Subscription cancel can only be done by participants"); - } - - if(!Subscription.isEnabled()) { - throw new NxtException.NotYetEnabledException("Subscription cancel not yet enabled"); - } - } - - @Override - final public boolean hasRecipient() { - return false; - } - }; - - public final static TransactionType SUBSCRIPTION_PAYMENT = new AdvancedPayment() { - - @Override - public final byte getSubtype() { - return TransactionType.SUBTYPE_ADVANCED_PAYMENT_SUBSCRIPTION_PAYMENT; - } - - @Override - Attachment.AdvancedPaymentSubscriptionPayment parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { - return new Attachment.AdvancedPaymentSubscriptionPayment(buffer, transactionVersion); - } - - @Override - Attachment.AdvancedPaymentSubscriptionPayment parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { - return new Attachment.AdvancedPaymentSubscriptionPayment(attachmentData); - } - - @Override - final boolean applyAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { - return false; - } - - @Override - final void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { - } - - @Override - final void undoAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { - } - - @Override - boolean isDuplicate(Transaction transaction, Map> duplicates) { - return true; - } - - @Override - void validateAttachment(Transaction transaction) throws NxtException.ValidationException { - throw new NxtException.NotValidException("Subscription payment never validates"); - } - - @Override - final public boolean hasRecipient() { - return true; - } - - @Override - final public boolean isSigned() { - return false; - } - }; - } - - public static abstract class AutomatedTransactions extends TransactionType{ - private AutomatedTransactions() { - - } - - @Override - public final byte getType(){ - return TransactionType.TYPE_AUTOMATED_TRANSACTIONS; - } - - @Override - boolean applyAttachmentUnconfirmed(Transaction transaction,Account senderAccount){ - return true; - } - - @Override - void undoAttachmentUnconfirmed(Transaction transaction, Account senderAccount){ - - } - - @Override - final void validateAttachment(Transaction transaction) throws NxtException.ValidationException { - if (transaction.getAmountNQT() != 0) { - throw new NxtException.NotValidException("Invalid automated transaction transaction"); - } - doValidateAttachment(transaction); - } - - abstract void doValidateAttachment(Transaction transaction) throws NxtException.ValidationException; - - - public static final TransactionType AUTOMATED_TRANSACTION_CREATION = new AutomatedTransactions(){ - - @Override - public byte getSubtype() { - return TransactionType.SUBTYPE_AT_CREATION; - } - - @Override - AbstractAttachment parseAttachment(ByteBuffer buffer, - byte transactionVersion) throws NotValidException { - // TODO Auto-generated method stub - //System.out.println("parsing byte AT attachment"); - AutomatedTransactionsCreation attachment = new Attachment.AutomatedTransactionsCreation(buffer,transactionVersion); - //System.out.println("byte AT attachment parsed"); - return attachment; - } - - @Override - AbstractAttachment parseAttachment(JSONObject attachmentData) - throws NotValidException { - // TODO Auto-generated method stub - //System.out.println("parsing at attachment"); - Attachment.AutomatedTransactionsCreation atCreateAttachment = new Attachment.AutomatedTransactionsCreation(attachmentData); - //System.out.println("attachment parsed"); - return atCreateAttachment; - } - - @Override - void doValidateAttachment(Transaction transaction) - throws ValidationException { - //System.out.println("validating attachment"); - if (Nxt.getBlockchain().getLastBlock().getHeight()< Constants.AUTOMATED_TRANSACTION_BLOCK){ - throw new NxtException.NotYetEnabledException("Automated Transactions not yet enabled at height " + Nxt.getBlockchain().getLastBlock().getHeight()); - } - if (transaction.getSignature() != null && Account.getAccount(transaction.getId()) != null) { - Account existingAccount = Account.getAccount(transaction.getId()); - if(existingAccount.getPublicKey() != null && !Arrays.equals(existingAccount.getPublicKey(), new byte[32])) - throw new NxtException.NotValidException("Account with id already exists"); - } - Attachment.AutomatedTransactionsCreation attachment = (Attachment.AutomatedTransactionsCreation) transaction.getAttachment(); - long totalPages = 0; - try { - totalPages = AT_Controller.checkCreationBytes(attachment.getCreationBytes(), Nxt.getBlockchain().getHeight()); - } - catch(AT_Exception e) { - throw new NxtException.NotCurrentlyValidException("Invalid AT creation bytes", e); - } - long requiredFee = totalPages * AT_Constants.getInstance().COST_PER_PAGE( transaction.getHeight() ); - if (transaction.getFeeNQT() < requiredFee){ - throw new NxtException.NotValidException("Insufficient fee for AT creation. Minimum: " + Convert.toUnsignedLong(requiredFee / Constants.ONE_NXT)); - } - if(Nxt.getBlockchain().getHeight() >= Constants.AT_FIX_BLOCK_3) { - if(attachment.getName().length() > Constants.MAX_AUTOMATED_TRANSACTION_NAME_LENGTH) { - throw new NxtException.NotValidException("Name of automated transaction over size limit"); - } - if(attachment.getDescription().length() > Constants.MAX_AUTOMATED_TRANSACTION_DESCRIPTION_LENGTH) { - throw new NxtException.NotValidException("Description of automated transaction over size limit"); - } - } - //System.out.println("validating success"); - } - - @Override - void applyAttachment(Transaction transaction, - Account senderAccount, Account recipientAccount) { - // TODO Auto-generated method stub - Attachment.AutomatedTransactionsCreation attachment = (Attachment.AutomatedTransactionsCreation) transaction.getAttachment(); - Long atId = transaction.getId(); - //System.out.println("Applying AT attachent"); - AT.addAT( transaction.getId() , transaction.getSenderId() , attachment.getName() , attachment.getDescription() , attachment.getCreationBytes() , transaction.getHeight() ); - //System.out.println("At with id "+atId+" successfully applied"); - } - - - @Override - public boolean hasRecipient() { - // TODO Auto-generated method stub - return false; - } - }; - - public static final TransactionType AT_PAYMENT = new AutomatedTransactions() { - @Override - public final byte getSubtype() { - return TransactionType.SUBTYPE_AT_NXT_PAYMENT; - } - - @Override - AbstractAttachment parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { - return Attachment.AT_PAYMENT; - } - - @Override - AbstractAttachment parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { - return Attachment.AT_PAYMENT; - } - - @Override - void doValidateAttachment(Transaction transaction) throws NxtException.ValidationException { - /*if (transaction.getAmountNQT() <= 0 || transaction.getAmountNQT() >= Constants.MAX_BALANCE_NQT) { - throw new NxtException.NotValidException("Invalid ordinary payment"); - }*/ - throw new NxtException.NotValidException("AT payment never validates"); - } - - @Override - void applyAttachment(Transaction transaction, - Account senderAccount, Account recipientAccount) { - // TODO Auto-generated method stub - - } - - - @Override - public boolean hasRecipient() { - return true; - } - - @Override - final public boolean isSigned() { - return false; - } - }; - - } - - long minimumFeeNQT(int height, int appendagesSize) { - if (height < BASELINE_FEE_HEIGHT) { - return 0; // No need to validate fees before baseline block - } - Fee fee; - if (height >= NEXT_FEE_HEIGHT) { - fee = getNextFee(); - } else { - fee = getBaselineFee(); - } - return Convert.safeAdd(fee.getConstantFee(), Convert.safeMultiply(appendagesSize, fee.getAppendagesFee())); - } - - protected Fee getBaselineFee() { - return BASELINE_FEE; - } - - protected Fee getNextFee() { - return NEXT_FEE; - } - - public static final class Fee { - private final long constantFee; - private final long appendagesFee; - - public Fee(long constantFee, long appendagesFee) { - this.constantFee = constantFee; - this.appendagesFee = appendagesFee; - } - - public long getConstantFee() { - return constantFee; - } - - public long getAppendagesFee() { - return appendagesFee; - } - - @Override - public String toString() { - return "Fee{" + - "constantFee=" + constantFee + - ", appendagesFee=" + appendagesFee + - '}'; - } - } - -} +package nxt; + +import nxt.Attachment.AbstractAttachment; +import nxt.Attachment.AutomatedTransactionsCreation; +import nxt.NxtException.NotValidException; +import nxt.NxtException.ValidationException; +import nxt.at.AT_Constants; +import nxt.at.AT_Controller; +import nxt.at.AT_Exception; +import nxt.util.Convert; + +import org.json.simple.JSONObject; + +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + + +public abstract class TransactionType { + + private static final byte TYPE_PAYMENT = 0; + private static final byte TYPE_MESSAGING = 1; + private static final byte TYPE_COLORED_COINS = 2; + private static final byte TYPE_DIGITAL_GOODS = 3; + private static final byte TYPE_ACCOUNT_CONTROL = 4; + + private static final byte TYPE_BURST_MINING = 20; // jump some for easier nxt updating + private static final byte TYPE_ADVANCED_PAYMENT = 21; + private static final byte TYPE_AUTOMATED_TRANSACTIONS = 22; + + private static final byte SUBTYPE_PAYMENT_ORDINARY_PAYMENT = 0; + + private static final byte SUBTYPE_MESSAGING_ARBITRARY_MESSAGE = 0; + private static final byte SUBTYPE_MESSAGING_ALIAS_ASSIGNMENT = 1; + private static final byte SUBTYPE_MESSAGING_POLL_CREATION = 2; + private static final byte SUBTYPE_MESSAGING_VOTE_CASTING = 3; + private static final byte SUBTYPE_MESSAGING_HUB_ANNOUNCEMENT = 4; + private static final byte SUBTYPE_MESSAGING_ACCOUNT_INFO = 5; + private static final byte SUBTYPE_MESSAGING_ALIAS_SELL = 6; + private static final byte SUBTYPE_MESSAGING_ALIAS_BUY = 7; + + private static final byte SUBTYPE_COLORED_COINS_ASSET_ISSUANCE = 0; + private static final byte SUBTYPE_COLORED_COINS_ASSET_TRANSFER = 1; + private static final byte SUBTYPE_COLORED_COINS_ASK_ORDER_PLACEMENT = 2; + private static final byte SUBTYPE_COLORED_COINS_BID_ORDER_PLACEMENT = 3; + private static final byte SUBTYPE_COLORED_COINS_ASK_ORDER_CANCELLATION = 4; + private static final byte SUBTYPE_COLORED_COINS_BID_ORDER_CANCELLATION = 5; + + private static final byte SUBTYPE_DIGITAL_GOODS_LISTING = 0; + private static final byte SUBTYPE_DIGITAL_GOODS_DELISTING = 1; + private static final byte SUBTYPE_DIGITAL_GOODS_PRICE_CHANGE = 2; + private static final byte SUBTYPE_DIGITAL_GOODS_QUANTITY_CHANGE = 3; + private static final byte SUBTYPE_DIGITAL_GOODS_PURCHASE = 4; + private static final byte SUBTYPE_DIGITAL_GOODS_DELIVERY = 5; + private static final byte SUBTYPE_DIGITAL_GOODS_FEEDBACK = 6; + private static final byte SUBTYPE_DIGITAL_GOODS_REFUND = 7; + + private static final byte SUBTYPE_AT_CREATION = 0; + private static final byte SUBTYPE_AT_NXT_PAYMENT = 1; + + private static final byte SUBTYPE_ACCOUNT_CONTROL_EFFECTIVE_BALANCE_LEASING = 0; + + private static final byte SUBTYPE_BURST_MINING_REWARD_RECIPIENT_ASSIGNMENT = 0; + + private static final byte SUBTYPE_ADVANCED_PAYMENT_ESCROW_CREATION = 0; + private static final byte SUBTYPE_ADVANCED_PAYMENT_ESCROW_SIGN = 1; + private static final byte SUBTYPE_ADVANCED_PAYMENT_ESCROW_RESULT = 2; + private static final byte SUBTYPE_ADVANCED_PAYMENT_SUBSCRIPTION_SUBSCRIBE = 3; + private static final byte SUBTYPE_ADVANCED_PAYMENT_SUBSCRIPTION_CANCEL = 4; + private static final byte SUBTYPE_ADVANCED_PAYMENT_SUBSCRIPTION_PAYMENT = 5; + + private static final int BASELINE_FEE_HEIGHT = 1; // At release time must be less than current block - 1440 + private static final Fee BASELINE_FEE = new Fee(Constants.ONE_NXT, 0); + private static final Fee BASELINE_ASSET_ISSUANCE_FEE = new Fee(1000 * Constants.ONE_NXT, 0); + private static final int NEXT_FEE_HEIGHT = Integer.MAX_VALUE; + private static final Fee NEXT_FEE = new Fee(Constants.ONE_NXT, 0); + private static final Fee NEXT_ASSET_ISSUANCE_FEE = new Fee(1000 * Constants.ONE_NXT, 0); + + public static TransactionType findTransactionType(byte type, byte subtype) { + switch (type) { + case TYPE_PAYMENT: + switch (subtype) { + case SUBTYPE_PAYMENT_ORDINARY_PAYMENT: + return Payment.ORDINARY; + default: + return null; + } + case TYPE_MESSAGING: + switch (subtype) { + case SUBTYPE_MESSAGING_ARBITRARY_MESSAGE: + return Messaging.ARBITRARY_MESSAGE; + case SUBTYPE_MESSAGING_ALIAS_ASSIGNMENT: + return Messaging.ALIAS_ASSIGNMENT; + case SUBTYPE_MESSAGING_POLL_CREATION: + return Messaging.POLL_CREATION; + case SUBTYPE_MESSAGING_VOTE_CASTING: + return Messaging.VOTE_CASTING; + case SUBTYPE_MESSAGING_HUB_ANNOUNCEMENT: + return Messaging.HUB_ANNOUNCEMENT; + case SUBTYPE_MESSAGING_ACCOUNT_INFO: + return Messaging.ACCOUNT_INFO; + case SUBTYPE_MESSAGING_ALIAS_SELL: + return Messaging.ALIAS_SELL; + case SUBTYPE_MESSAGING_ALIAS_BUY: + return Messaging.ALIAS_BUY; + default: + return null; + } + case TYPE_COLORED_COINS: + switch (subtype) { + case SUBTYPE_COLORED_COINS_ASSET_ISSUANCE: + return ColoredCoins.ASSET_ISSUANCE; + case SUBTYPE_COLORED_COINS_ASSET_TRANSFER: + return ColoredCoins.ASSET_TRANSFER; + case SUBTYPE_COLORED_COINS_ASK_ORDER_PLACEMENT: + return ColoredCoins.ASK_ORDER_PLACEMENT; + case SUBTYPE_COLORED_COINS_BID_ORDER_PLACEMENT: + return ColoredCoins.BID_ORDER_PLACEMENT; + case SUBTYPE_COLORED_COINS_ASK_ORDER_CANCELLATION: + return ColoredCoins.ASK_ORDER_CANCELLATION; + case SUBTYPE_COLORED_COINS_BID_ORDER_CANCELLATION: + return ColoredCoins.BID_ORDER_CANCELLATION; + default: + return null; + } + case TYPE_DIGITAL_GOODS: + switch (subtype) { + case SUBTYPE_DIGITAL_GOODS_LISTING: + return DigitalGoods.LISTING; + case SUBTYPE_DIGITAL_GOODS_DELISTING: + return DigitalGoods.DELISTING; + case SUBTYPE_DIGITAL_GOODS_PRICE_CHANGE: + return DigitalGoods.PRICE_CHANGE; + case SUBTYPE_DIGITAL_GOODS_QUANTITY_CHANGE: + return DigitalGoods.QUANTITY_CHANGE; + case SUBTYPE_DIGITAL_GOODS_PURCHASE: + return DigitalGoods.PURCHASE; + case SUBTYPE_DIGITAL_GOODS_DELIVERY: + return DigitalGoods.DELIVERY; + case SUBTYPE_DIGITAL_GOODS_FEEDBACK: + return DigitalGoods.FEEDBACK; + case SUBTYPE_DIGITAL_GOODS_REFUND: + return DigitalGoods.REFUND; + default: + return null; + } + case TYPE_ACCOUNT_CONTROL: + switch (subtype) { + case SUBTYPE_ACCOUNT_CONTROL_EFFECTIVE_BALANCE_LEASING: + return AccountControl.EFFECTIVE_BALANCE_LEASING; + default: + return null; + } + case TYPE_BURST_MINING: + switch (subtype) { + case SUBTYPE_BURST_MINING_REWARD_RECIPIENT_ASSIGNMENT: + return BurstMining.REWARD_RECIPIENT_ASSIGNMENT; + default: + return null; + } + case TYPE_ADVANCED_PAYMENT: + switch (subtype) { + case SUBTYPE_ADVANCED_PAYMENT_ESCROW_CREATION: + return AdvancedPayment.ESCROW_CREATION; + case SUBTYPE_ADVANCED_PAYMENT_ESCROW_SIGN: + return AdvancedPayment.ESCROW_SIGN; + case SUBTYPE_ADVANCED_PAYMENT_ESCROW_RESULT: + return AdvancedPayment.ESCROW_RESULT; + case SUBTYPE_ADVANCED_PAYMENT_SUBSCRIPTION_SUBSCRIBE: + return AdvancedPayment.SUBSCRIPTION_SUBSCRIBE; + case SUBTYPE_ADVANCED_PAYMENT_SUBSCRIPTION_CANCEL: + return AdvancedPayment.SUBSCRIPTION_CANCEL; + case SUBTYPE_ADVANCED_PAYMENT_SUBSCRIPTION_PAYMENT: + return AdvancedPayment.SUBSCRIPTION_PAYMENT; + default: + return null; + } + case TYPE_AUTOMATED_TRANSACTIONS: + switch (subtype) { + case SUBTYPE_AT_CREATION: + return AutomatedTransactions.AUTOMATED_TRANSACTION_CREATION; + case SUBTYPE_AT_NXT_PAYMENT: + return AutomatedTransactions.AT_PAYMENT; + default: + return null; + } + default: + return null; + } + } + + private TransactionType() { + } + + public abstract byte getType(); + + public abstract byte getSubtype(); + + abstract Attachment.AbstractAttachment parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException; + + abstract Attachment.AbstractAttachment parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException; + + abstract void validateAttachment(Transaction transaction) throws NxtException.ValidationException; + + // return false iff double spending + final boolean applyUnconfirmed(Transaction transaction, Account senderAccount) { + long totalAmountNQT = Convert.safeAdd(transaction.getAmountNQT(), transaction.getFeeNQT()); + if (transaction.getReferencedTransactionFullHash() != null + && transaction.getTimestamp() > Constants.REFERENCED_TRANSACTION_FULL_HASH_BLOCK_TIMESTAMP) { + totalAmountNQT = Convert.safeAdd(totalAmountNQT, Constants.UNCONFIRMED_POOL_DEPOSIT_NQT); + } + if (senderAccount.getUnconfirmedBalanceNQT() < totalAmountNQT) { + return false; + } + senderAccount.addToUnconfirmedBalanceNQT(-totalAmountNQT); + if (!applyAttachmentUnconfirmed(transaction, senderAccount)) { + senderAccount.addToUnconfirmedBalanceNQT(totalAmountNQT); + return false; + } + return true; + } + + abstract boolean applyAttachmentUnconfirmed(Transaction transaction, Account senderAccount); + + final void apply(Transaction transaction, Account senderAccount, Account recipientAccount) { + senderAccount.addToBalanceNQT(- (Convert.safeAdd(transaction.getAmountNQT(), transaction.getFeeNQT()))); + if (transaction.getReferencedTransactionFullHash() != null + && transaction.getTimestamp() > Constants.REFERENCED_TRANSACTION_FULL_HASH_BLOCK_TIMESTAMP) { + senderAccount.addToUnconfirmedBalanceNQT(Constants.UNCONFIRMED_POOL_DEPOSIT_NQT); + } + if (recipientAccount != null) { + recipientAccount.addToBalanceAndUnconfirmedBalanceNQT(transaction.getAmountNQT()); + } + applyAttachment(transaction, senderAccount, recipientAccount); + } + + abstract void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount); + + final void undoUnconfirmed(Transaction transaction, Account senderAccount) { + undoAttachmentUnconfirmed(transaction, senderAccount); + senderAccount.addToUnconfirmedBalanceNQT(Convert.safeAdd(transaction.getAmountNQT(), transaction.getFeeNQT())); + if (transaction.getReferencedTransactionFullHash() != null + && transaction.getTimestamp() > Constants.REFERENCED_TRANSACTION_FULL_HASH_BLOCK_TIMESTAMP) { + senderAccount.addToUnconfirmedBalanceNQT(Constants.UNCONFIRMED_POOL_DEPOSIT_NQT); + } + } + + abstract void undoAttachmentUnconfirmed(Transaction transaction, Account senderAccount); + + boolean isDuplicate(Transaction transaction, Map> duplicates) { + return false; + } + + static boolean isDuplicate(TransactionType uniqueType, String key, Map> duplicates) { + Set typeDuplicates = duplicates.get(uniqueType); + if (typeDuplicates == null) { + typeDuplicates = new HashSet<>(); + duplicates.put(uniqueType, typeDuplicates); + } + return ! typeDuplicates.add(key); + } + + public abstract boolean hasRecipient(); + + public boolean isSigned() { + return true; + } + + @Override + public final String toString() { + return "type: " + getType() + ", subtype: " + getSubtype(); + } + + /* + Collection getPhasingTransactionTypes() { + return Collections.emptyList(); + } + + Collection getPhasedTransactionTypes() { + return Collections.emptyList(); + } + */ + + public static abstract class Payment extends TransactionType { + + private Payment() { + } + + @Override + public final byte getType() { + return TransactionType.TYPE_PAYMENT; + } + + @Override + final boolean applyAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { + return true; + } + + @Override + final void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { + } + + @Override + final void undoAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { + } + + @Override + final public boolean hasRecipient() { + return true; + } + + public static final TransactionType ORDINARY = new Payment() { + + @Override + public final byte getSubtype() { + return TransactionType.SUBTYPE_PAYMENT_ORDINARY_PAYMENT; + } + + @Override + Attachment.EmptyAttachment parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { + return Attachment.ORDINARY_PAYMENT; + } + + @Override + Attachment.EmptyAttachment parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { + return Attachment.ORDINARY_PAYMENT; + } + + @Override + void validateAttachment(Transaction transaction) throws NxtException.ValidationException { + if (transaction.getAmountNQT() <= 0 || transaction.getAmountNQT() >= Constants.MAX_BALANCE_NQT) { + throw new NxtException.NotValidException("Invalid ordinary payment"); + } + } + + }; + + } + + public static abstract class Messaging extends TransactionType { + + private Messaging() { + } + + @Override + public final byte getType() { + return TransactionType.TYPE_MESSAGING; + } + + @Override + final boolean applyAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { + return true; + } + + @Override + final void undoAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { + } + + public final static TransactionType ARBITRARY_MESSAGE = new Messaging() { + + @Override + public final byte getSubtype() { + return TransactionType.SUBTYPE_MESSAGING_ARBITRARY_MESSAGE; + } + + @Override + Attachment.EmptyAttachment parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { + return Attachment.ARBITRARY_MESSAGE; + } + + @Override + Attachment.EmptyAttachment parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { + return Attachment.ARBITRARY_MESSAGE; + } + + @Override + void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { + } + + @Override + void validateAttachment(Transaction transaction) throws NxtException.ValidationException { + Attachment attachment = transaction.getAttachment(); + if (transaction.getAmountNQT() != 0) { + throw new NxtException.NotValidException("Invalid arbitrary message: " + attachment.getJSONObject()); + } + if (Nxt.getBlockchain().getHeight() < Constants.DIGITAL_GOODS_STORE_BLOCK && transaction.getMessage() == null) { + throw new NxtException.NotCurrentlyValidException("Missing message appendix not allowed before DGS block"); + } + } + + @Override + public boolean hasRecipient() { + return true; + } + + }; + + public static final TransactionType ALIAS_ASSIGNMENT = new Messaging() { + + @Override + public final byte getSubtype() { + return TransactionType.SUBTYPE_MESSAGING_ALIAS_ASSIGNMENT; + } + + @Override + Attachment.MessagingAliasAssignment parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { + return new Attachment.MessagingAliasAssignment(buffer, transactionVersion); + } + + @Override + Attachment.MessagingAliasAssignment parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { + return new Attachment.MessagingAliasAssignment(attachmentData); + } + + @Override + void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { + Attachment.MessagingAliasAssignment attachment = (Attachment.MessagingAliasAssignment) transaction.getAttachment(); + Alias.addOrUpdateAlias(transaction, attachment); + } + + @Override + boolean isDuplicate(Transaction transaction, Map> duplicates) { + Attachment.MessagingAliasAssignment attachment = (Attachment.MessagingAliasAssignment) transaction.getAttachment(); + return isDuplicate(Messaging.ALIAS_ASSIGNMENT, attachment.getAliasName().toLowerCase(), duplicates); + } + + @Override + void validateAttachment(Transaction transaction) throws NxtException.ValidationException { + Attachment.MessagingAliasAssignment attachment = (Attachment.MessagingAliasAssignment) transaction.getAttachment(); + if (attachment.getAliasName().length() == 0 + || attachment.getAliasName().length() > Constants.MAX_ALIAS_LENGTH + || attachment.getAliasURI().length() > Constants.MAX_ALIAS_URI_LENGTH) { + throw new NxtException.NotValidException("Invalid alias assignment: " + attachment.getJSONObject()); + } + String normalizedAlias = attachment.getAliasName().toLowerCase(); + for (int i = 0; i < normalizedAlias.length(); i++) { + if (Constants.ALPHABET.indexOf(normalizedAlias.charAt(i)) < 0) { + throw new NxtException.NotValidException("Invalid alias name: " + normalizedAlias); + } + } + Alias alias = Alias.getAlias(normalizedAlias); + if (alias != null && alias.getAccountId() != transaction.getSenderId()) { + throw new NxtException.NotCurrentlyValidException("Alias already owned by another account: " + normalizedAlias); + } + } + + @Override + public boolean hasRecipient() { + return false; + } + + }; + + public static final TransactionType ALIAS_SELL = new Messaging() { + + @Override + public final byte getSubtype() { + return TransactionType.SUBTYPE_MESSAGING_ALIAS_SELL; + } + + @Override + Attachment.MessagingAliasSell parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { + return new Attachment.MessagingAliasSell(buffer, transactionVersion); + } + + @Override + Attachment.MessagingAliasSell parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { + return new Attachment.MessagingAliasSell(attachmentData); + } + + @Override + void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { + final Attachment.MessagingAliasSell attachment = + (Attachment.MessagingAliasSell) transaction.getAttachment(); + Alias.sellAlias(transaction, attachment); + } + + @Override + boolean isDuplicate(Transaction transaction, Map> duplicates) { + Attachment.MessagingAliasSell attachment = (Attachment.MessagingAliasSell) transaction.getAttachment(); + // not a bug, uniqueness is based on Messaging.ALIAS_ASSIGNMENT + return isDuplicate(Messaging.ALIAS_ASSIGNMENT, attachment.getAliasName().toLowerCase(), duplicates); + } + + @Override + void validateAttachment(Transaction transaction) throws NxtException.ValidationException { + if (Nxt.getBlockchain().getLastBlock().getHeight() < Constants.DIGITAL_GOODS_STORE_BLOCK) { + throw new NxtException.NotYetEnabledException("Alias transfer not yet enabled at height " + Nxt.getBlockchain().getLastBlock().getHeight()); + } + if (transaction.getAmountNQT() != 0) { + throw new NxtException.NotValidException("Invalid sell alias transaction: " + + transaction.getJSONObject()); + } + final Attachment.MessagingAliasSell attachment = + (Attachment.MessagingAliasSell) transaction.getAttachment(); + final String aliasName = attachment.getAliasName(); + if (aliasName == null || aliasName.length() == 0) { + throw new NxtException.NotValidException("Missing alias name"); + } + long priceNQT = attachment.getPriceNQT(); + if (priceNQT < 0 || priceNQT > Constants.MAX_BALANCE_NQT) { + throw new NxtException.NotValidException("Invalid alias sell price: " + priceNQT); + } + if (priceNQT == 0) { + if (Genesis.CREATOR_ID == transaction.getRecipientId()) { + throw new NxtException.NotValidException("Transferring aliases to Genesis account not allowed"); + } else if (transaction.getRecipientId() == 0) { + throw new NxtException.NotValidException("Missing alias transfer recipient"); + } + } + final Alias alias = Alias.getAlias(aliasName); + if (alias == null) { + throw new NxtException.NotCurrentlyValidException("Alias hasn't been registered yet: " + aliasName); + } else if (alias.getAccountId() != transaction.getSenderId()) { + throw new NxtException.NotCurrentlyValidException("Alias doesn't belong to sender: " + aliasName); + } + } + + @Override + public boolean hasRecipient() { + return true; + } + + }; + + public static final TransactionType ALIAS_BUY = new Messaging() { + + @Override + public final byte getSubtype() { + return TransactionType.SUBTYPE_MESSAGING_ALIAS_BUY; + } + + @Override + Attachment.MessagingAliasBuy parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { + return new Attachment.MessagingAliasBuy(buffer, transactionVersion); + } + + @Override + Attachment.MessagingAliasBuy parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { + return new Attachment.MessagingAliasBuy(attachmentData); + } + + @Override + void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { + final Attachment.MessagingAliasBuy attachment = + (Attachment.MessagingAliasBuy) transaction.getAttachment(); + final String aliasName = attachment.getAliasName(); + Alias.changeOwner(transaction.getSenderId(), aliasName, transaction.getBlockTimestamp()); + } + + @Override + boolean isDuplicate(Transaction transaction, Map> duplicates) { + Attachment.MessagingAliasBuy attachment = (Attachment.MessagingAliasBuy) transaction.getAttachment(); + // not a bug, uniqueness is based on Messaging.ALIAS_ASSIGNMENT + return isDuplicate(Messaging.ALIAS_ASSIGNMENT, attachment.getAliasName().toLowerCase(), duplicates); + } + + @Override + void validateAttachment(Transaction transaction) throws NxtException.ValidationException { + if (Nxt.getBlockchain().getLastBlock().getHeight() < Constants.DIGITAL_GOODS_STORE_BLOCK) { + throw new NxtException.NotYetEnabledException("Alias transfer not yet enabled at height " + Nxt.getBlockchain().getLastBlock().getHeight()); + } + final Attachment.MessagingAliasBuy attachment = + (Attachment.MessagingAliasBuy) transaction.getAttachment(); + final String aliasName = attachment.getAliasName(); + final Alias alias = Alias.getAlias(aliasName); + if (alias == null) { + throw new NxtException.NotCurrentlyValidException("Alias hasn't been registered yet: " + aliasName); + } else if (alias.getAccountId() != transaction.getRecipientId()) { + throw new NxtException.NotCurrentlyValidException("Alias is owned by account other than recipient: " + + Convert.toUnsignedLong(alias.getAccountId())); + } + Alias.Offer offer = Alias.getOffer(alias); + if (offer == null) { + throw new NxtException.NotCurrentlyValidException("Alias is not for sale: " + aliasName); + } + if (transaction.getAmountNQT() < offer.getPriceNQT()) { + String msg = "Price is too low for: " + aliasName + " (" + + transaction.getAmountNQT() + " < " + offer.getPriceNQT() + ")"; + throw new NxtException.NotCurrentlyValidException(msg); + } + if (offer.getBuyerId() != 0 && offer.getBuyerId() != transaction.getSenderId()) { + throw new NxtException.NotCurrentlyValidException("Wrong buyer for " + aliasName + ": " + + Convert.toUnsignedLong(transaction.getSenderId()) + " expected: " + + Convert.toUnsignedLong(offer.getBuyerId())); + } + } + + @Override + public boolean hasRecipient() { + return true; + } + + }; + + public final static TransactionType POLL_CREATION = new Messaging() { + @Override + public final byte getSubtype() { + return TransactionType.SUBTYPE_MESSAGING_POLL_CREATION; + } + + @Override + Attachment.MessagingPollCreation parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { + return new Attachment.MessagingPollCreation(buffer, transactionVersion); + } + + @Override + Attachment.MessagingPollCreation parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { + return new Attachment.MessagingPollCreation(attachmentData); + } + + @Override + void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { + Attachment.MessagingPollCreation attachment = (Attachment.MessagingPollCreation) transaction.getAttachment(); + Poll.addPoll(transaction, attachment); + } + + @Override + void validateAttachment(Transaction transaction) throws NxtException.ValidationException { + if (Nxt.getBlockchain().getLastBlock().getHeight() < Constants.VOTING_SYSTEM_BLOCK) { + throw new NxtException.NotYetEnabledException("Voting System not yet enabled at height " + Nxt.getBlockchain().getLastBlock().getHeight()); + } + Attachment.MessagingPollCreation attachment = (Attachment.MessagingPollCreation) transaction.getAttachment(); + for (int i = 0; i < attachment.getPollOptions().length; i++) { + if (attachment.getPollOptions()[i].length() > Constants.MAX_POLL_OPTION_LENGTH) { + throw new NxtException.NotValidException("Invalid poll options length: " + attachment.getJSONObject()); + } + } + if (attachment.getPollName().length() > Constants.MAX_POLL_NAME_LENGTH + || attachment.getPollDescription().length() > Constants.MAX_POLL_DESCRIPTION_LENGTH + || attachment.getPollOptions().length > Constants.MAX_POLL_OPTION_COUNT) { + throw new NxtException.NotValidException("Invalid poll attachment: " + attachment.getJSONObject()); + } + } + + @Override + public boolean hasRecipient() { + return false; + } + + }; + + public final static TransactionType VOTE_CASTING = new Messaging() { + + @Override + public final byte getSubtype() { + return TransactionType.SUBTYPE_MESSAGING_VOTE_CASTING; + } + + @Override + Attachment.MessagingVoteCasting parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { + return new Attachment.MessagingVoteCasting(buffer, transactionVersion); + } + + @Override + Attachment.MessagingVoteCasting parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { + return new Attachment.MessagingVoteCasting(attachmentData); + } + + @Override + void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { + Attachment.MessagingVoteCasting attachment = (Attachment.MessagingVoteCasting) transaction.getAttachment(); + Poll poll = Poll.getPoll(attachment.getPollId()); + if (poll != null) { + Vote.addVote(transaction, attachment); + } + } + + @Override + void validateAttachment(Transaction transaction) throws NxtException.ValidationException { + if (Nxt.getBlockchain().getLastBlock().getHeight() < Constants.VOTING_SYSTEM_BLOCK) { + throw new NxtException.NotYetEnabledException("Voting System not yet enabled at height " + Nxt.getBlockchain().getLastBlock().getHeight()); + } + Attachment.MessagingVoteCasting attachment = (Attachment.MessagingVoteCasting) transaction.getAttachment(); + if (attachment.getPollId() == 0 || attachment.getPollVote() == null + || attachment.getPollVote().length > Constants.MAX_POLL_OPTION_COUNT) { + throw new NxtException.NotValidException("Invalid vote casting attachment: " + attachment.getJSONObject()); + } + if (Poll.getPoll(attachment.getPollId()) == null) { + throw new NxtException.NotCurrentlyValidException("Invalid poll: " + Convert.toUnsignedLong(attachment.getPollId())); + } + } + + @Override + public boolean hasRecipient() { + return false; + } + + }; + + public static final TransactionType HUB_ANNOUNCEMENT = new Messaging() { + + @Override + public final byte getSubtype() { + return TransactionType.SUBTYPE_MESSAGING_HUB_ANNOUNCEMENT; + } + + @Override + Attachment.MessagingHubAnnouncement parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { + return new Attachment.MessagingHubAnnouncement(buffer, transactionVersion); + } + + @Override + Attachment.MessagingHubAnnouncement parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { + return new Attachment.MessagingHubAnnouncement(attachmentData); + } + + @Override + void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { + Attachment.MessagingHubAnnouncement attachment = (Attachment.MessagingHubAnnouncement) transaction.getAttachment(); + Hub.addOrUpdateHub(transaction, attachment); + } + + @Override + void validateAttachment(Transaction transaction) throws NxtException.ValidationException { + if (Nxt.getBlockchain().getLastBlock().getHeight() < Constants.TRANSPARENT_FORGING_BLOCK_7) { + throw new NxtException.NotYetEnabledException("Hub terminal announcement not yet enabled at height " + Nxt.getBlockchain().getLastBlock().getHeight()); + } + Attachment.MessagingHubAnnouncement attachment = (Attachment.MessagingHubAnnouncement) transaction.getAttachment(); + if (attachment.getMinFeePerByteNQT() < 0 || attachment.getMinFeePerByteNQT() > Constants.MAX_BALANCE_NQT + || attachment.getUris().length > Constants.MAX_HUB_ANNOUNCEMENT_URIS) { + // cfb: "0" is allowed to show that another way to determine the min fee should be used + throw new NxtException.NotValidException("Invalid hub terminal announcement: " + attachment.getJSONObject()); + } + for (String uri : attachment.getUris()) { + if (uri.length() > Constants.MAX_HUB_ANNOUNCEMENT_URI_LENGTH) { + throw new NxtException.NotValidException("Invalid URI length: " + uri.length()); + } + //TODO: also check URI validity here? + } + } + + @Override + public boolean hasRecipient() { + return false; + } + + }; + + public static final Messaging ACCOUNT_INFO = new Messaging() { + + @Override + public byte getSubtype() { + return TransactionType.SUBTYPE_MESSAGING_ACCOUNT_INFO; + } + + @Override + Attachment.MessagingAccountInfo parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { + return new Attachment.MessagingAccountInfo(buffer, transactionVersion); + } + + @Override + Attachment.MessagingAccountInfo parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { + return new Attachment.MessagingAccountInfo(attachmentData); + } + + @Override + void validateAttachment(Transaction transaction) throws NxtException.ValidationException { + Attachment.MessagingAccountInfo attachment = (Attachment.MessagingAccountInfo)transaction.getAttachment(); + if (attachment.getName().length() > Constants.MAX_ACCOUNT_NAME_LENGTH + || attachment.getDescription().length() > Constants.MAX_ACCOUNT_DESCRIPTION_LENGTH + ) { + throw new NxtException.NotValidException("Invalid account info issuance: " + attachment.getJSONObject()); + } + } + + @Override + void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { + Attachment.MessagingAccountInfo attachment = (Attachment.MessagingAccountInfo) transaction.getAttachment(); + senderAccount.setAccountInfo(attachment.getName(), attachment.getDescription()); + } + + @Override + public boolean hasRecipient() { + return false; + } + + }; + + } + + public static abstract class ColoredCoins extends TransactionType { + + private ColoredCoins() {} + + @Override + public final byte getType() { + return TransactionType.TYPE_COLORED_COINS; + } + + public static final TransactionType ASSET_ISSUANCE = new ColoredCoins() { + + @Override + public final byte getSubtype() { + return TransactionType.SUBTYPE_COLORED_COINS_ASSET_ISSUANCE; + } + + @Override + public Fee getBaselineFee() { + return BASELINE_ASSET_ISSUANCE_FEE; + } + + @Override + public Fee getNextFee() { + return NEXT_ASSET_ISSUANCE_FEE; + } + + @Override + Attachment.ColoredCoinsAssetIssuance parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { + return new Attachment.ColoredCoinsAssetIssuance(buffer, transactionVersion); + } + + @Override + Attachment.ColoredCoinsAssetIssuance parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { + return new Attachment.ColoredCoinsAssetIssuance(attachmentData); + } + + @Override + boolean applyAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { + return true; + } + + @Override + void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { + Attachment.ColoredCoinsAssetIssuance attachment = (Attachment.ColoredCoinsAssetIssuance) transaction.getAttachment(); + long assetId = transaction.getId(); + Asset.addAsset(transaction, attachment); + senderAccount.addToAssetAndUnconfirmedAssetBalanceQNT(assetId, attachment.getQuantityQNT()); + } + + @Override + void undoAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { + } + + @Override + void validateAttachment(Transaction transaction) throws NxtException.ValidationException { + Attachment.ColoredCoinsAssetIssuance attachment = (Attachment.ColoredCoinsAssetIssuance)transaction.getAttachment(); + if (attachment.getName().length() < Constants.MIN_ASSET_NAME_LENGTH + || attachment.getName().length() > Constants.MAX_ASSET_NAME_LENGTH + || attachment.getDescription().length() > Constants.MAX_ASSET_DESCRIPTION_LENGTH + || attachment.getDecimals() < 0 || attachment.getDecimals() > 8 + || attachment.getQuantityQNT() <= 0 + || attachment.getQuantityQNT() > Constants.MAX_ASSET_QUANTITY_QNT + ) { + throw new NxtException.NotValidException("Invalid asset issuance: " + attachment.getJSONObject()); + } + String normalizedName = attachment.getName().toLowerCase(); + for (int i = 0; i < normalizedName.length(); i++) { + if (Constants.ALPHABET.indexOf(normalizedName.charAt(i)) < 0) { + throw new NxtException.NotValidException("Invalid asset name: " + normalizedName); + } + } + } + + @Override + public boolean hasRecipient() { + return false; + } + + }; + + public static final TransactionType ASSET_TRANSFER = new ColoredCoins() { + + @Override + public final byte getSubtype() { + return TransactionType.SUBTYPE_COLORED_COINS_ASSET_TRANSFER; + } + + @Override + Attachment.ColoredCoinsAssetTransfer parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { + return new Attachment.ColoredCoinsAssetTransfer(buffer, transactionVersion); + } + + @Override + Attachment.ColoredCoinsAssetTransfer parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { + return new Attachment.ColoredCoinsAssetTransfer(attachmentData); + } + + @Override + boolean applyAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { + Attachment.ColoredCoinsAssetTransfer attachment = (Attachment.ColoredCoinsAssetTransfer) transaction.getAttachment(); + long unconfirmedAssetBalance = senderAccount.getUnconfirmedAssetBalanceQNT(attachment.getAssetId()); + if (unconfirmedAssetBalance >= 0 && unconfirmedAssetBalance >= attachment.getQuantityQNT()) { + senderAccount.addToUnconfirmedAssetBalanceQNT(attachment.getAssetId(), -attachment.getQuantityQNT()); + return true; + } + return false; + } + + @Override + void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { + Attachment.ColoredCoinsAssetTransfer attachment = (Attachment.ColoredCoinsAssetTransfer) transaction.getAttachment(); + senderAccount.addToAssetBalanceQNT(attachment.getAssetId(), -attachment.getQuantityQNT()); + recipientAccount.addToAssetAndUnconfirmedAssetBalanceQNT(attachment.getAssetId(), attachment.getQuantityQNT()); + AssetTransfer.addAssetTransfer(transaction, attachment); + } + + @Override + void undoAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { + Attachment.ColoredCoinsAssetTransfer attachment = (Attachment.ColoredCoinsAssetTransfer) transaction.getAttachment(); + senderAccount.addToUnconfirmedAssetBalanceQNT(attachment.getAssetId(), attachment.getQuantityQNT()); + } + + @Override + void validateAttachment(Transaction transaction) throws NxtException.ValidationException { + Attachment.ColoredCoinsAssetTransfer attachment = (Attachment.ColoredCoinsAssetTransfer)transaction.getAttachment(); + if (transaction.getAmountNQT() != 0 + || attachment.getComment() != null && attachment.getComment().length() > Constants.MAX_ASSET_TRANSFER_COMMENT_LENGTH + || attachment.getAssetId() == 0) { + throw new NxtException.NotValidException("Invalid asset transfer amount or comment: " + attachment.getJSONObject()); + } + if (transaction.getVersion() > 0 && attachment.getComment() != null) { + throw new NxtException.NotValidException("Asset transfer comments no longer allowed, use message " + + "or encrypted message appendix instead"); + } + Asset asset = Asset.getAsset(attachment.getAssetId()); + if (attachment.getQuantityQNT() <= 0 || (asset != null && attachment.getQuantityQNT() > asset.getQuantityQNT())) { + throw new NxtException.NotValidException("Invalid asset transfer asset or quantity: " + attachment.getJSONObject()); + } + if (asset == null) { + throw new NxtException.NotCurrentlyValidException("Asset " + Convert.toUnsignedLong(attachment.getAssetId()) + + " does not exist yet"); + } + } + + @Override + public boolean hasRecipient() { + return true; + } + + }; + + abstract static class ColoredCoinsOrderPlacement extends ColoredCoins { + + @Override + final void validateAttachment(Transaction transaction) throws NxtException.ValidationException { + Attachment.ColoredCoinsOrderPlacement attachment = (Attachment.ColoredCoinsOrderPlacement)transaction.getAttachment(); + if (attachment.getPriceNQT() <= 0 || attachment.getPriceNQT() > Constants.MAX_BALANCE_NQT + || attachment.getAssetId() == 0) { + throw new NxtException.NotValidException("Invalid asset order placement: " + attachment.getJSONObject()); + } + Asset asset = Asset.getAsset(attachment.getAssetId()); + if (attachment.getQuantityQNT() <= 0 || (asset != null && attachment.getQuantityQNT() > asset.getQuantityQNT())) { + throw new NxtException.NotValidException("Invalid asset order placement asset or quantity: " + attachment.getJSONObject()); + } + if (asset == null) { + throw new NxtException.NotCurrentlyValidException("Asset " + Convert.toUnsignedLong(attachment.getAssetId()) + + " does not exist yet"); + } + } + + @Override + final public boolean hasRecipient() { + return false; + } + + } + + public static final TransactionType ASK_ORDER_PLACEMENT = new ColoredCoins.ColoredCoinsOrderPlacement() { + + @Override + public final byte getSubtype() { + return TransactionType.SUBTYPE_COLORED_COINS_ASK_ORDER_PLACEMENT; + } + + @Override + Attachment.ColoredCoinsAskOrderPlacement parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { + return new Attachment.ColoredCoinsAskOrderPlacement(buffer, transactionVersion); + } + + @Override + Attachment.ColoredCoinsAskOrderPlacement parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { + return new Attachment.ColoredCoinsAskOrderPlacement(attachmentData); + } + + @Override + boolean applyAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { + Attachment.ColoredCoinsAskOrderPlacement attachment = (Attachment.ColoredCoinsAskOrderPlacement) transaction.getAttachment(); + long unconfirmedAssetBalance = senderAccount.getUnconfirmedAssetBalanceQNT(attachment.getAssetId()); + if (unconfirmedAssetBalance >= 0 && unconfirmedAssetBalance >= attachment.getQuantityQNT()) { + senderAccount.addToUnconfirmedAssetBalanceQNT(attachment.getAssetId(), -attachment.getQuantityQNT()); + return true; + } + return false; + } + + @Override + void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { + Attachment.ColoredCoinsAskOrderPlacement attachment = (Attachment.ColoredCoinsAskOrderPlacement) transaction.getAttachment(); + if (Asset.getAsset(attachment.getAssetId()) != null) { + Order.Ask.addOrder(transaction, attachment); + } + } + + @Override + void undoAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { + Attachment.ColoredCoinsAskOrderPlacement attachment = (Attachment.ColoredCoinsAskOrderPlacement) transaction.getAttachment(); + senderAccount.addToUnconfirmedAssetBalanceQNT(attachment.getAssetId(), attachment.getQuantityQNT()); + } + + }; + + public final static TransactionType BID_ORDER_PLACEMENT = new ColoredCoins.ColoredCoinsOrderPlacement() { + + @Override + public final byte getSubtype() { + return TransactionType.SUBTYPE_COLORED_COINS_BID_ORDER_PLACEMENT; + } + + @Override + Attachment.ColoredCoinsBidOrderPlacement parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { + return new Attachment.ColoredCoinsBidOrderPlacement(buffer, transactionVersion); + } + + @Override + Attachment.ColoredCoinsBidOrderPlacement parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { + return new Attachment.ColoredCoinsBidOrderPlacement(attachmentData); + } + + @Override + boolean applyAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { + Attachment.ColoredCoinsBidOrderPlacement attachment = (Attachment.ColoredCoinsBidOrderPlacement) transaction.getAttachment(); + if (senderAccount.getUnconfirmedBalanceNQT() >= Convert.safeMultiply(attachment.getQuantityQNT(), attachment.getPriceNQT())) { + senderAccount.addToUnconfirmedBalanceNQT(-Convert.safeMultiply(attachment.getQuantityQNT(), attachment.getPriceNQT())); + return true; + } + return false; + } + + @Override + void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { + Attachment.ColoredCoinsBidOrderPlacement attachment = (Attachment.ColoredCoinsBidOrderPlacement) transaction.getAttachment(); + if (Asset.getAsset(attachment.getAssetId()) != null) { + Order.Bid.addOrder(transaction, attachment); + } + } + + @Override + void undoAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { + Attachment.ColoredCoinsBidOrderPlacement attachment = (Attachment.ColoredCoinsBidOrderPlacement) transaction.getAttachment(); + senderAccount.addToUnconfirmedBalanceNQT(Convert.safeMultiply(attachment.getQuantityQNT(), attachment.getPriceNQT())); + } + + }; + + abstract static class ColoredCoinsOrderCancellation extends ColoredCoins { + + @Override + final boolean applyAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { + return true; + } + + @Override + final void undoAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { + } + + @Override + public boolean hasRecipient() { + return false; + } + + } + + public static final TransactionType ASK_ORDER_CANCELLATION = new ColoredCoins.ColoredCoinsOrderCancellation() { + + @Override + public final byte getSubtype() { + return TransactionType.SUBTYPE_COLORED_COINS_ASK_ORDER_CANCELLATION; + } + + @Override + Attachment.ColoredCoinsAskOrderCancellation parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { + return new Attachment.ColoredCoinsAskOrderCancellation(buffer, transactionVersion); + } + + @Override + Attachment.ColoredCoinsAskOrderCancellation parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { + return new Attachment.ColoredCoinsAskOrderCancellation(attachmentData); + } + + @Override + void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { + Attachment.ColoredCoinsAskOrderCancellation attachment = (Attachment.ColoredCoinsAskOrderCancellation) transaction.getAttachment(); + Order order = Order.Ask.getAskOrder(attachment.getOrderId()); + Order.Ask.removeOrder(attachment.getOrderId()); + if (order != null) { + senderAccount.addToUnconfirmedAssetBalanceQNT(order.getAssetId(), order.getQuantityQNT()); + } + } + + @Override + void validateAttachment(Transaction transaction) throws NxtException.ValidationException { + Attachment.ColoredCoinsAskOrderCancellation attachment = (Attachment.ColoredCoinsAskOrderCancellation) transaction.getAttachment(); + Order ask = Order.Ask.getAskOrder(attachment.getOrderId()); + if(ask == null) { + throw new NxtException.NotCurrentlyValidException("Invalid ask order: " + Convert.toUnsignedLong(attachment.getOrderId())); + } + if(ask.getAccountId() != transaction.getSenderId()) { + throw new NxtException.NotValidException("Order " + Convert.toUnsignedLong(attachment.getOrderId()) + " was created by account " + + Convert.toUnsignedLong(ask.getAccountId())); + } + } + + }; + + public static final TransactionType BID_ORDER_CANCELLATION = new ColoredCoins.ColoredCoinsOrderCancellation() { + + @Override + public final byte getSubtype() { + return TransactionType.SUBTYPE_COLORED_COINS_BID_ORDER_CANCELLATION; + } + + @Override + Attachment.ColoredCoinsBidOrderCancellation parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { + return new Attachment.ColoredCoinsBidOrderCancellation(buffer, transactionVersion); + } + + @Override + Attachment.ColoredCoinsBidOrderCancellation parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { + return new Attachment.ColoredCoinsBidOrderCancellation(attachmentData); + } + + @Override + void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { + Attachment.ColoredCoinsBidOrderCancellation attachment = (Attachment.ColoredCoinsBidOrderCancellation) transaction.getAttachment(); + Order order = Order.Bid.getBidOrder(attachment.getOrderId()); + Order.Bid.removeOrder(attachment.getOrderId()); + if (order != null) { + senderAccount.addToUnconfirmedBalanceNQT(Convert.safeMultiply(order.getQuantityQNT(), order.getPriceNQT())); + } + } + + @Override + void validateAttachment(Transaction transaction) throws NxtException.ValidationException { + Attachment.ColoredCoinsBidOrderCancellation attachment = (Attachment.ColoredCoinsBidOrderCancellation) transaction.getAttachment(); + Order bid = Order.Bid.getBidOrder(attachment.getOrderId()); + if(bid == null) { + throw new NxtException.NotCurrentlyValidException("Invalid bid order: " + Convert.toUnsignedLong(attachment.getOrderId())); + } + if(bid.getAccountId() != transaction.getSenderId()) { + throw new NxtException.NotValidException("Order " + Convert.toUnsignedLong(attachment.getOrderId()) + " was created by account " + + Convert.toUnsignedLong(bid.getAccountId())); + } + } + + }; + } + + public static abstract class DigitalGoods extends TransactionType { + + private DigitalGoods() { + } + + @Override + public final byte getType() { + return TransactionType.TYPE_DIGITAL_GOODS; + } + + @Override + boolean applyAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { + return true; + } + + @Override + void undoAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { + } + + @Override + final void validateAttachment(Transaction transaction) throws NxtException.ValidationException { + if (Nxt.getBlockchain().getLastBlock().getHeight() < Constants.DIGITAL_GOODS_STORE_BLOCK) { + throw new NxtException.NotYetEnabledException("Digital goods listing not yet enabled at height " + Nxt.getBlockchain().getLastBlock().getHeight()); + } + if (transaction.getAmountNQT() != 0) { + throw new NxtException.NotValidException("Invalid digital goods transaction"); + } + doValidateAttachment(transaction); + } + + abstract void doValidateAttachment(Transaction transaction) throws NxtException.ValidationException; + + + public static final TransactionType LISTING = new DigitalGoods() { + + @Override + public final byte getSubtype() { + return TransactionType.SUBTYPE_DIGITAL_GOODS_LISTING; + } + + @Override + Attachment.DigitalGoodsListing parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { + return new Attachment.DigitalGoodsListing(buffer, transactionVersion); + } + + @Override + Attachment.DigitalGoodsListing parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { + return new Attachment.DigitalGoodsListing(attachmentData); + } + + @Override + void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { + Attachment.DigitalGoodsListing attachment = (Attachment.DigitalGoodsListing) transaction.getAttachment(); + DigitalGoodsStore.listGoods(transaction, attachment); + } + + @Override + void doValidateAttachment(Transaction transaction) throws NxtException.ValidationException { + Attachment.DigitalGoodsListing attachment = (Attachment.DigitalGoodsListing) transaction.getAttachment(); + if (attachment.getName().length() == 0 + || attachment.getName().length() > Constants.MAX_DGS_LISTING_NAME_LENGTH + || attachment.getDescription().length() > Constants.MAX_DGS_LISTING_DESCRIPTION_LENGTH + || attachment.getTags().length() > Constants.MAX_DGS_LISTING_TAGS_LENGTH + || attachment.getQuantity() < 0 || attachment.getQuantity() > Constants.MAX_DGS_LISTING_QUANTITY + || attachment.getPriceNQT() <= 0 || attachment.getPriceNQT() > Constants.MAX_BALANCE_NQT) { + throw new NxtException.NotValidException("Invalid digital goods listing: " + attachment.getJSONObject()); + } + } + + @Override + public boolean hasRecipient() { + return false; + } + + }; + + public static final TransactionType DELISTING = new DigitalGoods() { + + @Override + public final byte getSubtype() { + return TransactionType.SUBTYPE_DIGITAL_GOODS_DELISTING; + } + + @Override + Attachment.DigitalGoodsDelisting parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { + return new Attachment.DigitalGoodsDelisting(buffer, transactionVersion); + } + + @Override + Attachment.DigitalGoodsDelisting parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { + return new Attachment.DigitalGoodsDelisting(attachmentData); + } + + @Override + void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { + Attachment.DigitalGoodsDelisting attachment = (Attachment.DigitalGoodsDelisting) transaction.getAttachment(); + DigitalGoodsStore.delistGoods(attachment.getGoodsId()); + } + + @Override + void doValidateAttachment(Transaction transaction) throws NxtException.ValidationException { + Attachment.DigitalGoodsDelisting attachment = (Attachment.DigitalGoodsDelisting) transaction.getAttachment(); + DigitalGoodsStore.Goods goods = DigitalGoodsStore.getGoods(attachment.getGoodsId()); + if (goods != null && transaction.getSenderId() != goods.getSellerId()) { + throw new NxtException.NotValidException("Invalid digital goods delisting - seller is different: " + attachment.getJSONObject()); + } + if (goods == null || goods.isDelisted()) { + throw new NxtException.NotCurrentlyValidException("Goods " + Convert.toUnsignedLong(attachment.getGoodsId()) + + "not yet listed or already delisted"); + } + } + + @Override + boolean isDuplicate(Transaction transaction, Map> duplicates) { + Attachment.DigitalGoodsDelisting attachment = (Attachment.DigitalGoodsDelisting) transaction.getAttachment(); + return isDuplicate(DigitalGoods.DELISTING, Convert.toUnsignedLong(attachment.getGoodsId()), duplicates); + } + + @Override + public boolean hasRecipient() { + return false; + } + + }; + + public static final TransactionType PRICE_CHANGE = new DigitalGoods() { + + @Override + public final byte getSubtype() { + return TransactionType.SUBTYPE_DIGITAL_GOODS_PRICE_CHANGE; + } + + @Override + Attachment.DigitalGoodsPriceChange parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { + return new Attachment.DigitalGoodsPriceChange(buffer, transactionVersion); + } + + @Override + Attachment.DigitalGoodsPriceChange parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { + return new Attachment.DigitalGoodsPriceChange(attachmentData); + } + + @Override + void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { + Attachment.DigitalGoodsPriceChange attachment = (Attachment.DigitalGoodsPriceChange) transaction.getAttachment(); + DigitalGoodsStore.changePrice(attachment.getGoodsId(), attachment.getPriceNQT()); + } + + @Override + void doValidateAttachment(Transaction transaction) throws NxtException.ValidationException { + Attachment.DigitalGoodsPriceChange attachment = (Attachment.DigitalGoodsPriceChange) transaction.getAttachment(); + DigitalGoodsStore.Goods goods = DigitalGoodsStore.getGoods(attachment.getGoodsId()); + if (attachment.getPriceNQT() <= 0 || attachment.getPriceNQT() > Constants.MAX_BALANCE_NQT + || (goods != null && transaction.getSenderId() != goods.getSellerId())) { + throw new NxtException.NotValidException("Invalid digital goods price change: " + attachment.getJSONObject()); + } + if (goods == null || goods.isDelisted()) { + throw new NxtException.NotCurrentlyValidException("Goods " + Convert.toUnsignedLong(attachment.getGoodsId()) + + "not yet listed or already delisted"); + } + } + + @Override + boolean isDuplicate(Transaction transaction, Map> duplicates) { + Attachment.DigitalGoodsPriceChange attachment = (Attachment.DigitalGoodsPriceChange) transaction.getAttachment(); + // not a bug, uniqueness is based on DigitalGoods.DELISTING + return isDuplicate(DigitalGoods.DELISTING, Convert.toUnsignedLong(attachment.getGoodsId()), duplicates); + } + + @Override + public boolean hasRecipient() { + return false; + } + + }; + + public static final TransactionType QUANTITY_CHANGE = new DigitalGoods() { + + @Override + public final byte getSubtype() { + return TransactionType.SUBTYPE_DIGITAL_GOODS_QUANTITY_CHANGE; + } + + @Override + Attachment.DigitalGoodsQuantityChange parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { + return new Attachment.DigitalGoodsQuantityChange(buffer, transactionVersion); + } + + @Override + Attachment.DigitalGoodsQuantityChange parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { + return new Attachment.DigitalGoodsQuantityChange(attachmentData); + } + + @Override + void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { + Attachment.DigitalGoodsQuantityChange attachment = (Attachment.DigitalGoodsQuantityChange) transaction.getAttachment(); + DigitalGoodsStore.changeQuantity(attachment.getGoodsId(), attachment.getDeltaQuantity()); + } + + @Override + void doValidateAttachment(Transaction transaction) throws NxtException.ValidationException { + Attachment.DigitalGoodsQuantityChange attachment = (Attachment.DigitalGoodsQuantityChange) transaction.getAttachment(); + DigitalGoodsStore.Goods goods = DigitalGoodsStore.getGoods(attachment.getGoodsId()); + if (attachment.getDeltaQuantity() < -Constants.MAX_DGS_LISTING_QUANTITY + || attachment.getDeltaQuantity() > Constants.MAX_DGS_LISTING_QUANTITY + || (goods != null && transaction.getSenderId() != goods.getSellerId())) { + throw new NxtException.NotValidException("Invalid digital goods quantity change: " + attachment.getJSONObject()); + } + if (goods == null || goods.isDelisted()) { + throw new NxtException.NotCurrentlyValidException("Goods " + Convert.toUnsignedLong(attachment.getGoodsId()) + + "not yet listed or already delisted"); + } + } + + @Override + boolean isDuplicate(Transaction transaction, Map> duplicates) { + Attachment.DigitalGoodsQuantityChange attachment = (Attachment.DigitalGoodsQuantityChange) transaction.getAttachment(); + // not a bug, uniqueness is based on DigitalGoods.DELISTING + return isDuplicate(DigitalGoods.DELISTING, Convert.toUnsignedLong(attachment.getGoodsId()), duplicates); + } + + @Override + public boolean hasRecipient() { + return false; + } + + }; + + public static final TransactionType PURCHASE = new DigitalGoods() { + + @Override + public final byte getSubtype() { + return TransactionType.SUBTYPE_DIGITAL_GOODS_PURCHASE; + } + + @Override + Attachment.DigitalGoodsPurchase parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { + return new Attachment.DigitalGoodsPurchase(buffer, transactionVersion); + } + + @Override + Attachment.DigitalGoodsPurchase parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { + return new Attachment.DigitalGoodsPurchase(attachmentData); + } + + @Override + boolean applyAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { + Attachment.DigitalGoodsPurchase attachment = (Attachment.DigitalGoodsPurchase) transaction.getAttachment(); + if (senderAccount.getUnconfirmedBalanceNQT() >= Convert.safeMultiply(attachment.getQuantity(), attachment.getPriceNQT())) { + senderAccount.addToUnconfirmedBalanceNQT(-Convert.safeMultiply(attachment.getQuantity(), attachment.getPriceNQT())); + return true; + } + return false; + } + + @Override + void undoAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { + Attachment.DigitalGoodsPurchase attachment = (Attachment.DigitalGoodsPurchase) transaction.getAttachment(); + senderAccount.addToUnconfirmedBalanceNQT(Convert.safeMultiply(attachment.getQuantity(), attachment.getPriceNQT())); + } + + @Override + void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { + Attachment.DigitalGoodsPurchase attachment = (Attachment.DigitalGoodsPurchase) transaction.getAttachment(); + DigitalGoodsStore.purchase(transaction, attachment); + } + + @Override + void doValidateAttachment(Transaction transaction) throws NxtException.ValidationException { + Attachment.DigitalGoodsPurchase attachment = (Attachment.DigitalGoodsPurchase) transaction.getAttachment(); + DigitalGoodsStore.Goods goods = DigitalGoodsStore.getGoods(attachment.getGoodsId()); + if (attachment.getQuantity() <= 0 || attachment.getQuantity() > Constants.MAX_DGS_LISTING_QUANTITY + || attachment.getPriceNQT() <= 0 || attachment.getPriceNQT() > Constants.MAX_BALANCE_NQT + || (goods != null && goods.getSellerId() != transaction.getRecipientId())) { + throw new NxtException.NotValidException("Invalid digital goods purchase: " + attachment.getJSONObject()); + } + if (transaction.getEncryptedMessage() != null && ! transaction.getEncryptedMessage().isText()) { + throw new NxtException.NotValidException("Only text encrypted messages allowed"); + } + if (goods == null || goods.isDelisted()) { + throw new NxtException.NotCurrentlyValidException("Goods " + Convert.toUnsignedLong(attachment.getGoodsId()) + + "not yet listed or already delisted"); + } + if (attachment.getQuantity() > goods.getQuantity() || attachment.getPriceNQT() != goods.getPriceNQT()) { + throw new NxtException.NotCurrentlyValidException("Goods price or quantity changed: " + attachment.getJSONObject()); + } + if (attachment.getDeliveryDeadlineTimestamp() <= Nxt.getBlockchain().getLastBlock().getTimestamp()) { + throw new NxtException.NotCurrentlyValidException("Delivery deadline has already expired: " + attachment.getDeliveryDeadlineTimestamp()); + } + } + + @Override + public boolean hasRecipient() { + return true; + } + + }; + + public static final TransactionType DELIVERY = new DigitalGoods() { + + @Override + public final byte getSubtype() { + return TransactionType.SUBTYPE_DIGITAL_GOODS_DELIVERY; + } + + @Override + Attachment.DigitalGoodsDelivery parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { + return new Attachment.DigitalGoodsDelivery(buffer, transactionVersion); + } + + @Override + Attachment.DigitalGoodsDelivery parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { + return new Attachment.DigitalGoodsDelivery(attachmentData); + } + + @Override + void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { + Attachment.DigitalGoodsDelivery attachment = (Attachment.DigitalGoodsDelivery)transaction.getAttachment(); + DigitalGoodsStore.deliver(transaction, attachment); + } + + @Override + void doValidateAttachment(Transaction transaction) throws NxtException.ValidationException { + Attachment.DigitalGoodsDelivery attachment = (Attachment.DigitalGoodsDelivery) transaction.getAttachment(); + DigitalGoodsStore.Purchase purchase = DigitalGoodsStore.getPendingPurchase(attachment.getPurchaseId()); + if (attachment.getGoods().getData().length > Constants.MAX_DGS_GOODS_LENGTH + || attachment.getGoods().getData().length == 0 + || attachment.getGoods().getNonce().length != 32 + || attachment.getDiscountNQT() < 0 || attachment.getDiscountNQT() > Constants.MAX_BALANCE_NQT + || (purchase != null && + (purchase.getBuyerId() != transaction.getRecipientId() + || transaction.getSenderId() != purchase.getSellerId() + || attachment.getDiscountNQT() > Convert.safeMultiply(purchase.getPriceNQT(), purchase.getQuantity())))) { + throw new NxtException.NotValidException("Invalid digital goods delivery: " + attachment.getJSONObject()); + } + if (purchase == null || purchase.getEncryptedGoods() != null) { + throw new NxtException.NotCurrentlyValidException("Purchase does not exist yet, or already delivered: " + + attachment.getJSONObject()); + } + } + + @Override + boolean isDuplicate(Transaction transaction, Map> duplicates) { + Attachment.DigitalGoodsDelivery attachment = (Attachment.DigitalGoodsDelivery) transaction.getAttachment(); + return isDuplicate(DigitalGoods.DELIVERY, Convert.toUnsignedLong(attachment.getPurchaseId()), duplicates); + } + + @Override + public boolean hasRecipient() { + return true; + } + + }; + + public static final TransactionType FEEDBACK = new DigitalGoods() { + + @Override + public final byte getSubtype() { + return TransactionType.SUBTYPE_DIGITAL_GOODS_FEEDBACK; + } + + @Override + Attachment.DigitalGoodsFeedback parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { + return new Attachment.DigitalGoodsFeedback(buffer, transactionVersion); + } + + @Override + Attachment.DigitalGoodsFeedback parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { + return new Attachment.DigitalGoodsFeedback(attachmentData); + } + + @Override + void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { + Attachment.DigitalGoodsFeedback attachment = (Attachment.DigitalGoodsFeedback)transaction.getAttachment(); + DigitalGoodsStore.feedback(attachment.getPurchaseId(), transaction.getEncryptedMessage(), transaction.getMessage()); + } + + @Override + void doValidateAttachment(Transaction transaction) throws NxtException.ValidationException { + Attachment.DigitalGoodsFeedback attachment = (Attachment.DigitalGoodsFeedback) transaction.getAttachment(); + DigitalGoodsStore.Purchase purchase = DigitalGoodsStore.getPurchase(attachment.getPurchaseId()); + if (purchase != null && + (purchase.getSellerId() != transaction.getRecipientId() + || transaction.getSenderId() != purchase.getBuyerId())) { + throw new NxtException.NotValidException("Invalid digital goods feedback: " + attachment.getJSONObject()); + } + if (transaction.getEncryptedMessage() == null && transaction.getMessage() == null) { + throw new NxtException.NotValidException("Missing feedback message"); + } + if (transaction.getEncryptedMessage() != null && ! transaction.getEncryptedMessage().isText()) { + throw new NxtException.NotValidException("Only text encrypted messages allowed"); + } + if (transaction.getMessage() != null && ! transaction.getMessage().isText()) { + throw new NxtException.NotValidException("Only text public messages allowed"); + } + if (purchase == null || purchase.getEncryptedGoods() == null) { + throw new NxtException.NotCurrentlyValidException("Purchase does not exist yet or not yet delivered"); + } + } + + @Override + boolean isDuplicate(Transaction transaction, Map> duplicates) { + Attachment.DigitalGoodsFeedback attachment = (Attachment.DigitalGoodsFeedback) transaction.getAttachment(); + return isDuplicate(DigitalGoods.FEEDBACK, Convert.toUnsignedLong(attachment.getPurchaseId()), duplicates); + } + + @Override + public boolean hasRecipient() { + return true; + } + + }; + + public static final TransactionType REFUND = new DigitalGoods() { + + @Override + public final byte getSubtype() { + return TransactionType.SUBTYPE_DIGITAL_GOODS_REFUND; + } + + @Override + Attachment.DigitalGoodsRefund parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { + return new Attachment.DigitalGoodsRefund(buffer, transactionVersion); + } + + @Override + Attachment.DigitalGoodsRefund parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { + return new Attachment.DigitalGoodsRefund(attachmentData); + } + + @Override + boolean applyAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { + Attachment.DigitalGoodsRefund attachment = (Attachment.DigitalGoodsRefund) transaction.getAttachment(); + if (senderAccount.getUnconfirmedBalanceNQT() >= attachment.getRefundNQT()) { + senderAccount.addToUnconfirmedBalanceNQT(-attachment.getRefundNQT()); + return true; + } + return false; + } + + @Override + void undoAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { + Attachment.DigitalGoodsRefund attachment = (Attachment.DigitalGoodsRefund) transaction.getAttachment(); + senderAccount.addToUnconfirmedBalanceNQT(attachment.getRefundNQT()); + } + + @Override + void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { + Attachment.DigitalGoodsRefund attachment = (Attachment.DigitalGoodsRefund) transaction.getAttachment(); + DigitalGoodsStore.refund(transaction.getSenderId(), attachment.getPurchaseId(), + attachment.getRefundNQT(), transaction.getEncryptedMessage()); + } + + @Override + void doValidateAttachment(Transaction transaction) throws NxtException.ValidationException { + Attachment.DigitalGoodsRefund attachment = (Attachment.DigitalGoodsRefund) transaction.getAttachment(); + DigitalGoodsStore.Purchase purchase = DigitalGoodsStore.getPurchase(attachment.getPurchaseId()); + if (attachment.getRefundNQT() < 0 || attachment.getRefundNQT() > Constants.MAX_BALANCE_NQT + || (purchase != null && + (purchase.getBuyerId() != transaction.getRecipientId() + || transaction.getSenderId() != purchase.getSellerId()))) { + throw new NxtException.NotValidException("Invalid digital goods refund: " + attachment.getJSONObject()); + } + if (transaction.getEncryptedMessage() != null && ! transaction.getEncryptedMessage().isText()) { + throw new NxtException.NotValidException("Only text encrypted messages allowed"); + } + if (purchase == null || purchase.getEncryptedGoods() == null || purchase.getRefundNQT() != 0) { + throw new NxtException.NotCurrentlyValidException("Purchase does not exist or is not delivered or is already refunded"); + } + } + + @Override + boolean isDuplicate(Transaction transaction, Map> duplicates) { + Attachment.DigitalGoodsRefund attachment = (Attachment.DigitalGoodsRefund) transaction.getAttachment(); + return isDuplicate(DigitalGoods.REFUND, Convert.toUnsignedLong(attachment.getPurchaseId()), duplicates); + } + + @Override + public boolean hasRecipient() { + return true; + } + + }; + + } + + public static abstract class AccountControl extends TransactionType { + + private AccountControl() { + } + + @Override + public final byte getType() { + return TransactionType.TYPE_ACCOUNT_CONTROL; + } + + @Override + final boolean applyAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { + return true; + } + + @Override + final void undoAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { + } + + public static final TransactionType EFFECTIVE_BALANCE_LEASING = new AccountControl() { + + @Override + public final byte getSubtype() { + return TransactionType.SUBTYPE_ACCOUNT_CONTROL_EFFECTIVE_BALANCE_LEASING; + } + + @Override + Attachment.AccountControlEffectiveBalanceLeasing parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { + return new Attachment.AccountControlEffectiveBalanceLeasing(buffer, transactionVersion); + } + + @Override + Attachment.AccountControlEffectiveBalanceLeasing parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { + return new Attachment.AccountControlEffectiveBalanceLeasing(attachmentData); + } + + @Override + void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { + Attachment.AccountControlEffectiveBalanceLeasing attachment = (Attachment.AccountControlEffectiveBalanceLeasing) transaction.getAttachment(); + Account.getAccount(transaction.getSenderId()).leaseEffectiveBalance(transaction.getRecipientId(), attachment.getPeriod()); + } + + @Override + void validateAttachment(Transaction transaction) throws NxtException.ValidationException { + Attachment.AccountControlEffectiveBalanceLeasing attachment = (Attachment.AccountControlEffectiveBalanceLeasing)transaction.getAttachment(); + Account recipientAccount = Account.getAccount(transaction.getRecipientId()); + if (transaction.getSenderId() == transaction.getRecipientId() + || transaction.getAmountNQT() != 0 + || attachment.getPeriod() < 1440) { + throw new NxtException.NotValidException("Invalid effective balance leasing: " + + transaction.getJSONObject() + " transaction " + transaction.getStringId()); + } + if (recipientAccount == null + || (recipientAccount.getPublicKey() == null && ! transaction.getStringId().equals("5081403377391821646"))) { + throw new NxtException.NotCurrentlyValidException("Invalid effective balance leasing: " + + " recipient account " + transaction.getRecipientId() + " not found or no public key published"); + } + } + + @Override + public boolean hasRecipient() { + return true; + } + + }; + + } + + public static abstract class BurstMining extends TransactionType { + + private BurstMining() {} + + @Override + public final byte getType() { + return TransactionType.TYPE_BURST_MINING; + } + + @Override + final boolean applyAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { + return true; + } + + @Override + final void undoAttachmentUnconfirmed(Transaction transaction, Account senderAccount) {} + + public static final TransactionType REWARD_RECIPIENT_ASSIGNMENT = new BurstMining() { + + @Override + public final byte getSubtype() { + return TransactionType.SUBTYPE_BURST_MINING_REWARD_RECIPIENT_ASSIGNMENT; + } + + @Override + Attachment.BurstMiningRewardRecipientAssignment parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { + return new Attachment.BurstMiningRewardRecipientAssignment(buffer, transactionVersion); + } + + @Override + Attachment.BurstMiningRewardRecipientAssignment parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { + return new Attachment.BurstMiningRewardRecipientAssignment(attachmentData); + } + + @Override + void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { + senderAccount.setRewardRecipientAssignment(recipientAccount.getId()); + } + + @Override + boolean isDuplicate(Transaction transaction, Map> duplicates) { + if(Nxt.getBlockchain().getHeight() < Constants.DIGITAL_GOODS_STORE_BLOCK) { + return false; // sync fails after 7007 without this + } + return isDuplicate(BurstMining.REWARD_RECIPIENT_ASSIGNMENT, Convert.toUnsignedLong(transaction.getSenderId()), duplicates); + } + + @Override + void validateAttachment(Transaction transaction) throws NxtException.ValidationException { + long height = Nxt.getBlockchain().getLastBlock().getHeight() + 1; + Account sender = Account.getAccount(transaction.getSenderId()); + Account.RewardRecipientAssignment rewardAssignment = sender.getRewardRecipientAssignment(); + if(rewardAssignment != null && rewardAssignment.getFromHeight() >= height) { + throw new NxtException.NotValidException("Cannot reassign reward recipient before previous goes into effect: " + transaction.getJSONObject()); + } + Account recip = Account.getAccount(transaction.getRecipientId()); + if(recip == null || recip.getPublicKey() == null) { + throw new NxtException.NotValidException("Reward recipient must have public key saved in blockchain: " + transaction.getJSONObject()); + } + if(transaction.getAmountNQT() != 0 || transaction.getFeeNQT() != Constants.ONE_NXT) { + throw new NxtException.NotValidException("Reward recipient assisnment transaction must have 0 send amount and 1 fee: " + transaction.getJSONObject()); + } + if(height < Constants.BURST_REWARD_RECIPIENT_ASSIGNMENT_START_BLOCK) { + throw new NxtException.NotCurrentlyValidException("Reward recipient assignment not allowed before block " + Constants.BURST_REWARD_RECIPIENT_ASSIGNMENT_START_BLOCK); + } + } + + @Override + public boolean hasRecipient() { + return true; + } + }; + } + + public static abstract class AdvancedPayment extends TransactionType { + + private AdvancedPayment() {} + + @Override + public final byte getType() { + return TransactionType.TYPE_ADVANCED_PAYMENT; + } + + public final static TransactionType ESCROW_CREATION = new AdvancedPayment() { + + @Override + public final byte getSubtype() { + return TransactionType.SUBTYPE_ADVANCED_PAYMENT_ESCROW_CREATION; + } + + @Override + Attachment.AdvancedPaymentEscrowCreation parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { + return new Attachment.AdvancedPaymentEscrowCreation(buffer, transactionVersion); + } + + @Override + Attachment.AdvancedPaymentEscrowCreation parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { + return new Attachment.AdvancedPaymentEscrowCreation(attachmentData); + } + + @Override + final boolean applyAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { + Attachment.AdvancedPaymentEscrowCreation attachment = (Attachment.AdvancedPaymentEscrowCreation) transaction.getAttachment(); + Long totalAmountNQT = Convert.safeAdd(attachment.getAmountNQT(), attachment.getTotalSigners() * Constants.ONE_NXT); + if(senderAccount.getUnconfirmedBalanceNQT() < totalAmountNQT.longValue()) { + return false; + } + senderAccount.addToUnconfirmedBalanceNQT(-totalAmountNQT); + return true; + } + + @Override + final void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { + Attachment.AdvancedPaymentEscrowCreation attachment = (Attachment.AdvancedPaymentEscrowCreation) transaction.getAttachment(); + Long totalAmountNQT = Convert.safeAdd(attachment.getAmountNQT(), attachment.getTotalSigners() * Constants.ONE_NXT); + senderAccount.addToBalanceNQT(-totalAmountNQT); + Collection signers = attachment.getSigners(); + for(Long signer : signers) { + Account.addOrGetAccount(signer).addToBalanceAndUnconfirmedBalanceNQT(Constants.ONE_NXT); + } + Escrow.addEscrowTransaction(senderAccount, + recipientAccount, + transaction.getId(), + attachment.getAmountNQT(), + attachment.getRequiredSigners(), + attachment.getSigners(), + transaction.getTimestamp() + attachment.getDeadline(), + attachment.getDeadlineAction()); + } + + @Override + final void undoAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { + Attachment.AdvancedPaymentEscrowCreation attachment = (Attachment.AdvancedPaymentEscrowCreation) transaction.getAttachment(); + Long totalAmountNQT = Convert.safeAdd(attachment.getAmountNQT(), attachment.getTotalSigners() * Constants.ONE_NXT); + senderAccount.addToUnconfirmedBalanceNQT(totalAmountNQT); + } + + @Override + boolean isDuplicate(Transaction transaction, Map> duplicates) { + return false; + } + + @Override + void validateAttachment(Transaction transaction) throws NxtException.ValidationException { + Attachment.AdvancedPaymentEscrowCreation attachment = (Attachment.AdvancedPaymentEscrowCreation) transaction.getAttachment(); + Long totalAmountNQT = Convert.safeAdd(attachment.getAmountNQT(), transaction.getFeeNQT()); + if(transaction.getSenderId() == transaction.getRecipientId()) { + throw new NxtException.NotValidException("Escrow must have different sender and recipient"); + } + totalAmountNQT = Convert.safeAdd(totalAmountNQT, attachment.getTotalSigners() * Constants.ONE_NXT); + if(transaction.getAmountNQT() != 0) { + throw new NxtException.NotValidException("Transaction sent amount must be 0 for escrow"); + } + if(totalAmountNQT.compareTo(0L) < 0 || + totalAmountNQT.compareTo(Constants.MAX_BALANCE_NQT) > 0) + { + throw new NxtException.NotValidException("Invalid escrow creation amount"); + } + if(transaction.getFeeNQT() < Constants.ONE_NXT) { + throw new NxtException.NotValidException("Escrow transaction must have a fee at least 1 burst"); + } + if(attachment.getRequiredSigners() < 1 || attachment.getRequiredSigners() > 10) { + throw new NxtException.NotValidException("Escrow required signers much be 1 - 10"); + } + if(attachment.getRequiredSigners() > attachment.getTotalSigners()) { + throw new NxtException.NotValidException("Cannot have more required than signers on escrow"); + } + if(attachment.getTotalSigners() < 1 || attachment.getTotalSigners() > 10) { + throw new NxtException.NotValidException("Escrow transaction requires 1 - 10 signers"); + } + if(attachment.getDeadline() < 1 || attachment.getDeadline() > 7776000) { // max deadline 3 months + throw new NxtException.NotValidException("Escrow deadline must be 1 - 7776000 seconds"); + } + if(attachment.getDeadlineAction() == null || attachment.getDeadlineAction() == Escrow.DecisionType.UNDECIDED) { + throw new NxtException.NotValidException("Invalid deadline action for escrow"); + } + if(attachment.getSigners().contains(transaction.getSenderId()) || + attachment.getSigners().contains(transaction.getRecipientId())) { + throw new NxtException.NotValidException("Escrow sender and recipient cannot be signers"); + } + if(!Escrow.isEnabled()) { + throw new NxtException.NotYetEnabledException("Escrow not yet enabled"); + } + } + + @Override + final public boolean hasRecipient() { + return true; + } + }; + + public final static TransactionType ESCROW_SIGN = new AdvancedPayment() { + + @Override + public final byte getSubtype() { + return TransactionType.SUBTYPE_ADVANCED_PAYMENT_ESCROW_SIGN; + } + + @Override + Attachment.AdvancedPaymentEscrowSign parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { + return new Attachment.AdvancedPaymentEscrowSign(buffer, transactionVersion); + } + + @Override + Attachment.AdvancedPaymentEscrowSign parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { + return new Attachment.AdvancedPaymentEscrowSign(attachmentData); + } + + @Override + final boolean applyAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { + return true; + } + + @Override + final void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { + Attachment.AdvancedPaymentEscrowSign attachment = (Attachment.AdvancedPaymentEscrowSign) transaction.getAttachment(); + Escrow escrow = Escrow.getEscrowTransaction(attachment.getEscrowId()); + escrow.sign(senderAccount.getId(), attachment.getDecision()); + } + + @Override + final void undoAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { + } + + @Override + boolean isDuplicate(Transaction transaction, Map> duplicates) { + Attachment.AdvancedPaymentEscrowSign attachment = (Attachment.AdvancedPaymentEscrowSign) transaction.getAttachment(); + String uniqueString = Convert.toUnsignedLong(attachment.getEscrowId()) + ":" + + Convert.toUnsignedLong(transaction.getSenderId()); + return isDuplicate(AdvancedPayment.ESCROW_SIGN, uniqueString, duplicates); + } + + @Override + void validateAttachment(Transaction transaction) throws NxtException.ValidationException { + Attachment.AdvancedPaymentEscrowSign attachment = (Attachment.AdvancedPaymentEscrowSign) transaction.getAttachment(); + if(transaction.getAmountNQT() != 0 || transaction.getFeeNQT() != Constants.ONE_NXT) { + throw new NxtException.NotValidException("Escrow signing must have amount 0 and fee of 1"); + } + if(attachment.getEscrowId() == null || attachment.getDecision() == null) { + throw new NxtException.NotValidException("Escrow signing requires escrow id and decision set"); + } + Escrow escrow = Escrow.getEscrowTransaction(attachment.getEscrowId()); + if(escrow == null) { + throw new NxtException.NotValidException("Escrow transaction not found"); + } + if(!escrow.isIdSigner(transaction.getSenderId()) && + !escrow.getSenderId().equals(transaction.getSenderId()) && + !escrow.getRecipientId().equals(transaction.getSenderId())) { + throw new NxtException.NotValidException("Sender is not a participant in specified escrow"); + } + if(escrow.getSenderId().equals(transaction.getSenderId()) && attachment.getDecision() != Escrow.DecisionType.RELEASE) { + throw new NxtException.NotValidException("Escrow sender can only release"); + } + if(escrow.getRecipientId().equals(transaction.getSenderId()) && attachment.getDecision() != Escrow.DecisionType.REFUND) { + throw new NxtException.NotValidException("Escrow recipient can only refund"); + } + if(!Escrow.isEnabled()) { + throw new NxtException.NotYetEnabledException("Escrow not yet enabled"); + } + } + + @Override + final public boolean hasRecipient() { + return false; + } + }; + + public final static TransactionType ESCROW_RESULT = new AdvancedPayment() { + + @Override + public final byte getSubtype() { + return TransactionType.SUBTYPE_ADVANCED_PAYMENT_ESCROW_RESULT; + } + + @Override + Attachment.AdvancedPaymentEscrowResult parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { + return new Attachment.AdvancedPaymentEscrowResult(buffer, transactionVersion); + } + + @Override + Attachment.AdvancedPaymentEscrowResult parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { + return new Attachment.AdvancedPaymentEscrowResult(attachmentData); + } + + @Override + final boolean applyAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { + return false; + } + + @Override + final void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { + } + + @Override + final void undoAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { + } + + @Override + boolean isDuplicate(Transaction transaction, Map> duplicates) { + return true; + } + + @Override + void validateAttachment(Transaction transaction) throws NxtException.ValidationException { + throw new NxtException.NotValidException("Escrow result never validates"); + } + + @Override + final public boolean hasRecipient() { + return true; + } + + @Override + final public boolean isSigned() { + return false; + } + }; + + public final static TransactionType SUBSCRIPTION_SUBSCRIBE = new AdvancedPayment() { + + @Override + public final byte getSubtype() { + return TransactionType.SUBTYPE_ADVANCED_PAYMENT_SUBSCRIPTION_SUBSCRIBE; + } + + @Override + Attachment.AdvancedPaymentSubscriptionSubscribe parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { + return new Attachment.AdvancedPaymentSubscriptionSubscribe(buffer, transactionVersion); + } + + @Override + Attachment.AdvancedPaymentSubscriptionSubscribe parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { + return new Attachment.AdvancedPaymentSubscriptionSubscribe(attachmentData); + } + + @Override + final boolean applyAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { + return true; + } + + @Override + final void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { + Attachment.AdvancedPaymentSubscriptionSubscribe attachment = (Attachment.AdvancedPaymentSubscriptionSubscribe) transaction.getAttachment(); + Subscription.addSubscription(senderAccount, recipientAccount, transaction.getId(), transaction.getAmountNQT(), transaction.getTimestamp(), attachment.getFrequency()); + } + + @Override + final void undoAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { + } + + @Override + boolean isDuplicate(Transaction transaction, Map> duplicates) { + return false; + } + + @Override + void validateAttachment(Transaction transaction) throws NxtException.ValidationException { + Attachment.AdvancedPaymentSubscriptionSubscribe attachment = (Attachment.AdvancedPaymentSubscriptionSubscribe) transaction.getAttachment(); + if(attachment.getFrequency() == null || + attachment.getFrequency().intValue() < Constants.BURST_SUBSCRIPTION_MIN_FREQ || + attachment.getFrequency().intValue() > Constants.BURST_SUBSCRIPTION_MAX_FREQ) { + throw new NxtException.NotValidException("Invalid subscription frequency"); + } + if(transaction.getAmountNQT() < Constants.ONE_NXT || transaction.getAmountNQT() > Constants.MAX_BALANCE_NQT) { + throw new NxtException.NotValidException("Subscriptions must be at least one burst"); + } + if(transaction.getSenderId() == transaction.getRecipientId()) { + throw new NxtException.NotValidException("Cannot create subscription to same address"); + } + if(!Subscription.isEnabled()) { + throw new NxtException.NotYetEnabledException("Subscriptions not yet enabled"); + } + } + + @Override + final public boolean hasRecipient() { + return true; + } + }; + + public final static TransactionType SUBSCRIPTION_CANCEL = new AdvancedPayment() { + + @Override + public final byte getSubtype() { + return TransactionType.SUBTYPE_ADVANCED_PAYMENT_SUBSCRIPTION_CANCEL; + } + + @Override + Attachment.AdvancedPaymentSubscriptionCancel parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { + return new Attachment.AdvancedPaymentSubscriptionCancel(buffer, transactionVersion); + } + + @Override + Attachment.AdvancedPaymentSubscriptionCancel parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { + return new Attachment.AdvancedPaymentSubscriptionCancel(attachmentData); + } + + @Override + final boolean applyAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { + Attachment.AdvancedPaymentSubscriptionCancel attachment = (Attachment.AdvancedPaymentSubscriptionCancel) transaction.getAttachment(); + Subscription.addRemoval(attachment.getSubscriptionId()); + return true; + } + + @Override + final void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { + Attachment.AdvancedPaymentSubscriptionCancel attachment = (Attachment.AdvancedPaymentSubscriptionCancel) transaction.getAttachment(); + Subscription.removeSubscription(attachment.getSubscriptionId()); + } + + @Override + final void undoAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { + } + + @Override + boolean isDuplicate(Transaction transaction, Map> duplicates) { + Attachment.AdvancedPaymentSubscriptionCancel attachment = (Attachment.AdvancedPaymentSubscriptionCancel) transaction.getAttachment(); + return isDuplicate(AdvancedPayment.SUBSCRIPTION_CANCEL, Convert.toUnsignedLong(attachment.getSubscriptionId()), duplicates); + } + + @Override + void validateAttachment(Transaction transaction) throws NxtException.ValidationException { + Attachment.AdvancedPaymentSubscriptionCancel attachment = (Attachment.AdvancedPaymentSubscriptionCancel) transaction.getAttachment(); + if(attachment.getSubscriptionId() == null) { + throw new NxtException.NotValidException("Subscription cancel must include subscription id"); + } + + Subscription subscription = Subscription.getSubscription(attachment.getSubscriptionId()); + if(subscription == null) { + throw new NxtException.NotValidException("Subscription cancel must contain current subscription id"); + } + + if(!subscription.getSenderId().equals(transaction.getSenderId()) && + !subscription.getRecipientId().equals(transaction.getSenderId())) { + throw new NxtException.NotValidException("Subscription cancel can only be done by participants"); + } + + if(!Subscription.isEnabled()) { + throw new NxtException.NotYetEnabledException("Subscription cancel not yet enabled"); + } + } + + @Override + final public boolean hasRecipient() { + return false; + } + }; + + public final static TransactionType SUBSCRIPTION_PAYMENT = new AdvancedPayment() { + + @Override + public final byte getSubtype() { + return TransactionType.SUBTYPE_ADVANCED_PAYMENT_SUBSCRIPTION_PAYMENT; + } + + @Override + Attachment.AdvancedPaymentSubscriptionPayment parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { + return new Attachment.AdvancedPaymentSubscriptionPayment(buffer, transactionVersion); + } + + @Override + Attachment.AdvancedPaymentSubscriptionPayment parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { + return new Attachment.AdvancedPaymentSubscriptionPayment(attachmentData); + } + + @Override + final boolean applyAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { + return false; + } + + @Override + final void applyAttachment(Transaction transaction, Account senderAccount, Account recipientAccount) { + } + + @Override + final void undoAttachmentUnconfirmed(Transaction transaction, Account senderAccount) { + } + + @Override + boolean isDuplicate(Transaction transaction, Map> duplicates) { + return true; + } + + @Override + void validateAttachment(Transaction transaction) throws NxtException.ValidationException { + throw new NxtException.NotValidException("Subscription payment never validates"); + } + + @Override + final public boolean hasRecipient() { + return true; + } + + @Override + final public boolean isSigned() { + return false; + } + }; + } + + public static abstract class AutomatedTransactions extends TransactionType{ + private AutomatedTransactions() { + + } + + @Override + public final byte getType(){ + return TransactionType.TYPE_AUTOMATED_TRANSACTIONS; + } + + @Override + boolean applyAttachmentUnconfirmed(Transaction transaction,Account senderAccount){ + return true; + } + + @Override + void undoAttachmentUnconfirmed(Transaction transaction, Account senderAccount){ + + } + + @Override + final void validateAttachment(Transaction transaction) throws NxtException.ValidationException { + if (transaction.getAmountNQT() != 0) { + throw new NxtException.NotValidException("Invalid automated transaction transaction"); + } + doValidateAttachment(transaction); + } + + abstract void doValidateAttachment(Transaction transaction) throws NxtException.ValidationException; + + + public static final TransactionType AUTOMATED_TRANSACTION_CREATION = new AutomatedTransactions(){ + + @Override + public byte getSubtype() { + return TransactionType.SUBTYPE_AT_CREATION; + } + + @Override + AbstractAttachment parseAttachment(ByteBuffer buffer, + byte transactionVersion) throws NotValidException { + // TODO Auto-generated method stub + //System.out.println("parsing byte AT attachment"); + AutomatedTransactionsCreation attachment = new Attachment.AutomatedTransactionsCreation(buffer,transactionVersion); + //System.out.println("byte AT attachment parsed"); + return attachment; + } + + @Override + AbstractAttachment parseAttachment(JSONObject attachmentData) + throws NotValidException { + // TODO Auto-generated method stub + //System.out.println("parsing at attachment"); + Attachment.AutomatedTransactionsCreation atCreateAttachment = new Attachment.AutomatedTransactionsCreation(attachmentData); + //System.out.println("attachment parsed"); + return atCreateAttachment; + } + + @Override + void doValidateAttachment(Transaction transaction) + throws ValidationException { + //System.out.println("validating attachment"); + if (Nxt.getBlockchain().getLastBlock().getHeight()< Constants.AUTOMATED_TRANSACTION_BLOCK){ + throw new NxtException.NotYetEnabledException("Automated Transactions not yet enabled at height " + Nxt.getBlockchain().getLastBlock().getHeight()); + } + if (transaction.getSignature() != null && Account.getAccount(transaction.getId()) != null) { + Account existingAccount = Account.getAccount(transaction.getId()); + if(existingAccount.getPublicKey() != null && !Arrays.equals(existingAccount.getPublicKey(), new byte[32])) + throw new NxtException.NotValidException("Account with id already exists"); + } + Attachment.AutomatedTransactionsCreation attachment = (Attachment.AutomatedTransactionsCreation) transaction.getAttachment(); + long totalPages = 0; + try { + totalPages = AT_Controller.checkCreationBytes(attachment.getCreationBytes(), Nxt.getBlockchain().getHeight()); + } + catch(AT_Exception e) { + throw new NxtException.NotCurrentlyValidException("Invalid AT creation bytes", e); + } + long requiredFee = totalPages * AT_Constants.getInstance().COST_PER_PAGE( transaction.getHeight() ); + if (transaction.getFeeNQT() < requiredFee){ + throw new NxtException.NotValidException("Insufficient fee for AT creation. Minimum: " + Convert.toUnsignedLong(requiredFee / Constants.ONE_NXT)); + } + if(Nxt.getBlockchain().getHeight() >= Constants.AT_FIX_BLOCK_3) { + if(attachment.getName().length() > Constants.MAX_AUTOMATED_TRANSACTION_NAME_LENGTH) { + throw new NxtException.NotValidException("Name of automated transaction over size limit"); + } + if(attachment.getDescription().length() > Constants.MAX_AUTOMATED_TRANSACTION_DESCRIPTION_LENGTH) { + throw new NxtException.NotValidException("Description of automated transaction over size limit"); + } + } + //System.out.println("validating success"); + } + + @Override + void applyAttachment(Transaction transaction, + Account senderAccount, Account recipientAccount) { + // TODO Auto-generated method stub + Attachment.AutomatedTransactionsCreation attachment = (Attachment.AutomatedTransactionsCreation) transaction.getAttachment(); + Long atId = transaction.getId(); + //System.out.println("Applying AT attachent"); + AT.addAT( transaction.getId() , transaction.getSenderId() , attachment.getName() , attachment.getDescription() , attachment.getCreationBytes() , transaction.getHeight() ); + //System.out.println("At with id "+atId+" successfully applied"); + } + + + @Override + public boolean hasRecipient() { + // TODO Auto-generated method stub + return false; + } + }; + + public static final TransactionType AT_PAYMENT = new AutomatedTransactions() { + @Override + public final byte getSubtype() { + return TransactionType.SUBTYPE_AT_NXT_PAYMENT; + } + + @Override + AbstractAttachment parseAttachment(ByteBuffer buffer, byte transactionVersion) throws NxtException.NotValidException { + return Attachment.AT_PAYMENT; + } + + @Override + AbstractAttachment parseAttachment(JSONObject attachmentData) throws NxtException.NotValidException { + return Attachment.AT_PAYMENT; + } + + @Override + void doValidateAttachment(Transaction transaction) throws NxtException.ValidationException { + /*if (transaction.getAmountNQT() <= 0 || transaction.getAmountNQT() >= Constants.MAX_BALANCE_NQT) { + throw new NxtException.NotValidException("Invalid ordinary payment"); + }*/ + throw new NxtException.NotValidException("AT payment never validates"); + } + + @Override + void applyAttachment(Transaction transaction, + Account senderAccount, Account recipientAccount) { + // TODO Auto-generated method stub + + } + + + @Override + public boolean hasRecipient() { + return true; + } + + @Override + final public boolean isSigned() { + return false; + } + }; + + } + + long minimumFeeNQT(int height, int appendagesSize) { + if (height < BASELINE_FEE_HEIGHT) { + return 0; // No need to validate fees before baseline block + } + Fee fee; + if (height >= NEXT_FEE_HEIGHT) { + fee = getNextFee(); + } else { + fee = getBaselineFee(); + } + return Convert.safeAdd(fee.getConstantFee(), Convert.safeMultiply(appendagesSize, fee.getAppendagesFee())); + } + + protected Fee getBaselineFee() { + return BASELINE_FEE; + } + + protected Fee getNextFee() { + return NEXT_FEE; + } + + public static final class Fee { + private final long constantFee; + private final long appendagesFee; + + public Fee(long constantFee, long appendagesFee) { + this.constantFee = constantFee; + this.appendagesFee = appendagesFee; + } + + public long getConstantFee() { + return constantFee; + } + + public long getAppendagesFee() { + return appendagesFee; + } + + @Override + public String toString() { + return "Fee{" + + "constantFee=" + constantFee + + ", appendagesFee=" + appendagesFee + + '}'; + } + } + +} diff --git a/src/java/nxt/at/AT_API_Impl.java b/src/java/nxt/at/AT_API_Impl.java index e21f75dad..e1564c963 100644 --- a/src/java/nxt/at/AT_API_Impl.java +++ b/src/java/nxt/at/AT_API_Impl.java @@ -1,813 +1,813 @@ -/* - * Copyright (c) 2014 CIYAM Developers - - Distributed under the MIT/X11 software license, please refer to the file license.txt - in the root project directory or http://www.opensource.org/licenses/mit-license.php. - - */ - -package nxt.at; - -import java.math.BigInteger; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.Arrays; - -import nxt.Constants; -import nxt.util.Convert; -import fr.cryptohash.RIPEMD160; - - - -public class AT_API_Impl implements AT_API -{ - - AT_API_Platform_Impl platform = AT_API_Platform_Impl.getInstance(); - - - @Override - public long get_A1( AT_Machine_State state ) { - return AT_API_Helper.getLong( state.get_A1() ); - } - - @Override - public long get_A2( AT_Machine_State state ) { - return AT_API_Helper.getLong( state.get_A2() ); - } - - @Override - public long get_A3( AT_Machine_State state ) { - return AT_API_Helper.getLong( state.get_A3() ); - } - - @Override - public long get_A4( AT_Machine_State state ) { - return AT_API_Helper.getLong( state.get_A4() ); - } - - @Override - public long get_B1( AT_Machine_State state ) { - return AT_API_Helper.getLong( state.get_B1() ); - } - - @Override - public long get_B2( AT_Machine_State state ) { - return AT_API_Helper.getLong( state.get_B2() ); - } - - @Override - public long get_B3( AT_Machine_State state ) { - return AT_API_Helper.getLong( state.get_B3() ); - } - - @Override - public long get_B4( AT_Machine_State state ) { - return AT_API_Helper.getLong( state.get_B4() ); - } - - @Override - public void set_A1( long val , AT_Machine_State state ) { - state.set_A1( AT_API_Helper.getByteArray( val ) ); - } - - @Override - public void set_A2( long val , AT_Machine_State state ) { - state.set_A2( AT_API_Helper.getByteArray( val ) ); - } - - @Override - public void set_A3( long val , AT_Machine_State state ) { - state.set_A3( AT_API_Helper.getByteArray( val ) ); - } - - @Override - public void set_A4( long val , AT_Machine_State state ) { - state.set_A4( AT_API_Helper.getByteArray( val ) ); - } - - @Override - public void set_A1_A2( long val1 , long val2 , AT_Machine_State state ) { - state.set_A1( AT_API_Helper.getByteArray( val1 ) ); - state.set_A2( AT_API_Helper.getByteArray( val2 ) ); - } - - @Override - public void set_A3_A4( long val1 , long val2 ,AT_Machine_State state ) { - state.set_A3( AT_API_Helper.getByteArray( val1 ) ); - state.set_A4( AT_API_Helper.getByteArray( val2 ) ); - - } - - @Override - public void set_B1( long val , AT_Machine_State state ) { - state.set_B1( AT_API_Helper.getByteArray( val ) ); - } - - @Override - public void set_B2( long val , AT_Machine_State state ) { - state.set_B2( AT_API_Helper.getByteArray( val ) ); - } - - @Override - public void set_B3( long val , AT_Machine_State state ) { - state.set_B3( AT_API_Helper.getByteArray( val ) ); - } - - @Override - public void set_B4( long val , AT_Machine_State state ) { - state.set_B4( AT_API_Helper.getByteArray( val ) ); - } - - @Override - public void set_B1_B2( long val1 , long val2 , AT_Machine_State state ) { - state.set_B1( AT_API_Helper.getByteArray( val1 ) ); - state.set_B2( AT_API_Helper.getByteArray( val2 ) ); - } - - @Override - public void set_B3_B4( long val3 , long val4 , AT_Machine_State state ) { - state.set_B3( AT_API_Helper.getByteArray( val3 ) ); - state.set_B4( AT_API_Helper.getByteArray( val4 ) ); - } - - @Override - public void clear_A( AT_Machine_State state ) { - byte[] b = new byte[ 8 ]; - state.set_A1( b ); - state.set_A2( b ); - state.set_A3( b ); - state.set_A4( b ); - } - - @Override - public void clear_B( AT_Machine_State state ) { - byte[] b = new byte[ 8 ]; - state.set_B1( b ); - state.set_B2( b ); - state.set_B3( b ); - state.set_B4( b ); - } - - @Override - public void copy_A_From_B( AT_Machine_State state ) { - state.set_A1( state.get_B1() ); - state.set_A2( state.get_B2() ); - state.set_A3( state.get_B3() ); - state.set_A4( state.get_B4() ); - } - - @Override - public void copy_B_From_A( AT_Machine_State state ) { - state.set_B1( state.get_A1() ); - state.set_B2( state.get_A2() ); - state.set_B3( state.get_A3() ); - state.set_B4( state.get_A4() ); - } - - @Override - public long check_A_Is_Zero( AT_Machine_State state ) { - byte[] b = new byte[ 8 ]; - return ( Arrays.equals( state.get_A1() , b ) && - Arrays.equals( state.get_A2() , b ) && - Arrays.equals( state.get_A3() , b ) && - Arrays.equals( state.get_A4() , b ) ) ? 0 : 1 ; - } - - @Override - public long check_B_Is_Zero( AT_Machine_State state ) { - byte[] b = new byte[ 8 ]; - return ( Arrays.equals( state.get_B1() , b ) && - Arrays.equals( state.get_B2() , b ) && - Arrays.equals( state.get_B3() , b ) && - Arrays.equals( state.get_B4() , b ) ) ? 0 : 1 ; - } - - public long check_A_equals_B( AT_Machine_State state ) { - return ( Arrays.equals( state.get_A1() , state.get_B1() ) && - Arrays.equals( state.get_A2() , state.get_B2() ) && - Arrays.equals( state.get_A3() , state.get_B3() ) && - Arrays.equals( state.get_A4() , state.get_B4() ) ) ? 1 : 0; - } - - @Override - public void swap_A_and_B( AT_Machine_State state ) { - byte[] b = new byte[ 8 ]; - - b = state.get_A1().clone(); - state.set_A1( state.get_B1() ); - state.set_B1( b ); - - b = state.get_A2().clone(); - state.set_A2( state.get_B2() ); - state.set_B2( b ); - - b = state.get_A3().clone(); - state.set_A3( state.get_B3() ); - state.set_B3( b ); - - b = state.get_A4().clone(); - state.set_A4( state.get_B4() ); - state.set_B4( b ); - - } - - @Override - public void add_A_to_B( AT_Machine_State state ) { - BigInteger a = AT_API_Helper.getBigInteger(state.get_A1(), state.get_A2(), state.get_A3(), state.get_A4()); - BigInteger b = AT_API_Helper.getBigInteger(state.get_B1(), state.get_B2(), state.get_B3(), state.get_B4()); - BigInteger result = a.add(b); - ByteBuffer resultBuffer = ByteBuffer.wrap(AT_API_Helper.getByteArray(result)); - resultBuffer.order(ByteOrder.LITTLE_ENDIAN); - - byte[] temp = new byte[8]; - resultBuffer.get(temp, 0, 8); - state.set_B1(temp); - resultBuffer.get(temp, 0, 8); - state.set_B2(temp); - resultBuffer.get(temp, 0, 8); - state.set_B3(temp); - resultBuffer.get(temp, 0, 8); - state.set_B4(temp); - } - - @Override - public void add_B_to_A( AT_Machine_State state ) { - BigInteger a = AT_API_Helper.getBigInteger(state.get_A1(), state.get_A2(), state.get_A3(), state.get_A4()); - BigInteger b = AT_API_Helper.getBigInteger(state.get_B1(), state.get_B2(), state.get_B3(), state.get_B4()); - BigInteger result = a.add(b); - ByteBuffer resultBuffer = ByteBuffer.wrap(AT_API_Helper.getByteArray(result)); - resultBuffer.order(ByteOrder.LITTLE_ENDIAN); - - byte[] temp = new byte[8]; - resultBuffer.get(temp, 0, 8); - state.set_A1(temp); - resultBuffer.get(temp, 0, 8); - state.set_A2(temp); - resultBuffer.get(temp, 0, 8); - state.set_A3(temp); - resultBuffer.get(temp, 0, 8); - state.set_A4(temp); - } - - @Override - public void sub_A_from_B( AT_Machine_State state ) { - BigInteger a = AT_API_Helper.getBigInteger(state.get_A1(), state.get_A2(), state.get_A3(), state.get_A4()); - BigInteger b = AT_API_Helper.getBigInteger(state.get_B1(), state.get_B2(), state.get_B3(), state.get_B4()); - BigInteger result = b.subtract(a); - ByteBuffer resultBuffer = ByteBuffer.wrap(AT_API_Helper.getByteArray(result)); - resultBuffer.order(ByteOrder.LITTLE_ENDIAN); - - byte[] temp = new byte[8]; - resultBuffer.get(temp, 0, 8); - state.set_B1(temp); - resultBuffer.get(temp, 0, 8); - state.set_B2(temp); - resultBuffer.get(temp, 0, 8); - state.set_B3(temp); - resultBuffer.get(temp, 0, 8); - state.set_B4(temp); - } - - @Override - public void sub_B_from_A( AT_Machine_State state ) { - BigInteger a = AT_API_Helper.getBigInteger(state.get_A1(), state.get_A2(), state.get_A3(), state.get_A4()); - BigInteger b = AT_API_Helper.getBigInteger(state.get_B1(), state.get_B2(), state.get_B3(), state.get_B4()); - BigInteger result = a.subtract(b); - ByteBuffer resultBuffer = ByteBuffer.wrap(AT_API_Helper.getByteArray(result)); - resultBuffer.order(ByteOrder.LITTLE_ENDIAN); - - byte[] temp = new byte[8]; - resultBuffer.get(temp, 0, 8); - state.set_A1(temp); - resultBuffer.get(temp, 0, 8); - state.set_A2(temp); - resultBuffer.get(temp, 0, 8); - state.set_A3(temp); - resultBuffer.get(temp, 0, 8); - state.set_A4(temp); - } - - @Override - public void mul_A_by_B( AT_Machine_State state ) { - BigInteger a = AT_API_Helper.getBigInteger(state.get_A1(), state.get_A2(), state.get_A3(), state.get_A4()); - BigInteger b = AT_API_Helper.getBigInteger(state.get_B1(), state.get_B2(), state.get_B3(), state.get_B4()); - BigInteger result = a.multiply(b); - ByteBuffer resultBuffer = ByteBuffer.wrap(AT_API_Helper.getByteArray(result)); - resultBuffer.order(ByteOrder.LITTLE_ENDIAN); - - byte[] temp = new byte[8]; - resultBuffer.get(temp, 0, 8); - state.set_B1(temp); - resultBuffer.get(temp, 0, 8); - state.set_B2(temp); - resultBuffer.get(temp, 0, 8); - state.set_B3(temp); - resultBuffer.get(temp, 0, 8); - state.set_B4(temp); - } - - @Override - public void mul_B_by_A( AT_Machine_State state ) { - BigInteger a = AT_API_Helper.getBigInteger(state.get_A1(), state.get_A2(), state.get_A3(), state.get_A4()); - BigInteger b = AT_API_Helper.getBigInteger(state.get_B1(), state.get_B2(), state.get_B3(), state.get_B4()); - BigInteger result = a.multiply(b); - ByteBuffer resultBuffer = ByteBuffer.wrap(AT_API_Helper.getByteArray(result)); - resultBuffer.order(ByteOrder.LITTLE_ENDIAN); - - byte[] temp = new byte[8]; - resultBuffer.get(temp, 0, 8); - state.set_A1(temp); - resultBuffer.get(temp, 0, 8); - state.set_A2(temp); - resultBuffer.get(temp, 0, 8); - state.set_A3(temp); - resultBuffer.get(temp, 0, 8); - state.set_A4(temp); - } - - @Override - public void div_A_by_B( AT_Machine_State state ) { - BigInteger a = AT_API_Helper.getBigInteger(state.get_A1(), state.get_A2(), state.get_A3(), state.get_A4()); - BigInteger b = AT_API_Helper.getBigInteger(state.get_B1(), state.get_B2(), state.get_B3(), state.get_B4()); - if(b.compareTo(BigInteger.ZERO) == 0) - return; - BigInteger result = a.divide(b); - ByteBuffer resultBuffer = ByteBuffer.wrap(AT_API_Helper.getByteArray(result)); - resultBuffer.order(ByteOrder.LITTLE_ENDIAN); - - byte[] temp = new byte[8]; - resultBuffer.get(temp, 0, 8); - state.set_B1(temp); - resultBuffer.get(temp, 0, 8); - state.set_B2(temp); - resultBuffer.get(temp, 0, 8); - state.set_B3(temp); - resultBuffer.get(temp, 0, 8); - state.set_B4(temp); - } - - @Override - public void div_B_by_A( AT_Machine_State state ) { - BigInteger a = AT_API_Helper.getBigInteger(state.get_A1(), state.get_A2(), state.get_A3(), state.get_A4()); - BigInteger b = AT_API_Helper.getBigInteger(state.get_B1(), state.get_B2(), state.get_B3(), state.get_B4()); - if(a.compareTo(BigInteger.ZERO) == 0) - return; - BigInteger result = b.divide(a); - ByteBuffer resultBuffer = ByteBuffer.wrap(AT_API_Helper.getByteArray(result)); - resultBuffer.order(ByteOrder.LITTLE_ENDIAN); - - byte[] temp = new byte[8]; - resultBuffer.get(temp, 0, 8); - state.set_A1(temp); - resultBuffer.get(temp, 0, 8); - state.set_A2(temp); - resultBuffer.get(temp, 0, 8); - state.set_A3(temp); - resultBuffer.get(temp, 0, 8); - state.set_A4(temp); - } - - @Override - public void or_A_with_B ( AT_Machine_State state ) { - ByteBuffer a = ByteBuffer.allocate(32); - a.order( ByteOrder.LITTLE_ENDIAN ); - a.put(state.get_A1()); - a.put(state.get_A2()); - a.put(state.get_A3()); - a.put(state.get_A4()); - a.clear(); - - ByteBuffer b = ByteBuffer.allocate(32); - b.order( ByteOrder.LITTLE_ENDIAN ); - b.put(state.get_B1()); - b.put(state.get_B2()); - b.put(state.get_B3()); - b.put(state.get_B4()); - b.clear(); - - state.set_A1(AT_API_Helper.getByteArray(a.getLong(0) | b.getLong(0))); - state.set_A2(AT_API_Helper.getByteArray(a.getLong(8) | b.getLong(8))); - state.set_A3(AT_API_Helper.getByteArray(a.getLong(16) | b.getLong(16))); - state.set_A4(AT_API_Helper.getByteArray(a.getLong(24) | b.getLong(24))); - } - - @Override - public void or_B_with_A ( AT_Machine_State state ) { - ByteBuffer a = ByteBuffer.allocate(32); - a.order( ByteOrder.LITTLE_ENDIAN ); - a.put(state.get_A1()); - a.put(state.get_A2()); - a.put(state.get_A3()); - a.put(state.get_A4()); - a.clear(); - - ByteBuffer b = ByteBuffer.allocate(32); - b.order( ByteOrder.LITTLE_ENDIAN ); - b.put(state.get_B1()); - b.put(state.get_B2()); - b.put(state.get_B3()); - b.put(state.get_B4()); - b.clear(); - - state.set_B1(AT_API_Helper.getByteArray(a.getLong(0) | b.getLong(0))); - state.set_B2(AT_API_Helper.getByteArray(a.getLong(8) | b.getLong(8))); - state.set_B3(AT_API_Helper.getByteArray(a.getLong(16) | b.getLong(16))); - state.set_B4(AT_API_Helper.getByteArray(a.getLong(24) | b.getLong(24))); - } - - @Override - public void and_A_with_B ( AT_Machine_State state ) { - ByteBuffer a = ByteBuffer.allocate(32); - a.order( ByteOrder.LITTLE_ENDIAN ); - a.put(state.get_A1()); - a.put(state.get_A2()); - a.put(state.get_A3()); - a.put(state.get_A4()); - a.clear(); - - ByteBuffer b = ByteBuffer.allocate(32); - b.order( ByteOrder.LITTLE_ENDIAN ); - b.put(state.get_B1()); - b.put(state.get_B2()); - b.put(state.get_B3()); - b.put(state.get_B4()); - b.clear(); - - state.set_A1(AT_API_Helper.getByteArray(a.getLong(0) & b.getLong(0))); - state.set_A2(AT_API_Helper.getByteArray(a.getLong(8) & b.getLong(8))); - state.set_A3(AT_API_Helper.getByteArray(a.getLong(16) & b.getLong(16))); - state.set_A4(AT_API_Helper.getByteArray(a.getLong(24) & b.getLong(24))); - } - - @Override - public void and_B_with_A ( AT_Machine_State state ) { - ByteBuffer a = ByteBuffer.allocate(32); - a.order( ByteOrder.LITTLE_ENDIAN ); - a.put(state.get_A1()); - a.put(state.get_A2()); - a.put(state.get_A3()); - a.put(state.get_A4()); - a.clear(); - - ByteBuffer b = ByteBuffer.allocate(32); - b.order( ByteOrder.LITTLE_ENDIAN ); - b.put(state.get_B1()); - b.put(state.get_B2()); - b.put(state.get_B3()); - b.put(state.get_B4()); - b.clear(); - - state.set_B1(AT_API_Helper.getByteArray(a.getLong(0) & b.getLong(0))); - state.set_B2(AT_API_Helper.getByteArray(a.getLong(8) & b.getLong(8))); - state.set_B3(AT_API_Helper.getByteArray(a.getLong(16) & b.getLong(16))); - state.set_B4(AT_API_Helper.getByteArray(a.getLong(24) & b.getLong(24))); - } - - @Override - public void xor_A_with_B ( AT_Machine_State state ) { - ByteBuffer a = ByteBuffer.allocate(32); - a.order( ByteOrder.LITTLE_ENDIAN ); - a.put(state.get_A1()); - a.put(state.get_A2()); - a.put(state.get_A3()); - a.put(state.get_A4()); - a.clear(); - - ByteBuffer b = ByteBuffer.allocate(32); - b.order( ByteOrder.LITTLE_ENDIAN ); - b.put(state.get_B1()); - b.put(state.get_B2()); - b.put(state.get_B3()); - b.put(state.get_B4()); - b.clear(); - - state.set_A1(AT_API_Helper.getByteArray(a.getLong(0) ^ b.getLong(0))); - state.set_A2(AT_API_Helper.getByteArray(a.getLong(8) ^ b.getLong(8))); - state.set_A3(AT_API_Helper.getByteArray(a.getLong(16) ^ b.getLong(16))); - state.set_A4(AT_API_Helper.getByteArray(a.getLong(24) ^ b.getLong(24))); - } - - @Override - public void xor_B_with_A ( AT_Machine_State state ) { - ByteBuffer a = ByteBuffer.allocate(32); - a.order( ByteOrder.LITTLE_ENDIAN ); - a.put(state.get_A1()); - a.put(state.get_A2()); - a.put(state.get_A3()); - a.put(state.get_A4()); - a.clear(); - - ByteBuffer b = ByteBuffer.allocate(32); - b.order( ByteOrder.LITTLE_ENDIAN ); - b.put(state.get_B1()); - b.put(state.get_B2()); - b.put(state.get_B3()); - b.put(state.get_B4()); - b.clear(); - - state.set_B1(AT_API_Helper.getByteArray(a.getLong(0) ^ b.getLong(0))); - state.set_B2(AT_API_Helper.getByteArray(a.getLong(8) ^ b.getLong(8))); - state.set_B3(AT_API_Helper.getByteArray(a.getLong(16) ^ b.getLong(16))); - state.set_B4(AT_API_Helper.getByteArray(a.getLong(24) ^ b.getLong(24))); - } - - @Override - public void MD5_A_to_B( AT_Machine_State state ) { - ByteBuffer b = ByteBuffer.allocate( 16 ); - b.order( ByteOrder.LITTLE_ENDIAN ); - - b.put( state.get_A1() ); - b.put( state.get_A2() ); - - try { - MessageDigest md5 = MessageDigest.getInstance("MD5"); - ByteBuffer mdb = ByteBuffer.wrap( md5.digest( b.array() ) ); - mdb.order( ByteOrder.LITTLE_ENDIAN ); - - state.set_B1( AT_API_Helper.getByteArray( mdb.getLong(0) ) ); - state.set_B1( AT_API_Helper.getByteArray( mdb.getLong(8) ) ); - - } catch (NoSuchAlgorithmException e) { - //not expected to reach that point - e.printStackTrace(); - } - } - - - @Override - public long check_MD5_A_with_B( AT_Machine_State state ) { - if ( state.getHeight() >= Constants.AT_FIX_BLOCK_3 ) { - ByteBuffer b = ByteBuffer.allocate( 16 ); - b.order( ByteOrder.LITTLE_ENDIAN ); - - b.put( state.get_A1() ); - b.put( state.get_A2() ); - - try { - MessageDigest md5 = MessageDigest.getInstance("MD5"); - ByteBuffer mdb = ByteBuffer.wrap( md5.digest( b.array() ) ); - mdb.order( ByteOrder.LITTLE_ENDIAN ); - - return ( mdb.getLong(0) == AT_API_Helper.getLong( state.get_B1() ) && - mdb.getLong(8) == AT_API_Helper.getLong( state.get_B2() ) ) ? 1 : 0; - } catch (NoSuchAlgorithmException e) { - //not expected to reach that point - e.printStackTrace(); - throw new RuntimeException("Failed to check md5"); - } - } - else { - return ( Arrays.equals( state.get_A1() , state.get_B1() ) && - Arrays.equals( state.get_A2() , state.get_B2() ) ) ? 1 : 0; - } - } - - @Override - public void HASH160_A_to_B( AT_Machine_State state ) { - ByteBuffer b = ByteBuffer.allocate(32); - b.order(ByteOrder.LITTLE_ENDIAN); - - b.put(state.get_A1()); - b.put(state.get_A2()); - b.put(state.get_A3()); - b.put(state.get_A4()); - - RIPEMD160 ripemd160 = new RIPEMD160(); - ByteBuffer ripemdb = ByteBuffer.wrap(ripemd160.digest(b.array())); - ripemdb.order(ByteOrder.LITTLE_ENDIAN); - - state.set_B1(AT_API_Helper.getByteArray(ripemdb.getLong(0))); - state.set_B2(AT_API_Helper.getByteArray(ripemdb.getLong(8))); - state.set_B3(AT_API_Helper.getByteArray((long)ripemdb.getInt(16))); - - } - - @Override - public long check_HASH160_A_with_B( AT_Machine_State state ) { - if ( state.getHeight() >= Constants.AT_FIX_BLOCK_3 ) { - ByteBuffer b = ByteBuffer.allocate( 32 ); - b.order( ByteOrder.LITTLE_ENDIAN ); - - b.put( state.get_A1() ); - b.put( state.get_A2() ); - b.put( state.get_A3() ); - b.put( state.get_A4() ); - - RIPEMD160 ripemd160 = new RIPEMD160(); - ByteBuffer ripemdb = ByteBuffer.wrap( ripemd160.digest( b.array() ) ); - ripemdb.order( ByteOrder.LITTLE_ENDIAN ); - - return ( ripemdb.getLong(0) == AT_API_Helper.getLong( state.get_B1() ) && - ripemdb.getLong(8) == AT_API_Helper.getLong( state.get_B2() ) && - ripemdb.getInt(16) == ((int)(AT_API_Helper.getLong( state.get_B3() ) & 0x00000000FFFFFFFFL )) - ) ? 1 : 0; - } - else { - return(Arrays.equals(state.get_A1(), state.get_B1()) && - Arrays.equals(state.get_A2(), state.get_B2()) && - (AT_API_Helper.getLong(state.get_A3()) & 0x00000000FFFFFFFFL) == (AT_API_Helper.getLong(state.get_B3()) & 0x00000000FFFFFFFFL)) ? 1 : 0; - } - } - - @Override - public void SHA256_A_to_B( AT_Machine_State state ) { - ByteBuffer b = ByteBuffer.allocate( 32 ); - b.order( ByteOrder.LITTLE_ENDIAN ); - - b.put( state.get_A1() ); - b.put( state.get_A2() ); - b.put( state.get_A3() ); - b.put( state.get_A4() ); - - try { - MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); - ByteBuffer shab = ByteBuffer.wrap( sha256.digest( b.array() ) ); - shab.order( ByteOrder.LITTLE_ENDIAN ); - - state.set_B1( AT_API_Helper.getByteArray( shab.getLong( 0 ) ) ); - state.set_B2( AT_API_Helper.getByteArray( shab.getLong( 8 ) ) ); - state.set_B3( AT_API_Helper.getByteArray( shab.getLong( 16 ) ) ); - state.set_B4( AT_API_Helper.getByteArray( shab.getLong( 24 ) ) ); - - } catch (NoSuchAlgorithmException e) { - //not expected to reach that point - e.printStackTrace(); - } - } - - @Override - public long check_SHA256_A_with_B( AT_Machine_State state ) { - if ( state.getHeight() >= Constants.AT_FIX_BLOCK_3 ) { - ByteBuffer b = ByteBuffer.allocate(32); - b.order( ByteOrder.LITTLE_ENDIAN ); - - b.put( state.get_A1() ); - b.put( state.get_A2() ); - b.put( state.get_A3() ); - b.put( state.get_A4() ); - - try { - MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); - ByteBuffer shab = ByteBuffer.wrap( sha256.digest( b.array() ) ); - shab.order( ByteOrder.LITTLE_ENDIAN ); - - return ( shab.getLong(0) == AT_API_Helper.getLong( state.get_B1() ) && - shab.getLong(8) == AT_API_Helper.getLong( state.get_B2() ) && - shab.getLong(16) == AT_API_Helper.getLong( state.get_B3() ) && - shab.getLong(24) == AT_API_Helper.getLong( state.get_B4() ) ) ? 1 : 0; - } catch (NoSuchAlgorithmException e) { - //not expected to reach that point - e.printStackTrace(); - throw new RuntimeException("Failed to check sha256"); - } - } - else { - return ( Arrays.equals( state.get_A1() , state.get_B1() ) && - Arrays.equals( state.get_A2() , state.get_B2() ) && - Arrays.equals( state.get_A3() , state.get_B3() ) && - Arrays.equals( state.get_A4() , state.get_B4() )) ? 1 : 0; - } - } - - @Override - public long get_Block_Timestamp( AT_Machine_State state ) { - return platform.get_Block_Timestamp( state ); - - } - - @Override - public long get_Creation_Timestamp( AT_Machine_State state ) { - return platform.get_Creation_Timestamp( state ); - } - - @Override - public long get_Last_Block_Timestamp( AT_Machine_State state ) { - return platform.get_Last_Block_Timestamp( state ); - } - - @Override - public void put_Last_Block_Hash_In_A( AT_Machine_State state ) { - platform.put_Last_Block_Hash_In_A( state ); - - } - - @Override - public void A_to_Tx_after_Timestamp( long val , AT_Machine_State state ) { - platform.A_to_Tx_after_Timestamp( val , state ); - - } - - @Override - public long get_Type_for_Tx_in_A( AT_Machine_State state ) { - return platform.get_Type_for_Tx_in_A( state ); - } - - @Override - public long get_Amount_for_Tx_in_A( AT_Machine_State state ) { - return platform.get_Amount_for_Tx_in_A( state ); - } - - @Override - public long get_Timestamp_for_Tx_in_A( AT_Machine_State state ) { - return platform.get_Timestamp_for_Tx_in_A( state ); - } - - @Override - public long get_Random_Id_for_Tx_in_A( AT_Machine_State state ) { - return platform.get_Random_Id_for_Tx_in_A( state ); - } - - @Override - public void message_from_Tx_in_A_to_B( AT_Machine_State state ) { - platform.message_from_Tx_in_A_to_B( state ); - } - - @Override - public void B_to_Address_of_Tx_in_A( AT_Machine_State state ) { - - platform.B_to_Address_of_Tx_in_A( state ); - } - - @Override - public void B_to_Address_of_Creator( AT_Machine_State state ) { - platform.B_to_Address_of_Creator( state ); - - } - - @Override - public long get_Current_Balance( AT_Machine_State state ) { - return platform.get_Current_Balance( state ); - } - - @Override - public long get_Previous_Balance( AT_Machine_State state ) { - return platform.get_Previous_Balance( state ); - } - - @Override - public void send_to_Address_in_B( long val , AT_Machine_State state ) { - platform.send_to_Address_in_B( val , state ); - } - - @Override - public void send_All_to_Address_in_B( AT_Machine_State state ) { - platform.send_All_to_Address_in_B( state ); - } - - @Override - public void send_Old_to_Address_in_B( AT_Machine_State state ) { - platform.send_Old_to_Address_in_B( state ); - } - - @Override - public void send_A_to_Address_in_B( AT_Machine_State state ) { - platform.send_A_to_Address_in_B( state ); - } - - @Override - public long add_Minutes_to_Timestamp( long val1 , long val2 , AT_Machine_State state ) { - return platform.add_Minutes_to_Timestamp( val1 , val2 , state ); - } - - @Override - public void set_Min_Activation_Amount( long val , AT_Machine_State state ) { - state.setMinActivationAmount(val); - } - - @Override - public void put_Last_Block_Generation_Signature_In_A( AT_Machine_State state ) { - platform.put_Last_Block_Generation_Signature_In_A( state ); - } - - @Override - public void SHA256_to_B( long val1 , long val2 , AT_Machine_State state ) { - if(val1 < 0 || val2 < 0 || - (val1 + val2 - 1) < 0 || - ((long)val1)*8+8>((long)Integer.MAX_VALUE) || - val1*8+8>state.getDsize() || - ((long)val1 + (long)val2 - 1)*8+8>((long)Integer.MAX_VALUE) || - (val1 + val2 - 1)*8+8>state.getDsize()) - { - return; - } - - try { - MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); - sha256.update(state.getAp_data().array(), (int)val1, (int)(val2 > 256 ? 256 : val2)); - ByteBuffer shab = ByteBuffer.wrap( sha256.digest() ); - shab.order( ByteOrder.LITTLE_ENDIAN ); - - state.set_B1( AT_API_Helper.getByteArray( shab.getLong( 0 ) ) ); - state.set_B2( AT_API_Helper.getByteArray( shab.getLong( 8 ) ) ); - state.set_B3( AT_API_Helper.getByteArray( shab.getLong( 16 ) ) ); - state.set_B4( AT_API_Helper.getByteArray( shab.getLong( 24 ) ) ); - - } catch (NoSuchAlgorithmException e) { - //not expected to reach that point - e.printStackTrace(); - } - } - +/* + * Copyright (c) 2014 CIYAM Developers + + Distributed under the MIT/X11 software license, please refer to the file license.txt + in the root project directory or http://www.opensource.org/licenses/mit-license.php. + + */ + +package nxt.at; + +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; + +import nxt.Constants; +import nxt.util.Convert; +import fr.cryptohash.RIPEMD160; + + + +public class AT_API_Impl implements AT_API +{ + + AT_API_Platform_Impl platform = AT_API_Platform_Impl.getInstance(); + + + @Override + public long get_A1( AT_Machine_State state ) { + return AT_API_Helper.getLong( state.get_A1() ); + } + + @Override + public long get_A2( AT_Machine_State state ) { + return AT_API_Helper.getLong( state.get_A2() ); + } + + @Override + public long get_A3( AT_Machine_State state ) { + return AT_API_Helper.getLong( state.get_A3() ); + } + + @Override + public long get_A4( AT_Machine_State state ) { + return AT_API_Helper.getLong( state.get_A4() ); + } + + @Override + public long get_B1( AT_Machine_State state ) { + return AT_API_Helper.getLong( state.get_B1() ); + } + + @Override + public long get_B2( AT_Machine_State state ) { + return AT_API_Helper.getLong( state.get_B2() ); + } + + @Override + public long get_B3( AT_Machine_State state ) { + return AT_API_Helper.getLong( state.get_B3() ); + } + + @Override + public long get_B4( AT_Machine_State state ) { + return AT_API_Helper.getLong( state.get_B4() ); + } + + @Override + public void set_A1( long val , AT_Machine_State state ) { + state.set_A1( AT_API_Helper.getByteArray( val ) ); + } + + @Override + public void set_A2( long val , AT_Machine_State state ) { + state.set_A2( AT_API_Helper.getByteArray( val ) ); + } + + @Override + public void set_A3( long val , AT_Machine_State state ) { + state.set_A3( AT_API_Helper.getByteArray( val ) ); + } + + @Override + public void set_A4( long val , AT_Machine_State state ) { + state.set_A4( AT_API_Helper.getByteArray( val ) ); + } + + @Override + public void set_A1_A2( long val1 , long val2 , AT_Machine_State state ) { + state.set_A1( AT_API_Helper.getByteArray( val1 ) ); + state.set_A2( AT_API_Helper.getByteArray( val2 ) ); + } + + @Override + public void set_A3_A4( long val1 , long val2 ,AT_Machine_State state ) { + state.set_A3( AT_API_Helper.getByteArray( val1 ) ); + state.set_A4( AT_API_Helper.getByteArray( val2 ) ); + + } + + @Override + public void set_B1( long val , AT_Machine_State state ) { + state.set_B1( AT_API_Helper.getByteArray( val ) ); + } + + @Override + public void set_B2( long val , AT_Machine_State state ) { + state.set_B2( AT_API_Helper.getByteArray( val ) ); + } + + @Override + public void set_B3( long val , AT_Machine_State state ) { + state.set_B3( AT_API_Helper.getByteArray( val ) ); + } + + @Override + public void set_B4( long val , AT_Machine_State state ) { + state.set_B4( AT_API_Helper.getByteArray( val ) ); + } + + @Override + public void set_B1_B2( long val1 , long val2 , AT_Machine_State state ) { + state.set_B1( AT_API_Helper.getByteArray( val1 ) ); + state.set_B2( AT_API_Helper.getByteArray( val2 ) ); + } + + @Override + public void set_B3_B4( long val3 , long val4 , AT_Machine_State state ) { + state.set_B3( AT_API_Helper.getByteArray( val3 ) ); + state.set_B4( AT_API_Helper.getByteArray( val4 ) ); + } + + @Override + public void clear_A( AT_Machine_State state ) { + byte[] b = new byte[ 8 ]; + state.set_A1( b ); + state.set_A2( b ); + state.set_A3( b ); + state.set_A4( b ); + } + + @Override + public void clear_B( AT_Machine_State state ) { + byte[] b = new byte[ 8 ]; + state.set_B1( b ); + state.set_B2( b ); + state.set_B3( b ); + state.set_B4( b ); + } + + @Override + public void copy_A_From_B( AT_Machine_State state ) { + state.set_A1( state.get_B1() ); + state.set_A2( state.get_B2() ); + state.set_A3( state.get_B3() ); + state.set_A4( state.get_B4() ); + } + + @Override + public void copy_B_From_A( AT_Machine_State state ) { + state.set_B1( state.get_A1() ); + state.set_B2( state.get_A2() ); + state.set_B3( state.get_A3() ); + state.set_B4( state.get_A4() ); + } + + @Override + public long check_A_Is_Zero( AT_Machine_State state ) { + byte[] b = new byte[ 8 ]; + return ( Arrays.equals( state.get_A1() , b ) && + Arrays.equals( state.get_A2() , b ) && + Arrays.equals( state.get_A3() , b ) && + Arrays.equals( state.get_A4() , b ) ) ? 0 : 1 ; + } + + @Override + public long check_B_Is_Zero( AT_Machine_State state ) { + byte[] b = new byte[ 8 ]; + return ( Arrays.equals( state.get_B1() , b ) && + Arrays.equals( state.get_B2() , b ) && + Arrays.equals( state.get_B3() , b ) && + Arrays.equals( state.get_B4() , b ) ) ? 0 : 1 ; + } + + public long check_A_equals_B( AT_Machine_State state ) { + return ( Arrays.equals( state.get_A1() , state.get_B1() ) && + Arrays.equals( state.get_A2() , state.get_B2() ) && + Arrays.equals( state.get_A3() , state.get_B3() ) && + Arrays.equals( state.get_A4() , state.get_B4() ) ) ? 1 : 0; + } + + @Override + public void swap_A_and_B( AT_Machine_State state ) { + byte[] b = new byte[ 8 ]; + + b = state.get_A1().clone(); + state.set_A1( state.get_B1() ); + state.set_B1( b ); + + b = state.get_A2().clone(); + state.set_A2( state.get_B2() ); + state.set_B2( b ); + + b = state.get_A3().clone(); + state.set_A3( state.get_B3() ); + state.set_B3( b ); + + b = state.get_A4().clone(); + state.set_A4( state.get_B4() ); + state.set_B4( b ); + + } + + @Override + public void add_A_to_B( AT_Machine_State state ) { + BigInteger a = AT_API_Helper.getBigInteger(state.get_A1(), state.get_A2(), state.get_A3(), state.get_A4()); + BigInteger b = AT_API_Helper.getBigInteger(state.get_B1(), state.get_B2(), state.get_B3(), state.get_B4()); + BigInteger result = a.add(b); + ByteBuffer resultBuffer = ByteBuffer.wrap(AT_API_Helper.getByteArray(result)); + resultBuffer.order(ByteOrder.LITTLE_ENDIAN); + + byte[] temp = new byte[8]; + resultBuffer.get(temp, 0, 8); + state.set_B1(temp); + resultBuffer.get(temp, 0, 8); + state.set_B2(temp); + resultBuffer.get(temp, 0, 8); + state.set_B3(temp); + resultBuffer.get(temp, 0, 8); + state.set_B4(temp); + } + + @Override + public void add_B_to_A( AT_Machine_State state ) { + BigInteger a = AT_API_Helper.getBigInteger(state.get_A1(), state.get_A2(), state.get_A3(), state.get_A4()); + BigInteger b = AT_API_Helper.getBigInteger(state.get_B1(), state.get_B2(), state.get_B3(), state.get_B4()); + BigInteger result = a.add(b); + ByteBuffer resultBuffer = ByteBuffer.wrap(AT_API_Helper.getByteArray(result)); + resultBuffer.order(ByteOrder.LITTLE_ENDIAN); + + byte[] temp = new byte[8]; + resultBuffer.get(temp, 0, 8); + state.set_A1(temp); + resultBuffer.get(temp, 0, 8); + state.set_A2(temp); + resultBuffer.get(temp, 0, 8); + state.set_A3(temp); + resultBuffer.get(temp, 0, 8); + state.set_A4(temp); + } + + @Override + public void sub_A_from_B( AT_Machine_State state ) { + BigInteger a = AT_API_Helper.getBigInteger(state.get_A1(), state.get_A2(), state.get_A3(), state.get_A4()); + BigInteger b = AT_API_Helper.getBigInteger(state.get_B1(), state.get_B2(), state.get_B3(), state.get_B4()); + BigInteger result = b.subtract(a); + ByteBuffer resultBuffer = ByteBuffer.wrap(AT_API_Helper.getByteArray(result)); + resultBuffer.order(ByteOrder.LITTLE_ENDIAN); + + byte[] temp = new byte[8]; + resultBuffer.get(temp, 0, 8); + state.set_B1(temp); + resultBuffer.get(temp, 0, 8); + state.set_B2(temp); + resultBuffer.get(temp, 0, 8); + state.set_B3(temp); + resultBuffer.get(temp, 0, 8); + state.set_B4(temp); + } + + @Override + public void sub_B_from_A( AT_Machine_State state ) { + BigInteger a = AT_API_Helper.getBigInteger(state.get_A1(), state.get_A2(), state.get_A3(), state.get_A4()); + BigInteger b = AT_API_Helper.getBigInteger(state.get_B1(), state.get_B2(), state.get_B3(), state.get_B4()); + BigInteger result = a.subtract(b); + ByteBuffer resultBuffer = ByteBuffer.wrap(AT_API_Helper.getByteArray(result)); + resultBuffer.order(ByteOrder.LITTLE_ENDIAN); + + byte[] temp = new byte[8]; + resultBuffer.get(temp, 0, 8); + state.set_A1(temp); + resultBuffer.get(temp, 0, 8); + state.set_A2(temp); + resultBuffer.get(temp, 0, 8); + state.set_A3(temp); + resultBuffer.get(temp, 0, 8); + state.set_A4(temp); + } + + @Override + public void mul_A_by_B( AT_Machine_State state ) { + BigInteger a = AT_API_Helper.getBigInteger(state.get_A1(), state.get_A2(), state.get_A3(), state.get_A4()); + BigInteger b = AT_API_Helper.getBigInteger(state.get_B1(), state.get_B2(), state.get_B3(), state.get_B4()); + BigInteger result = a.multiply(b); + ByteBuffer resultBuffer = ByteBuffer.wrap(AT_API_Helper.getByteArray(result)); + resultBuffer.order(ByteOrder.LITTLE_ENDIAN); + + byte[] temp = new byte[8]; + resultBuffer.get(temp, 0, 8); + state.set_B1(temp); + resultBuffer.get(temp, 0, 8); + state.set_B2(temp); + resultBuffer.get(temp, 0, 8); + state.set_B3(temp); + resultBuffer.get(temp, 0, 8); + state.set_B4(temp); + } + + @Override + public void mul_B_by_A( AT_Machine_State state ) { + BigInteger a = AT_API_Helper.getBigInteger(state.get_A1(), state.get_A2(), state.get_A3(), state.get_A4()); + BigInteger b = AT_API_Helper.getBigInteger(state.get_B1(), state.get_B2(), state.get_B3(), state.get_B4()); + BigInteger result = a.multiply(b); + ByteBuffer resultBuffer = ByteBuffer.wrap(AT_API_Helper.getByteArray(result)); + resultBuffer.order(ByteOrder.LITTLE_ENDIAN); + + byte[] temp = new byte[8]; + resultBuffer.get(temp, 0, 8); + state.set_A1(temp); + resultBuffer.get(temp, 0, 8); + state.set_A2(temp); + resultBuffer.get(temp, 0, 8); + state.set_A3(temp); + resultBuffer.get(temp, 0, 8); + state.set_A4(temp); + } + + @Override + public void div_A_by_B( AT_Machine_State state ) { + BigInteger a = AT_API_Helper.getBigInteger(state.get_A1(), state.get_A2(), state.get_A3(), state.get_A4()); + BigInteger b = AT_API_Helper.getBigInteger(state.get_B1(), state.get_B2(), state.get_B3(), state.get_B4()); + if(b.compareTo(BigInteger.ZERO) == 0) + return; + BigInteger result = a.divide(b); + ByteBuffer resultBuffer = ByteBuffer.wrap(AT_API_Helper.getByteArray(result)); + resultBuffer.order(ByteOrder.LITTLE_ENDIAN); + + byte[] temp = new byte[8]; + resultBuffer.get(temp, 0, 8); + state.set_B1(temp); + resultBuffer.get(temp, 0, 8); + state.set_B2(temp); + resultBuffer.get(temp, 0, 8); + state.set_B3(temp); + resultBuffer.get(temp, 0, 8); + state.set_B4(temp); + } + + @Override + public void div_B_by_A( AT_Machine_State state ) { + BigInteger a = AT_API_Helper.getBigInteger(state.get_A1(), state.get_A2(), state.get_A3(), state.get_A4()); + BigInteger b = AT_API_Helper.getBigInteger(state.get_B1(), state.get_B2(), state.get_B3(), state.get_B4()); + if(a.compareTo(BigInteger.ZERO) == 0) + return; + BigInteger result = b.divide(a); + ByteBuffer resultBuffer = ByteBuffer.wrap(AT_API_Helper.getByteArray(result)); + resultBuffer.order(ByteOrder.LITTLE_ENDIAN); + + byte[] temp = new byte[8]; + resultBuffer.get(temp, 0, 8); + state.set_A1(temp); + resultBuffer.get(temp, 0, 8); + state.set_A2(temp); + resultBuffer.get(temp, 0, 8); + state.set_A3(temp); + resultBuffer.get(temp, 0, 8); + state.set_A4(temp); + } + + @Override + public void or_A_with_B ( AT_Machine_State state ) { + ByteBuffer a = ByteBuffer.allocate(32); + a.order( ByteOrder.LITTLE_ENDIAN ); + a.put(state.get_A1()); + a.put(state.get_A2()); + a.put(state.get_A3()); + a.put(state.get_A4()); + a.clear(); + + ByteBuffer b = ByteBuffer.allocate(32); + b.order( ByteOrder.LITTLE_ENDIAN ); + b.put(state.get_B1()); + b.put(state.get_B2()); + b.put(state.get_B3()); + b.put(state.get_B4()); + b.clear(); + + state.set_A1(AT_API_Helper.getByteArray(a.getLong(0) | b.getLong(0))); + state.set_A2(AT_API_Helper.getByteArray(a.getLong(8) | b.getLong(8))); + state.set_A3(AT_API_Helper.getByteArray(a.getLong(16) | b.getLong(16))); + state.set_A4(AT_API_Helper.getByteArray(a.getLong(24) | b.getLong(24))); + } + + @Override + public void or_B_with_A ( AT_Machine_State state ) { + ByteBuffer a = ByteBuffer.allocate(32); + a.order( ByteOrder.LITTLE_ENDIAN ); + a.put(state.get_A1()); + a.put(state.get_A2()); + a.put(state.get_A3()); + a.put(state.get_A4()); + a.clear(); + + ByteBuffer b = ByteBuffer.allocate(32); + b.order( ByteOrder.LITTLE_ENDIAN ); + b.put(state.get_B1()); + b.put(state.get_B2()); + b.put(state.get_B3()); + b.put(state.get_B4()); + b.clear(); + + state.set_B1(AT_API_Helper.getByteArray(a.getLong(0) | b.getLong(0))); + state.set_B2(AT_API_Helper.getByteArray(a.getLong(8) | b.getLong(8))); + state.set_B3(AT_API_Helper.getByteArray(a.getLong(16) | b.getLong(16))); + state.set_B4(AT_API_Helper.getByteArray(a.getLong(24) | b.getLong(24))); + } + + @Override + public void and_A_with_B ( AT_Machine_State state ) { + ByteBuffer a = ByteBuffer.allocate(32); + a.order( ByteOrder.LITTLE_ENDIAN ); + a.put(state.get_A1()); + a.put(state.get_A2()); + a.put(state.get_A3()); + a.put(state.get_A4()); + a.clear(); + + ByteBuffer b = ByteBuffer.allocate(32); + b.order( ByteOrder.LITTLE_ENDIAN ); + b.put(state.get_B1()); + b.put(state.get_B2()); + b.put(state.get_B3()); + b.put(state.get_B4()); + b.clear(); + + state.set_A1(AT_API_Helper.getByteArray(a.getLong(0) & b.getLong(0))); + state.set_A2(AT_API_Helper.getByteArray(a.getLong(8) & b.getLong(8))); + state.set_A3(AT_API_Helper.getByteArray(a.getLong(16) & b.getLong(16))); + state.set_A4(AT_API_Helper.getByteArray(a.getLong(24) & b.getLong(24))); + } + + @Override + public void and_B_with_A ( AT_Machine_State state ) { + ByteBuffer a = ByteBuffer.allocate(32); + a.order( ByteOrder.LITTLE_ENDIAN ); + a.put(state.get_A1()); + a.put(state.get_A2()); + a.put(state.get_A3()); + a.put(state.get_A4()); + a.clear(); + + ByteBuffer b = ByteBuffer.allocate(32); + b.order( ByteOrder.LITTLE_ENDIAN ); + b.put(state.get_B1()); + b.put(state.get_B2()); + b.put(state.get_B3()); + b.put(state.get_B4()); + b.clear(); + + state.set_B1(AT_API_Helper.getByteArray(a.getLong(0) & b.getLong(0))); + state.set_B2(AT_API_Helper.getByteArray(a.getLong(8) & b.getLong(8))); + state.set_B3(AT_API_Helper.getByteArray(a.getLong(16) & b.getLong(16))); + state.set_B4(AT_API_Helper.getByteArray(a.getLong(24) & b.getLong(24))); + } + + @Override + public void xor_A_with_B ( AT_Machine_State state ) { + ByteBuffer a = ByteBuffer.allocate(32); + a.order( ByteOrder.LITTLE_ENDIAN ); + a.put(state.get_A1()); + a.put(state.get_A2()); + a.put(state.get_A3()); + a.put(state.get_A4()); + a.clear(); + + ByteBuffer b = ByteBuffer.allocate(32); + b.order( ByteOrder.LITTLE_ENDIAN ); + b.put(state.get_B1()); + b.put(state.get_B2()); + b.put(state.get_B3()); + b.put(state.get_B4()); + b.clear(); + + state.set_A1(AT_API_Helper.getByteArray(a.getLong(0) ^ b.getLong(0))); + state.set_A2(AT_API_Helper.getByteArray(a.getLong(8) ^ b.getLong(8))); + state.set_A3(AT_API_Helper.getByteArray(a.getLong(16) ^ b.getLong(16))); + state.set_A4(AT_API_Helper.getByteArray(a.getLong(24) ^ b.getLong(24))); + } + + @Override + public void xor_B_with_A ( AT_Machine_State state ) { + ByteBuffer a = ByteBuffer.allocate(32); + a.order( ByteOrder.LITTLE_ENDIAN ); + a.put(state.get_A1()); + a.put(state.get_A2()); + a.put(state.get_A3()); + a.put(state.get_A4()); + a.clear(); + + ByteBuffer b = ByteBuffer.allocate(32); + b.order( ByteOrder.LITTLE_ENDIAN ); + b.put(state.get_B1()); + b.put(state.get_B2()); + b.put(state.get_B3()); + b.put(state.get_B4()); + b.clear(); + + state.set_B1(AT_API_Helper.getByteArray(a.getLong(0) ^ b.getLong(0))); + state.set_B2(AT_API_Helper.getByteArray(a.getLong(8) ^ b.getLong(8))); + state.set_B3(AT_API_Helper.getByteArray(a.getLong(16) ^ b.getLong(16))); + state.set_B4(AT_API_Helper.getByteArray(a.getLong(24) ^ b.getLong(24))); + } + + @Override + public void MD5_A_to_B( AT_Machine_State state ) { + ByteBuffer b = ByteBuffer.allocate( 16 ); + b.order( ByteOrder.LITTLE_ENDIAN ); + + b.put( state.get_A1() ); + b.put( state.get_A2() ); + + try { + MessageDigest md5 = MessageDigest.getInstance("MD5"); + ByteBuffer mdb = ByteBuffer.wrap( md5.digest( b.array() ) ); + mdb.order( ByteOrder.LITTLE_ENDIAN ); + + state.set_B1( AT_API_Helper.getByteArray( mdb.getLong(0) ) ); + state.set_B1( AT_API_Helper.getByteArray( mdb.getLong(8) ) ); + + } catch (NoSuchAlgorithmException e) { + //not expected to reach that point + e.printStackTrace(); + } + } + + + @Override + public long check_MD5_A_with_B( AT_Machine_State state ) { + if ( state.getHeight() >= Constants.AT_FIX_BLOCK_3 ) { + ByteBuffer b = ByteBuffer.allocate( 16 ); + b.order( ByteOrder.LITTLE_ENDIAN ); + + b.put( state.get_A1() ); + b.put( state.get_A2() ); + + try { + MessageDigest md5 = MessageDigest.getInstance("MD5"); + ByteBuffer mdb = ByteBuffer.wrap( md5.digest( b.array() ) ); + mdb.order( ByteOrder.LITTLE_ENDIAN ); + + return ( mdb.getLong(0) == AT_API_Helper.getLong( state.get_B1() ) && + mdb.getLong(8) == AT_API_Helper.getLong( state.get_B2() ) ) ? 1 : 0; + } catch (NoSuchAlgorithmException e) { + //not expected to reach that point + e.printStackTrace(); + throw new RuntimeException("Failed to check md5"); + } + } + else { + return ( Arrays.equals( state.get_A1() , state.get_B1() ) && + Arrays.equals( state.get_A2() , state.get_B2() ) ) ? 1 : 0; + } + } + + @Override + public void HASH160_A_to_B( AT_Machine_State state ) { + ByteBuffer b = ByteBuffer.allocate(32); + b.order(ByteOrder.LITTLE_ENDIAN); + + b.put(state.get_A1()); + b.put(state.get_A2()); + b.put(state.get_A3()); + b.put(state.get_A4()); + + RIPEMD160 ripemd160 = new RIPEMD160(); + ByteBuffer ripemdb = ByteBuffer.wrap(ripemd160.digest(b.array())); + ripemdb.order(ByteOrder.LITTLE_ENDIAN); + + state.set_B1(AT_API_Helper.getByteArray(ripemdb.getLong(0))); + state.set_B2(AT_API_Helper.getByteArray(ripemdb.getLong(8))); + state.set_B3(AT_API_Helper.getByteArray((long)ripemdb.getInt(16))); + + } + + @Override + public long check_HASH160_A_with_B( AT_Machine_State state ) { + if ( state.getHeight() >= Constants.AT_FIX_BLOCK_3 ) { + ByteBuffer b = ByteBuffer.allocate( 32 ); + b.order( ByteOrder.LITTLE_ENDIAN ); + + b.put( state.get_A1() ); + b.put( state.get_A2() ); + b.put( state.get_A3() ); + b.put( state.get_A4() ); + + RIPEMD160 ripemd160 = new RIPEMD160(); + ByteBuffer ripemdb = ByteBuffer.wrap( ripemd160.digest( b.array() ) ); + ripemdb.order( ByteOrder.LITTLE_ENDIAN ); + + return ( ripemdb.getLong(0) == AT_API_Helper.getLong( state.get_B1() ) && + ripemdb.getLong(8) == AT_API_Helper.getLong( state.get_B2() ) && + ripemdb.getInt(16) == ((int)(AT_API_Helper.getLong( state.get_B3() ) & 0x00000000FFFFFFFFL )) + ) ? 1 : 0; + } + else { + return(Arrays.equals(state.get_A1(), state.get_B1()) && + Arrays.equals(state.get_A2(), state.get_B2()) && + (AT_API_Helper.getLong(state.get_A3()) & 0x00000000FFFFFFFFL) == (AT_API_Helper.getLong(state.get_B3()) & 0x00000000FFFFFFFFL)) ? 1 : 0; + } + } + + @Override + public void SHA256_A_to_B( AT_Machine_State state ) { + ByteBuffer b = ByteBuffer.allocate( 32 ); + b.order( ByteOrder.LITTLE_ENDIAN ); + + b.put( state.get_A1() ); + b.put( state.get_A2() ); + b.put( state.get_A3() ); + b.put( state.get_A4() ); + + try { + MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); + ByteBuffer shab = ByteBuffer.wrap( sha256.digest( b.array() ) ); + shab.order( ByteOrder.LITTLE_ENDIAN ); + + state.set_B1( AT_API_Helper.getByteArray( shab.getLong( 0 ) ) ); + state.set_B2( AT_API_Helper.getByteArray( shab.getLong( 8 ) ) ); + state.set_B3( AT_API_Helper.getByteArray( shab.getLong( 16 ) ) ); + state.set_B4( AT_API_Helper.getByteArray( shab.getLong( 24 ) ) ); + + } catch (NoSuchAlgorithmException e) { + //not expected to reach that point + e.printStackTrace(); + } + } + + @Override + public long check_SHA256_A_with_B( AT_Machine_State state ) { + if ( state.getHeight() >= Constants.AT_FIX_BLOCK_3 ) { + ByteBuffer b = ByteBuffer.allocate(32); + b.order( ByteOrder.LITTLE_ENDIAN ); + + b.put( state.get_A1() ); + b.put( state.get_A2() ); + b.put( state.get_A3() ); + b.put( state.get_A4() ); + + try { + MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); + ByteBuffer shab = ByteBuffer.wrap( sha256.digest( b.array() ) ); + shab.order( ByteOrder.LITTLE_ENDIAN ); + + return ( shab.getLong(0) == AT_API_Helper.getLong( state.get_B1() ) && + shab.getLong(8) == AT_API_Helper.getLong( state.get_B2() ) && + shab.getLong(16) == AT_API_Helper.getLong( state.get_B3() ) && + shab.getLong(24) == AT_API_Helper.getLong( state.get_B4() ) ) ? 1 : 0; + } catch (NoSuchAlgorithmException e) { + //not expected to reach that point + e.printStackTrace(); + throw new RuntimeException("Failed to check sha256"); + } + } + else { + return ( Arrays.equals( state.get_A1() , state.get_B1() ) && + Arrays.equals( state.get_A2() , state.get_B2() ) && + Arrays.equals( state.get_A3() , state.get_B3() ) && + Arrays.equals( state.get_A4() , state.get_B4() )) ? 1 : 0; + } + } + + @Override + public long get_Block_Timestamp( AT_Machine_State state ) { + return platform.get_Block_Timestamp( state ); + + } + + @Override + public long get_Creation_Timestamp( AT_Machine_State state ) { + return platform.get_Creation_Timestamp( state ); + } + + @Override + public long get_Last_Block_Timestamp( AT_Machine_State state ) { + return platform.get_Last_Block_Timestamp( state ); + } + + @Override + public void put_Last_Block_Hash_In_A( AT_Machine_State state ) { + platform.put_Last_Block_Hash_In_A( state ); + + } + + @Override + public void A_to_Tx_after_Timestamp( long val , AT_Machine_State state ) { + platform.A_to_Tx_after_Timestamp( val , state ); + + } + + @Override + public long get_Type_for_Tx_in_A( AT_Machine_State state ) { + return platform.get_Type_for_Tx_in_A( state ); + } + + @Override + public long get_Amount_for_Tx_in_A( AT_Machine_State state ) { + return platform.get_Amount_for_Tx_in_A( state ); + } + + @Override + public long get_Timestamp_for_Tx_in_A( AT_Machine_State state ) { + return platform.get_Timestamp_for_Tx_in_A( state ); + } + + @Override + public long get_Random_Id_for_Tx_in_A( AT_Machine_State state ) { + return platform.get_Random_Id_for_Tx_in_A( state ); + } + + @Override + public void message_from_Tx_in_A_to_B( AT_Machine_State state ) { + platform.message_from_Tx_in_A_to_B( state ); + } + + @Override + public void B_to_Address_of_Tx_in_A( AT_Machine_State state ) { + + platform.B_to_Address_of_Tx_in_A( state ); + } + + @Override + public void B_to_Address_of_Creator( AT_Machine_State state ) { + platform.B_to_Address_of_Creator( state ); + + } + + @Override + public long get_Current_Balance( AT_Machine_State state ) { + return platform.get_Current_Balance( state ); + } + + @Override + public long get_Previous_Balance( AT_Machine_State state ) { + return platform.get_Previous_Balance( state ); + } + + @Override + public void send_to_Address_in_B( long val , AT_Machine_State state ) { + platform.send_to_Address_in_B( val , state ); + } + + @Override + public void send_All_to_Address_in_B( AT_Machine_State state ) { + platform.send_All_to_Address_in_B( state ); + } + + @Override + public void send_Old_to_Address_in_B( AT_Machine_State state ) { + platform.send_Old_to_Address_in_B( state ); + } + + @Override + public void send_A_to_Address_in_B( AT_Machine_State state ) { + platform.send_A_to_Address_in_B( state ); + } + + @Override + public long add_Minutes_to_Timestamp( long val1 , long val2 , AT_Machine_State state ) { + return platform.add_Minutes_to_Timestamp( val1 , val2 , state ); + } + + @Override + public void set_Min_Activation_Amount( long val , AT_Machine_State state ) { + state.setMinActivationAmount(val); + } + + @Override + public void put_Last_Block_Generation_Signature_In_A( AT_Machine_State state ) { + platform.put_Last_Block_Generation_Signature_In_A( state ); + } + + @Override + public void SHA256_to_B( long val1 , long val2 , AT_Machine_State state ) { + if(val1 < 0 || val2 < 0 || + (val1 + val2 - 1) < 0 || + ((long)val1)*8+8>((long)Integer.MAX_VALUE) || + val1*8+8>state.getDsize() || + ((long)val1 + (long)val2 - 1)*8+8>((long)Integer.MAX_VALUE) || + (val1 + val2 - 1)*8+8>state.getDsize()) + { + return; + } + + try { + MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); + sha256.update(state.getAp_data().array(), (int)val1, (int)(val2 > 256 ? 256 : val2)); + ByteBuffer shab = ByteBuffer.wrap( sha256.digest() ); + shab.order( ByteOrder.LITTLE_ENDIAN ); + + state.set_B1( AT_API_Helper.getByteArray( shab.getLong( 0 ) ) ); + state.set_B2( AT_API_Helper.getByteArray( shab.getLong( 8 ) ) ); + state.set_B3( AT_API_Helper.getByteArray( shab.getLong( 16 ) ) ); + state.set_B4( AT_API_Helper.getByteArray( shab.getLong( 24 ) ) ); + + } catch (NoSuchAlgorithmException e) { + //not expected to reach that point + e.printStackTrace(); + } + } + } \ No newline at end of file diff --git a/src/java/nxt/http/SubmitNonce.java b/src/java/nxt/http/SubmitNonce.java index 16832f683..beb1acc85 100644 --- a/src/java/nxt/http/SubmitNonce.java +++ b/src/java/nxt/http/SubmitNonce.java @@ -1,7 +1,8 @@ package nxt.http; import java.nio.ByteBuffer; - +import java.math.BigInteger; //used to write deadline +import nxt.util.Logger;//used to write deadline import javax.servlet.http.HttpServletRequest; import nxt.Account; @@ -19,6 +20,8 @@ public final class SubmitNonce extends APIServlet.APIRequestHandler { static final SubmitNonce instance = new SubmitNonce(); + static final int MaxDeadlineToLog = Nxt.getIntProperty("nxt.MaxDeadlineToLog"); //potential bug:you cannot set the max to more than what fits in an int + private SubmitNonce() { super(new APITag[] {APITag.MINING}, "secretPhrase", "nonce", "accountId"); } @@ -94,10 +97,22 @@ else if(assignment.getFromHeight() > Nxt.getBlockchain().getLastBlock().getHeigh response.put("result", "failed to create generator"); return response; } - + //this will write all interesting deadlines submitted to the wallet from miners to the console. + //to enable the feature, add + //MaxDeadlineToLog=100000 + //to the config file - the number 100000 means that all deadlines with a value below that, gets logged + BigInteger deadline = generator.getDeadline(); + + if (MaxDeadlineToLog!=0){ + BigInteger maxToReport = BigInteger.valueOf(MaxDeadlineToLog); + if (maxToReport.compareTo( deadline) >0){ + String log_msg = "Block:"+(Nxt.getBlockchain().getLastBlock().getHeight() + 1)+" Nonce: "+String.format("%15s",nonce)+ " Deadline:" + String.format("%8s",deadline); + Logger.logMessage(log_msg); + } + } //response.put("result", "deadline: " + generator.getDeadline()); response.put("result", "success"); - response.put("deadline", generator.getDeadline()); + response.put("deadline", deadline); return response; }