/****************************************************************************** * Copyright © 2013-2016 The Nxt Core Developers. * * * * See the AUTHORS.txt, DEVELOPER-AGREEMENT.txt and LICENSE.txt files at * * the top-level directory of this distribution for the individual copyright * * holder information and the developer policies on copyright and licensing. * * * * Unless otherwise agreed in a custom licensing agreement, no part of the * * Nxt software, including this file, may be copied, modified, propagated, * * or distributed except according to the terms contained in the LICENSE.txt * * file. * * * * Removal or modification of this copyright notice is prohibited. * * * ******************************************************************************/ package nxt.http; import nxt.Account; import nxt.Appendix; import nxt.Attachment; import nxt.Constants; import nxt.Nxt; import nxt.NxtException; import nxt.PhasingParams; import nxt.Transaction; import nxt.crypto.Crypto; import nxt.util.Convert; import org.json.simple.JSONObject; import org.json.simple.JSONStreamAware; import javax.servlet.http.HttpServletRequest; import java.util.Arrays; import static nxt.http.JSONResponses.FEATURE_NOT_AVAILABLE; import static nxt.http.JSONResponses.INCORRECT_DEADLINE; import static nxt.http.JSONResponses.INCORRECT_LINKED_FULL_HASH; import static nxt.http.JSONResponses.INCORRECT_WHITELIST; import static nxt.http.JSONResponses.MISSING_DEADLINE; import static nxt.http.JSONResponses.MISSING_SECRET_PHRASE; import static nxt.http.JSONResponses.NOT_ENOUGH_FUNDS; abstract class CreateTransaction extends APIServlet.APIRequestHandler { private static final String[] commonParameters = new String[]{"secretPhrase", "publicKey", "feeNQT", "deadline", "referencedTransactionFullHash", "broadcast", "message", "messageIsText", "messageIsPrunable", "messageToEncrypt", "messageToEncryptIsText", "encryptedMessageData", "encryptedMessageNonce", "encryptedMessageIsPrunable", "compressMessageToEncrypt", "messageToEncryptToSelf", "messageToEncryptToSelfIsText", "encryptToSelfMessageData", "encryptToSelfMessageNonce", "compressMessageToEncryptToSelf", "phased", "phasingFinishHeight", "phasingVotingModel", "phasingQuorum", "phasingMinBalance", "phasingHolding", "phasingMinBalanceModel", "phasingWhitelisted", "phasingWhitelisted", "phasingWhitelisted", "phasingLinkedFullHash", "phasingLinkedFullHash", "phasingLinkedFullHash", "phasingHashedSecret", "phasingHashedSecretAlgorithm", "recipientPublicKey"}; private static String[] addCommonParameters(String[] parameters) { String[] result = Arrays.copyOf(parameters, parameters.length + commonParameters.length); System.arraycopy(commonParameters, 0, result, parameters.length, commonParameters.length); return result; } CreateTransaction(APITag[] apiTags, String... parameters) { super(apiTags, addCommonParameters(parameters)); } CreateTransaction(String fileParameter, APITag[] apiTags, String... parameters) { super(fileParameter, apiTags, addCommonParameters(parameters)); } final JSONStreamAware createTransaction(HttpServletRequest req, Account senderAccount, Attachment attachment) throws NxtException { return createTransaction(req, senderAccount, 0, 0, attachment); } final JSONStreamAware createTransaction(HttpServletRequest req, Account senderAccount, long recipientId, long amountNQT) throws NxtException { return createTransaction(req, senderAccount, recipientId, amountNQT, Attachment.ORDINARY_PAYMENT); } private Appendix.Phasing parsePhasing(HttpServletRequest req) throws ParameterException { int finishHeight = ParameterParser.getInt(req, "phasingFinishHeight", Nxt.getBlockchain().getHeight() + 1, Nxt.getBlockchain().getHeight() + Constants.MAX_PHASING_DURATION + 1, true); PhasingParams phasingParams = parsePhasingParams(req, "phasing"); byte[][] linkedFullHashes = null; String[] linkedFullHashesValues = req.getParameterValues("phasingLinkedFullHash"); if (linkedFullHashesValues != null && linkedFullHashesValues.length > 0) { linkedFullHashes = new byte[linkedFullHashesValues.length][]; for (int i = 0; i < linkedFullHashes.length; i++) { linkedFullHashes[i] = Convert.parseHexString(linkedFullHashesValues[i]); if (Convert.emptyToNull(linkedFullHashes[i]) == null || linkedFullHashes[i].length != 32) { throw new ParameterException(INCORRECT_LINKED_FULL_HASH); } } } byte[] hashedSecret = Convert.parseHexString(Convert.emptyToNull(req.getParameter("phasingHashedSecret"))); byte algorithm = ParameterParser.getByte(req, "phasingHashedSecretAlgorithm", (byte) 0, Byte.MAX_VALUE, false); return new Appendix.Phasing(finishHeight, phasingParams, linkedFullHashes, hashedSecret, algorithm); } final PhasingParams parsePhasingParams(HttpServletRequest req, String parameterPrefix) throws ParameterException { byte votingModel = ParameterParser.getByte(req, parameterPrefix + "VotingModel", (byte)-1, (byte)5, true); long quorum = ParameterParser.getLong(req, parameterPrefix + "Quorum", 0, Long.MAX_VALUE, false); long minBalance = ParameterParser.getLong(req, parameterPrefix + "MinBalance", 0, Long.MAX_VALUE, false); byte minBalanceModel = ParameterParser.getByte(req, parameterPrefix + "MinBalanceModel", (byte)0, (byte)3, false); long holdingId = ParameterParser.getUnsignedLong(req, parameterPrefix + "Holding", false); long[] whitelist = null; String[] whitelistValues = req.getParameterValues(parameterPrefix + "Whitelisted"); if (whitelistValues != null && whitelistValues.length > 0) { whitelist = new long[whitelistValues.length]; for (int i = 0; i < whitelistValues.length; i++) { whitelist[i] = Convert.parseAccountId(whitelistValues[i]); if (whitelist[i] == 0) { throw new ParameterException(INCORRECT_WHITELIST); } } } return new PhasingParams(votingModel, holdingId, quorum, minBalance, minBalanceModel, whitelist); } final JSONStreamAware createTransaction(HttpServletRequest req, Account senderAccount, long recipientId, long amountNQT, Attachment attachment) throws NxtException { String deadlineValue = req.getParameter("deadline"); String referencedTransactionFullHash = Convert.emptyToNull(req.getParameter("referencedTransactionFullHash")); String secretPhrase = Convert.emptyToNull(req.getParameter("secretPhrase")); String publicKeyValue = Convert.emptyToNull(req.getParameter("publicKey")); boolean broadcast = !"false".equalsIgnoreCase(req.getParameter("broadcast")) && secretPhrase != null; Appendix.EncryptedMessage encryptedMessage = null; Appendix.PrunableEncryptedMessage prunableEncryptedMessage = null; if (attachment.getTransactionType().canHaveRecipient() && recipientId != 0) { Account recipient = Account.getAccount(recipientId); if ("true".equalsIgnoreCase(req.getParameter("encryptedMessageIsPrunable"))) { prunableEncryptedMessage = (Appendix.PrunableEncryptedMessage) ParameterParser.getEncryptedMessage(req, recipient, true); } else { encryptedMessage = (Appendix.EncryptedMessage) ParameterParser.getEncryptedMessage(req, recipient, false); } } Appendix.EncryptToSelfMessage encryptToSelfMessage = ParameterParser.getEncryptToSelfMessage(req); Appendix.Message message = null; Appendix.PrunablePlainMessage prunablePlainMessage = null; if ("true".equalsIgnoreCase(req.getParameter("messageIsPrunable"))) { prunablePlainMessage = (Appendix.PrunablePlainMessage) ParameterParser.getPlainMessage(req, true); } else { message = (Appendix.Message) ParameterParser.getPlainMessage(req, false); } Appendix.PublicKeyAnnouncement publicKeyAnnouncement = null; String recipientPublicKey = Convert.emptyToNull(req.getParameter("recipientPublicKey")); if (recipientPublicKey != null) { publicKeyAnnouncement = new Appendix.PublicKeyAnnouncement(Convert.parseHexString(recipientPublicKey)); } Appendix.Phasing phasing = null; boolean phased = "true".equalsIgnoreCase(req.getParameter("phased")); if (phased) { phasing = parsePhasing(req); } if (secretPhrase == null && publicKeyValue == null) { return MISSING_SECRET_PHRASE; } else if (deadlineValue == null) { return MISSING_DEADLINE; } short deadline; try { deadline = Short.parseShort(deadlineValue); if (deadline < 1) { return INCORRECT_DEADLINE; } } catch (NumberFormatException e) { return INCORRECT_DEADLINE; } long feeNQT = ParameterParser.getFeeNQT(req); JSONObject response = new JSONObject(); // shouldn't try to get publicKey from senderAccount as it may have not been set yet byte[] publicKey = secretPhrase != null ? Crypto.getPublicKey(secretPhrase) : Convert.parseHexString(publicKeyValue); try { Transaction.Builder builder = Nxt.newTransactionBuilder(publicKey, amountNQT, feeNQT, deadline, attachment).referencedTransactionFullHash(referencedTransactionFullHash); if (attachment.getTransactionType().canHaveRecipient()) { builder.recipientId(recipientId); } builder.appendix(encryptedMessage); builder.appendix(message); builder.appendix(publicKeyAnnouncement); builder.appendix(encryptToSelfMessage); builder.appendix(phasing); builder.appendix(prunablePlainMessage); builder.appendix(prunableEncryptedMessage); Transaction transaction = builder.build(secretPhrase); try { if (Math.addExact(amountNQT, transaction.getFeeNQT()) > senderAccount.getUnconfirmedBalanceNQT()) { return NOT_ENOUGH_FUNDS; } } catch (ArithmeticException e) { return NOT_ENOUGH_FUNDS; } JSONObject transactionJSON = JSONData.unconfirmedTransaction(transaction); response.put("transactionJSON", transactionJSON); try { response.put("unsignedTransactionBytes", Convert.toHexString(transaction.getUnsignedBytes())); } catch (NxtException.NotYetEncryptedException ignore) {} if (secretPhrase != null) { response.put("transaction", transaction.getStringId()); response.put("fullHash", transactionJSON.get("fullHash")); response.put("transactionBytes", Convert.toHexString(transaction.getBytes())); response.put("signatureHash", transactionJSON.get("signatureHash")); } if (broadcast) { Nxt.getTransactionProcessor().broadcast(transaction); response.put("broadcasted", true); } else { transaction.validate(); response.put("broadcasted", false); } } catch (NxtException.NotYetEnabledException e) { return FEATURE_NOT_AVAILABLE; } catch (NxtException.InsufficientBalanceException e) { throw e; } catch (NxtException.ValidationException e) { if (broadcast) { response.clear(); } response.put("broadcasted", false); JSONData.putException(response, e); } return response; } @Override final boolean requirePost() { return true; } @Override final boolean allowRequiredBlockParameters() { return false; } }